16 散列表-基于除余留数法与线性探测法的实现
散列表是实现字典操作的一种有效的数据结构。
在最坏情况下查找一个元素的时间与链表中查找的时间相同,达到了O(n)。
而理想的情况下,散列表查找一个元素的平均时间达到了O(1)。
下面就散列表的原理做一个简单的描述。
用最简单的数字x来做一个描述,就是一个数字在计算机中存放的位置坐标y与这个数字x有直接关系:通过一个特定的散列函数f(x)得出一个值y,而这个y就是x在计算机中存放的位置下标。
将这个数字广义化成各种字符各种数据:散列表的存储原理是对应数据的主码,通过一个特定的散列函数使得该数据的值与该数据最后在计算机中存储的坐标有一个直接的映射关系。
这样,当我们要查找这个数据存储的位置,只需在一个常量时间下实现函数的计算,就可以计算出元素最终在计算机存储的坐标。
这里实现散列表(哈希表)用的是除余留数法:通过取k除以m的余数,将关键字k映射到m个槽中的某一个字上,散列函数为:h(k)=k mod m
比如:如果散列表的大小为12,所给关键字为100,则h(k)=4
即元素100--存到-->4号槽上。
而散列表需要解决的首要问题是冲突。
冲突:两个关键字可能会被映射到相同的槽中。
最理想的办法当然是设计一个完美的散列函数使得数据可以避免所有的冲突。但是想要完全解决冲突,是几乎不可能的。
常用的解决冲突的方法有链接法和开放寻址法。
在这里使用开放寻址法中的线性探查法来解决这个冲突问题。
其主要解决思想为:先把要查找或者插入的元素通过对应的哈希函数计算出槽值。若槽值中已有元素,则按顺序往后遍历整个散列表(增量为1),直到找到合适的位置为止。
散列表(hash表)的具体实现
主要功能函数有如下几个:
插入函数(insertdata)
对于插入元素时有两种情况:
1、映射到的槽中未曾存放元素,或者原有元素已经被删除。此时可以直接插入数据,并将搜索次数置为1
2、映射到的槽中已经存放了元素,此时使用线性探测法寻找下一个元素的位置,继续记录发生冲突的次数,直到找到空余的槽为止。
1 void insertdata(hashtable h,int &hashlength,int insertdata,int p,int m) 2 { 3 int i,index; 4 index=insertdata%p;//通过除余留数法计算槽的位置 5 if(h[index].key==null||h[index].key==deleted)//哈希表为空的情况:原来为空或原有元素被删除 6 { 7 h[index].key=insertdata; 8 h[index].number=1; 9 } 10 else 11 { 12 i=1;//i记录发生冲突的次数 13 do 14 { 15 index=(index+1)%m; 16 i++; 17 } 18 while(h[index].key!=null&&h[index].key!=deleted); 19 20 h[index].key=insertdata; 21 h[index].number=i; 22 } 23 hashlength++; 24 }
建表函数(create_hash)
对于传入的一组元素调用插入函数(insertdata)实现哈希表的建立操作。
1 void create_hash(hashtable h,int arr[],int &length,int m,int p,int datanum)//创建一个哈希表 2 { 3 int hashlength=0; 4 for(int i=0; i<m; i++) 5 { 6 h[i].key=null; 7 h[i].number=0; 8 } 9 for(int i=0; i<datanum; i++)//调用insert data函数把元素插入hash表 10 { 11 insertdata(h,hashlength,arr[i],p,m); 12 } 13 length=hashlength; 14 }
查找函数(search_hash)
只要碰到一个空槽的时候,查找算法就停止。
因为如果k在表中,它就一定在这个位置,不会在探查序列随后的位置上。
1 int search_hash(hashtable h,int p,int k)//在哈希表中寻找关键字k 2 { 3 int index=k%p; 4 while(h[index].key!=null&&h[index].key!=k) 5 { 6 index=(index+1)%p;//使用线性查找法查找下一个地址 7 } 8 if(h[index].key==k)//查找成功 9 return index; 10 else return -1; 11 }
删除函数(delete_hash):
对要删除的元素先调用查找函数(search_hash)查找元素所在位置。
若查找到,将删除标记把元素替换掉。
1 int delete_hash(hashtable h,int p,int k,int &hashlength) 2 { 3 int index; 4 index=search_hash(h,p,k); 5 if(index!=-1) 6 { 7 h[index].key=deleted; 8 hashlength--; 9 return 1; 10 } 11 else return 0; 12 }
其他具体细节描述详见测试代码如下:
1 #include<cstdio> 2 #include<iostream> 3 using namespace std; 4 #define maxsize 100 5 #define null -1 6 #define deleted -2 7 typedef struct //哈希表结点的声明 8 { 9 int key;//存放结点元素 10 int number;//记录查找次数 11 } hashNode; 12 typedef hashNode hashtable[maxsize];//定义一个哈希表 13 /*插入元素到哈希表中*/ 14 void insertdata(hashtable h,int &hashlength,int insertdata,int p,int m) 15 { 16 int i,index; 17 index=insertdata%p;//通过除余留数法计算槽的位置 18 if(h[index].key==null||h[index].key==deleted)//哈希表为空的情况:原来为空或原有元素被删除 19 { 20 h[index].key=insertdata; 21 h[index].number=1; 22 } 23 else 24 { 25 i=1;//i记录发生冲突的次数 26 do 27 { 28 index=(index+1)%m; 29 i++; 30 } 31 while(h[index].key!=null&&h[index].key!=deleted); 32 33 h[index].key=insertdata; 34 h[index].number=i; 35 } 36 hashlength++; 37 } 38 void create_hash(hashtable h,int arr[],int &length,int m,int p,int datanum)//创建一个哈希表 39 { 40 int hashlength=0; 41 for(int i=0; i<m; i++) 42 { 43 h[i].key=null; 44 h[i].number=0; 45 } 46 for(int i=0; i<datanum; i++)//调用insert data函数把元素插入hash表 47 { 48 insertdata(h,hashlength,arr[i],p,m); 49 } 50 length=hashlength; 51 } 52 int search_hash(hashtable h,int p,int k)//在哈希表中寻找关键字k 53 { 54 int index=k%p; 55 while(h[index].key!=null&&h[index].key!=k) 56 { 57 index=(index+1)%p;//使用线性查找法查找下一个地址 58 } 59 if(h[index].key==k)//查找成功 60 return index; 61 else return -1; 62 } 63 int delete_hash(hashtable h,int p,int k,int &hashlength) 64 { 65 int index; 66 index=search_hash(h,p,k); 67 if(index!=-1) 68 { 69 h[index].key=deleted; 70 hashlength--; 71 return 1; 72 } 73 else return 0; 74 } 75 void show_hash(hashtable h,int hashlength,int m) 76 { 77 float avg=0; 78 int i; 79 printf("hash index :"); 80 for(int i=0; i<m; i++)//输出哈希表中元素 81 if(i<10) cout<<i<<" "; 82 else cout<<i<<" "; 83 cout<<endl; 84 cout<<"key of hash :"; 85 for(i=0; i<m; i++) 86 if(h[i].key==null||h[i].key==deleted) 87 cout<<"none "; 88 else cout<<h[i].key<<" "; 89 cout<<endl; 90 cout<<"the number of search:";//输出搜索次数 91 for(i=0; i<m; i++) 92 if(h[i].key==null||h[i].key==deleted) 93 cout<<"- "; 94 else cout<<h[i].number<<" "; 95 cout<<endl; 96 for(int i=0; i<m; i++) 97 if(h[i].key!=null&&h[i].key!=deleted) 98 avg=avg+h[i].number; 99 100 cout<<"the length of hash is:"<<hashlength<<"."<<endl; 101 avg=avg/hashlength;//计算哈希表的平均搜索长度 102 cout<<"arrange search length of the hash is :"<<avg<<" 。"; 103 } 104 int main() 105 { 106 int arr[]={12,38,74,21,50,49,37,20,84,86}; 107 int datanum=10; 108 int length=0,m=13,p=13,i,key=21;//m为表长度,p为所选的作为除余留数法的质数。 109 hashtable A; 110 create_hash(A,arr,length,m,p,datanum); 111 show_hash(A,length,m); 112 cout<<endl; 113 114 cout<<"test:searching function"<<endl; 115 int index=search_hash(A,p,key); 116 if (index!=-1)cout<<"the data "<<key<<" is in the slot No."<<index<<"."<<endl; 117 else cout<<"failed to find the data "<<key<<"."<<endl; 118 cout<<endl; 119 cout<<"test:delete a element."<<endl; 120 delete_hash(A,p,key,length); 121 show_hash(A,length,m); 122 cout<<endl; 123 key=22; 124 cout<<"test:insert a element."<<endl<<endl; 125 insertdata(A,length,key,p,m); 126 show_hash(A,length,m); 127 return 0; 128 }
测试截图: