您的位置:首页 > 其它

查找--- 哈希表( 散列表)

2011-07-14 09:01 274 查看
哈希表优点:把数据的存储和查找消耗的时间大大降低,几乎可以看作是常数时间,而代价是消耗比较多的内存 散列表由两部分组成:桶数组,散列函数
1.桶数组
散列表的桶数组是一个大小为N的数组A,其中A的每个单元可看作一只“桶”,整数N表示数组的容量,如果关键字为整数,且
均匀地分散在范围[0,N-1]中,这个桶数组就是所需要的数组。关键字为K的元素e被简单地插入到桶A[k]中。
2.散列函数
散列函数将字典中的每个关键字k映射到区间[0,N-1]内的一个整数,其中N是这个散列表的桶数组的容量。这个方法的主要思想是利用散列函数值h(k)作为桶数组A的下标,而不是使用关键字k,即将数据项(k,e)存储在桶A[h(k)]中。 对于散列函数性能的评估有两个行为组成:一个是将关键字映射到一个整数上,称为散列编码;一个是将散列编码映射到桶数组下标内的一个整数上,称为压缩映射。
关键字——>散列编码——>压缩映射到桶数组区间
3.压缩映射
1)除法散列法
h(k)=|k| mod N;
如果N为素数,则该方法有助于"扩散"散列值的分布;如果N不是素数,关键字分布中的模式在散列编码分布中出现重复的可能性更大,因而会有冲突。
2)MAD方法(乘加与除方法)
h(k)=|ak+b| mod N;其中N是素数,a,b是在确定压缩函数时随机选取的非负整数,满足a mod N 不等于0.

3)余数法:先估计整个哈希表中的表项目数目大小。然后用这个估计值作为除数去除每个原始值,得到商和余数。用余数作为哈希值。因为这种方法产生冲突的可能性相当大,因此任何搜索算法都应该能够判断冲突是否发生并提出取代算法。 
4)折叠法:这种方法是针对原始值为数字时使用,将原始值分为若干部分,然后将各部分叠加,得到的最后四个数字(或者取其他位数的数字都可以)来作为哈希值。 
5)基数转换法:当原始值是数字时,可以将原始值的数制基数转为一个不同的数字。例如,可以将十进制的原始值转为十六进制的哈希值。为了使哈希值的长度相同,可以省略高位数字。 
6)数据重排法:这种方法只是简单的将原始值中的数据打乱排序。比如可以将第三位到第六位的数字逆序排列,然后利用重排后的数字作为哈希值。
4.冲突处理模式
装填因子:一个良好的散列函数将字典中的n个数据存储在容量为N的桶数组中,并期望每个桶的大小为n/N,则为散列表的装填因子。即数据个数与桶数组容量的比,通常装填因子小于1.
1)开放定址法
当冲突发生时,使用某种探查(亦称探测)技术在散列表中形成一个探查(测)序列。沿此序列逐个单元地查找,直到找到给定的关键字,或者碰到一个开放的地址(即该地址单元为空)为止(若要插入,在探查到开放的地址,则可将待插入的新结点存人该地址单元)。查找时探查到开放的地址则表明表中无待查的关键字,即查找失败。
注意:
①用开放定址法建立散列表时,建表前须将表中所有单元(更严格地说,是指单元中存储的关键字)置空。
②空单元的表示与具体的应用相关
Hi=(H(key)+di) MOD m, i=1,2,3,……k(k<=m-1);
H(key)为哈希函数;m为哈希表的长度;di为增量序列;增量序列有3种取法:
第一种:线性探测再散列 di=1,2,3,4……m-1;
使用线性探测散列会产生聚焦或堆积(散列地址不同的结点争夺同一个后继散列地址的现像叫聚焦或堆积)为了减少堆积的发生,不能像线性探查法那样探查一个顺序的地址序列(相当于顺序查找),而应使探查序列跳跃式地散列在整个散列表中
第二种:二次探测再散列 di=1^2,-(1^2),2^2,-(2^2),……k^2,-(k^2)
第三种:再哈希法
Hash(key),ReHash(key)是两个哈希函数,m为哈希表长度。 先用第一个函数对关键码计算哈希地址,一旦地址冲突,再用第二个函数ReHash(key)确定移动的步长因子,最后,通过步长因子序列由探测函数寻找空的哈希地址。
2)链地址法(拉链法)
将所有关键字为同义词的记录存储在同一线性链表中,若选定的散列表长度为m,则可将散列表定义为一个由m个头指针组成的指针数组T[0……m-1]。凡是散列地址为i的结点,均可以插入到以T[i]为头指针的单链表中。T中各分量的初值均为空指针。装填因子可以大于1,通常取小于1.
链地址优点:
(1)处理冲突简单,且无堆积现像,平均查找长度短。
(2)由于链地址中链表上的结点空间是动态申请的,因而它更适合于造表前无法确定表长的情况。
(3)开放定址法为减少冲突,要求装填因子较小,因而在结点规模较大时会浪费很多空间;而链地址法装填因子可取大于1,且结点数较大时,链地址中增加的指针域可不计,因而节省空间。
(4)链地址法构造的散列表中,删除结点操作易于实现。
而对开放地址法构造的散列表,删除结点不能简单地将被删结点的空间置为空,否则将截断在它之后填人散列表的同义词结点的查找路径。这是因为各种开放地址法中,空地址单元(即开放地址)都是查找失败的条件。因此在用开放地址法处理冲突的散列表上执行删除操作,只能在被删结点上做删除标记,而不能真正删除结点。
链地址缺点:
指针需要额外的空间,故当结点规模小时,开放定地址法较为节省空间;而若将节省的指针空间用来扩大散列表的规模,可使装填因子变小,这又减少了开放定地址的冲突,从而提高了平均查找速度。
程序1:除法散列构造哈希函数;
开放定址,线性探测散列处理冲突。

//除法散列构造哈希函数;开放定址,线性探测散列处理冲突
#include <iostream>

using namespace std;

//哈希表结构体
typedef struct HashTable
{
int *elem;     //存储元素的数组
int count;     //哈希表中元素个数
int sizeindex; //哈希表容量
}HashTable;

///////////////////////////函数声明///////////////////////////////////////////////////
int HashFunc(int key);
int HashCollision(int k,int d=1);
bool SearchHash(HashTable hash,int key,int &p);
int InsertHash(HashTable &hash,int e);
void PrintHash(HashTable hash);
bool DeleteHash(HashTable &hash,int e);
/////////////////////////////////////////////////////////////////////////////////////

int main()
{
int b_delete=false;
bool b_search=false;
int search;
int hashsize;
int input;  // 接收输入
HashTable hash;
int m=0;     //用来判断哈希表是否已经满了
cout<<"请输入哈希表的容量: ";
cin>>hashsize;
int* p=new int[hashsize];
hash.elem =p;
hash.count =0;
hash.sizeindex =hashsize;
int i=0;
while(hash.elem[i]!=0&&i<hash.sizeindex )
{
hash.elem[i]=0;
++i;
}

while(1)
{   system("cls");
system("color 2e");     //设置屏幕前景和背景颜色
cout<<"             ******************************************"<<endl;
cout<<" ╱◥██◣  *1.插入\t2.查找\t3.显示\t4.删除\t5.退出*"<<endl;
cout<<"|田|田田│ ******************************************"<<endl;
cout<<"╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬<哈希表vipper.zhang2011.7.13>"<<endl;
cout<<"请输入您所需的操作功能:\t";
cin>>input;
cout<<endl;
switch(input)
{
case 1:
cout<<"请输入要插入的关键字,0结束输入:";
cin>>input;
cout<<endl;
while(input!=0)
{
m=InsertHash(hash,input);
if(m==2)
break;
cout<<"请输入要插入的关键字,0结束输入:";
cin>>input;cout<<endl;
}
PrintHash(hash);
system("pause");
break;
case 2:
cout<<"输入你想查找的关键字:";
cin>>input;cout<<endl;
b_search=SearchHash(hash,input,search);
if(b_search)
cout<<"关键字"<<input<<"存在"<<endl;
else
cout<<"关键字"<<input<<"不存在"<<endl;
system("pause");
break;
case 3:
PrintHash(hash);
system("pause");
break;
case 4:
cout<<"输入你要删除的关键字:";
cin>>input;cout<<endl;
b_delete=DeleteHash(hash,input);
if(b_delete)
cout<<"删除成功!"<<endl;

system("pause");
break;
case 5:
cout<<"再见。"<<endl;
break;
default:
cout<<"输入错误,请重新输入:"<<endl;
break;
}
if(input==5)
break;
}
system("pause");
return 0;
}

///////////////////////////////////////////////////////////////////////
// 哈希函数: H(key)=key%mod    mod=13
// Input:key - 关键字
// Output: 返回关键字所在哈希表的地址
///////////////////////////////////////////////////////////////////////
int HashFunc(int key)
{
return (abs(key)%13);
}

///////////////////////////////////////////////////////////////////////
// 哈希冲突函数 H(key)=(H(key)+d)%mod   mod=13
// Input: k - 由哈希函数求得的冲突值
//        d - 线性探测散列 处理冲突  d=1
// Output:返回新的关键字所在哈希表地址
///////////////////////////////////////////////////////////////////////
int HashCollision(int k,int d)
{
return (k+d)%13;
}

///////////////////////////////////////////////////////////////////////
//在开放定址哈希表中搜索关键字
// Input: hash - 待搜索的哈希表
//        key  - 要搜索的关键字
//        p    - 查找成功时,指示关键字在表中的位置,否则,指示插入位置
// Output: bool- 指示查找是否成功
///////////////////////////////////////////////////////////////////////
bool SearchHash(HashTable hash,int key,int &p)
{
p=HashFunc(key);
while(hash.elem [p]!=0&&hash.elem [p]!=key)
{
p=HashCollision(p);
}

if(hash.elem[p]==key)
return true;
else
return false;
}

///////////////////////////////////////////////////////////////////////
// 在哈希表中插入元素
// Input: hash - 哈希表
//        e    - 要插入的元素
// Output:bool - 返回是否插入成功
///////////////////////////////////////////////////////////////////////
int InsertHash(HashTable &hash,int e)
{
int p=0;
if(SearchHash(hash,e,p)){
cout<<"元素 "<<e<<"已经存在了"<<endl;
return 0;
}
else if(!SearchHash(hash,e,p)&&hash.count <hash.sizeindex)
{

hash.elem [p]=e;
hash.count ++;
return 1;
}
else
cout<<"哈希表已经满了"<<endl;
return 2;
return 0;
}

///////////////////////////////////////////////////////////////////////
//显示哈希表元素

// Input: hash - 哈希表
///////////////////////////////////////////////////////////////////////
void PrintHash(HashTable hash)
{
int i=0;
do{
if(hash.elem[i]!=0)
cout<<hash.elem[i]<<"\t";
++i;
}while(i<hash.sizeindex);
}

///////////////////////////////////////////////////////////////////////
// 删除哈希表中元素
// Input: hash - 哈希表
//        e    - 要删除的元素
// Output: 返回删除是否成功
///////////////////////////////////////////////////////////////////////
bool DeleteHash(HashTable &hash,int e)
{
if(hash.count==0)
{
cout<<"哈希表已经空了"<<endl;
return false;
}
int p=0;
if(!SearchHash(hash,e,p)){
cout<<"哈希表中没有这个元素"<<endl;
return false;
}
else{
hash.elem[p]=0;
hash.count--;
return true;
}

}
程序2:除法散列构造哈希函数;
链地址法解决冲突。
注:我的程序写的有问题,会有内存泄露的情况,没解决好,有人解决好这个问题请告诉我O(∩_∩)O
//除法散列构造哈希函数;链地址法处理冲突
#include <iostream>

using namespace std;
const int chainLength=13;
//哈希表结构体
typedef struct HashNode{
HashNode *next;  //后继指针
int key;         //结点的值
}HashNode;

///////////////////////////函数声明///////////////////////////////////////////////////
int HashFunc(int key);
void HashCollision(HashNode* &pNode,HashNode* newNode);
bool SearchHash(HashNode* *hNode,int key);
void InsertHash(HashNode* *hNode,int e);
void PrintHash(HashNode* *hNode,int len);
bool DeleteHash(HashNode* *hNode,int e);
////////////////////////////////////////////////////////////////////////////////////

int main()
{
int input;
bool b_search=false;
bool b_delete=false;

//创建哈希表
HashNode* hHash[chainLength];
for(int i=0;i<chainLength;i++)
{
HashNode* p=new HashNode;
if(!p){
cout<<"分配内存失误,退出";
cout<<endl;
}
p->key=0;
p->next=NULL;
hHash[i]=p;
}

while(1)
{   system("cls");
system("color 2e");     //设置屏幕前景和背景颜色
cout<<"             ******************************************"<<endl;
cout<<" ╱◥██◣  *1.插入\t2.查找\t3.显示\t4.删除\t5.退出*"<<endl;
cout<<"|田|田田│ ******************************************"<<endl;
cout<<"╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬<哈希表vipper.zhang2011.7.14>"<<endl;
cout<<"请输入您所需的操作功能:\t";
cin>>input;
cout<<endl;
switch(input)
{
case 1:
cout<<"请输入你要插入的关键字:";
cin>>input;cout<<endl;
while(input!=0)
{
InsertHash(hHash,input);
cout<<"请输入你要插入的关键字:";
cin>>input;cout<<endl;
}
PrintHash(hHash,chainLength);
system("pause");
break;
case 2:
cout<<"请输入你要查找的关键字";
cin>>input;cout<<endl;
b_search=SearchHash(hHash,input);
PrintHash(hHash,chainLength);cout<<endl;
if(b_search==true)
cout<<"你要找的关键字"<<input<<"在哈希表中";
else
cout<<"哈希表中没有关键字 "<<input;
cout<<endl;
system("pause");
break;
case 3:
PrintHash(hHash,chainLength);cout<<endl;
system("pause");
break;
case 4:
cout<<"输入你想删除的关键字:";
cin>>input;cout<<endl;
b_delete=DeleteHash(hHash,input);
if(b_delete)
cout<<"成功删除关键字 "<<input;
else
cout<<"哈希表中没有关键字 "<<input;
cout<<endl;
PrintHash(hHash,chainLength);
system("pause");
break;
case 5:
cout<<"再见。"<<endl;
break;
default:
cout<<"输入错误,请重新输入:"<<endl;
break;
}
if(input==5)
break;
}
system("pause");
return 0;
}

///////////////////////////////////////////////////////////////////////
// 哈希函数: H(key)=key%mod    mod=13
// Input: key - 关键字
// Output:返回新的关键字所在哈希表地址
///////////////////////////////////////////////////////////////////////
int HashFunc(int key)
{
return key%13;
}

///////////////////////////////////////////////////////////////////////
// 哈希冲突函数
// Input: pNode - 哈希表中指向特定关键字的指针
//        newNode -  要插入的新关键字结点
///////////////////////////////////////////////////////////////////////
void HashCollision(HashNode* &pNode,HashNode* newNode)
{

HashNode* p=pNode;
while(p->next !=NULL)
p=pNode->next ;

p->next =newNode;
p=newNode;
p->next =NULL;
}

///////////////////////////////////////////////////////////////////////
//在在链地址法的哈希表中搜索关键字
// Input:hNode - 待搜索的哈希表
//        key  - 要搜索的关键字
// Output: bool- 指示查找是否成功
///////////////////////////////////////////////////////////////////////
bool SearchHash(HashNode* *hNode ,int key)
{

int m=HashFunc(key);
HashNode* hhNode=hNode[m];
if(hhNode->key==key)
{
return true;
}
else if(hhNode->key==0&&hhNode->next ==NULL)
{
return false;
}
else if(hhNode->key !=0)
{
while(hhNode->next !=NULL)
{
if(hhNode->key ==key){
return true;
}
hhNode=hhNode->next ;
}
return false;
}
return false;

}

///////////////////////////////////////////////////////////////////////
// 在哈希表中插入元素
// Input: hNode - 哈希表
//        e    - 要插入的元素
///////////////////////////////////////////////////////////////////////
void InsertHash(HashNode* *hNode,int e)
{
int m=HashFunc(e);
bool search=false;
if(SearchHash(hNode,e))
return;
else
{
HashNode* p=new HashNode;
p->key=e;
p->next =NULL;
if(hNode[m]->key==0)
{
hNode[m]->key=e;
hNode[m]->next=NULL;
}
else
HashCollision(hNode[m],p);
}

}

///////////////////////////////////////////////////////////////////////
//显示哈希表元素
// Input: hNode - 哈希表
///////////////////////////////////////////////////////////////////////
void PrintHash(HashNode* *hNode,int len)
{
int i=0;
HashNode* p;
for(;i< len;i++)
{
p=hNode[i];

while(p)
{
cout<<p->key<<"\t";
p=p->next;
}
cout<<endl;
}
}

///////////////////////////////////////////////////////////////////////
// 删除哈希表中元素
// Input: hNode - 哈希表
//        e     - 要删除的元素
// Output: 返回删除是否成功
///////////////////////////////////////////////////////////////////////
bool DeleteHash(HashNode* *hNode,int e)
{
int m=HashFunc(e);
HashNode* p=hNode[m];
if(!SearchHash(hNode,e))
return false;
else
{
while(p)
{
if(p->key==e)
{
p->key=-1; //将要删除的元素设置为-1
return true;
}
p=p->next;
}

}
return true;

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: