特点:
哈希表也称散列表,即在记录的存储地址(散列地址)和它的关键码(散列表)之间建立一个确定的对应关系(散列函数),如此,不经过比较,一次读取就能得到所查元素的查找方法。
散列表一般不适用于多个记录有同样关键码的情况;也不适用于范围的查找,如不能找到最大值或最小值。
散列函数的常用设计方法:
1、直接定址法:散列函数是关键码的线性函数,即 H(key)=a*key+b
适用于:事先知道关键码,关键码集合不大且连续性较好。
2、除留余数法:确定一个合适的整数p,H(key)=key mod p (mod:取余)
如何减少冲突:在确定了关键码个数后,哈希表的长度n一般选取比关键码数量略大的质数。
3、数字分析法:根据关键码在各个位上的分布情况,选取分布比较均匀的若干位组成散列地址。
适用情形:如存储学号,一般学号的前缀虽相同,但后两位肯定不同,可根据后两位来确定存储地址。
4、平方取中法:对关键码平方后,按散列表大小,取中间的若干位作为散列地址
适用于:事先不知道关键码且关键码位数不大
5、折叠法:将关键码从左到右分割成位数相等的几部分,将这几部分叠加求和,取结果的后几位作为存储地址
适用于:关键码位数较多,且事先不知道关键码的分别情况
散列函数并不局限于以上方法,可以根据实际需要自己设计。
当散列地址产生了冲突,如何处理:
1、开放定址法——线性探测法:发生冲突时,就去寻找下一个空的散列地址并将数据存入。
对于关键值key,散列表长度为m,则寻找下一个散列地址的公式为:
H=(H(key)+d)%m(d=1,2,3,…,m-1)
这种处理方法称为闭散列表
2、拉链法(链地址法):存放数据的结构体内部定义一个头指针,将产生冲突的关键码存放在头指针所指的链表中。
如此便可将同一类型的数据都集中在同一个结构体中,而不会像上一个方法那样产生堆积(抢占其他关键码的位置)。
当链表的结点大于8时,可将其转换为红黑树,提高搜索效率。
具体实现:
以下采用除留余数法和拉链法实现哈希表:
//哈希表
typedef struct HashLink
{
int key=0;
HashLink *next=nullptr;
}HashLink;
typedef struct HashMap
{
int key=0;
bool flag=false;//表示key是否已经存储了数据
HashLink *head=nullptr;
}HashMap;
//哈希碰撞:拉链法
//在链表中插入元素val,当个数大于8时转换为红黑树(这里暂不实现此功能)
void CreatLink(vector<HashMap> &H,int val)
{
if(H[val%7].head==nullptr)
{
H[val%7].head=new HashLink;
H[val%7].head->key=val;
}
else
{
HashLink *p=H[val%7].head;
while(p->next!=nullptr)
{
p=p->next;
}
p->next=new HashLink;
p->next->key=val;
}
}
//创建哈希表
void CreatHashMap(vector<HashMap> &H)
{
//确定数据
vector<int> v;
cout << "请输入要存储的数据(结束输入:100011):" << endl;
int val=0;
cin >> val;
while(val!=END)
{
v.push_back(val);
cin >> val;
}
//创建哈希表
int n=v.size();
H.resize(n);
for(int i=0;i<n;++i)
{
if(H[v[i]%7].flag==false)
{
H[v[i]%7].key=v[i];
H[v[i]%7].flag=true;
}
else
{
CreatLink(H,v[i]);
}
}
}
//哈希函数:除留余数法
bool SearchHashMap(vector<HashMap> &H,int val)
{
if(H[val%7].key==val)
return true;
else
{
auto p=H[val%7].head;
while(p!=nullptr)
{
if(p->key==val)
return true;
else
p=p->next;
}
return false;
}
}