当前位置:   article > 正文

C语言哈希表

c语言哈希表

目录

1.概念

2.哈希表的构造方法

2.1直接定址法

2.2除留余数法

2.3折叠法

2.4平方取中法

3.处理冲突的方法

3.1链地址法

3.2开放定址法

3.2.1线性探测法

3.2.2二次探测法

4.哈希表的实现

4.1链地址哈希表的实现

4.2开放定址哈希表的实现


1.概念

哈希表:关键字与存储位置有函数关系的表。只要给出关键字的值,我们就可以计算出该关键字在哈希表中的存储位置,方便查找。

冲突:两个或多个不同的关键字根据函数关系计算得到的存储位置一致。关键字个数大于存储位置的个数,就不可避免产生冲突,所以我们需要寻找处理冲突的方法。

同义词:产生冲突的关键字互称同义词。

2.哈希表的构造方法

随机关键字在哈希表上分布的越均匀,发生冲突的概率也越低,这是构造哈希表要遵循的原则,还有计算的函数要尽量简单,不然会拖慢运行速度。

设关键字为key,H(key)为存储位置

2.1直接定址法

直接定址法适用于连续的关键字,它的构造函数是线性的,H(key)=a*key+b

a为缩放系数,b为平移系数

如关键字9到18,取a=1,b=-9,存储位置为0到9.

2.2除留余数法

H(key)=key%p

p取不大于m且最接近m的素数(或不包含小于20的质因子的合数?)

m为哈希表地址区间长度。

2.3折叠法

折叠法就是将关键字分割成位数相同的若干部分,然后叠加求和(舍弃进位),得到的值就是存储位置。

叠加求和又分为移位叠加和Z形叠加。

举个栗子,关键字31 4159 2689,哈希表m=10000,故按4位分割,比m少一位,这样得到的地址是4位的。

移位叠加就是0031+4159+2689,Z形叠加就是0031+9514+2689

移位叠加实现如下

  1. //移位叠加
  2. int hash_m(long key){
  3. int i,j=1,sum=0;
  4. for(i=0;i<4;i++)j*=10; //按4位分割,可改成其他位数
  5. while(key!=0){
  6. i=key%j;
  7. sum+=i;
  8. if(sum>=j)sum%=j; //舍弃进位
  9. key/=j;
  10. }
  11. return sum;
  12. }

2.4平方取中法

平方取中法先取关键字的平方,然后根据哈希表地址区间长度m来取平方数中间若干位作为存储位置。如m=1000,则取平方数中间3位作为存储位置,比m少一位。

取关键字平方值的万千百三位的哈希函数如下

  1. int hash_3(int key){
  2. long temp;
  3. temp=key*key/100; //除去个十位
  4. if(temp>=1000) //如果平方数大于或等于5位数
  5. temp-=temp/1000*1000;
  6. return temp;
  7. }

3.处理冲突的方法

3.1链地址法

链地址法将关键字为同义词的记录链接在同一个单链表中。

3.2开放定址法

开放定址法是当发生冲突时,通过某种探测技术在哈希表计算得到另一个地址,然后插入,如果又产生冲突,则继续寻找下一个地址,直到能成功插入为止。

查找和插入的过程一样,先查找初始位置,没找到则按探测技术寻找下一个地址,没找到则继续下一个地址,直到探测到空闲地址,若仍找不到,则说明表中无要查的关键字。

而常用的探测方法有线性探测法和二次探测法两种。

3.2.1线性探测法

算法思路:把哈希表看成一个循环空间,哈希表地址区间长度为m,先根据哈希函数求出关键字key的初始存储位置,若产生冲突,则寻找下一个地址(H(key)+1)%m,若仍然冲突,则再寻找下一个地址(H(key)+2)%m,即按线性寻找下一个地址,重复操作,直到地址(H(key)+m-1)%m,再往下寻找就会回到初始位置。适用于关键字数量比地址区间长度小的情况。

3.2.2二次探测法

算法思路:同样先根据哈希函数求出关键字key的初始存储位置,若产生冲突,则寻找下一个地址(H(key)+1*1)%m,还是冲突,下一个寻找地址为(H(key)-1*1)%m,然后接着是(H(key)+2*2)%m,……,(H(key)+(m-1)*(m-1))%m,(H(key)-(m-1)*(m-1))%m。在H(key)两边跳跃寻找,故叫二次探测。

4.哈希表的实现

4.1链地址哈希表的实现

  1. typedef int KeyType; //定义KeyType为int型,方便修改
  2. typedef struct{
  3. KeyType key; //关键字
  4. ……
  5. }RcdType;
  6. typedef struct Node{
  7. RcdType r; //数据域
  8. struct Node *next; //指针域
  9. }Node; //结点
  10. typedef struct{
  11. Node **rcd; //rcd[size]内存放的是指针
  12. int size; //哈希表容量
  13. int count; //当前表内记录的个数
  14. int (*hash)(KeyType key,int hashSize); //函数指针变量,指针hash指向哈希函数,hashSize表示
  15. }HashTable; //哈希表地址区间长度
  16. //链地址哈希表的初始化
  17. Status InitHash(HashTable &H,int size,int (*hash)(KeyType,int)){
  18. int i;
  19. H.rcd=(Node**)malloc(size*sizeof(Node*)); //分配长度为size的空间,元素类型为指针Node*
  20. for(i=0;i<size;i++)H.rcd[i]=NULL; //置空
  21. H.size=size;
  22. H.hash=hash;
  23. H.count=0;
  24. return OK;
  25. }
  26. //链地址哈希表的查找
  27. int hash(KeyType key,int hashSize){ //至于为什么是hash,我也有点乱
  28. return (3*key)%hashSize;
  29. }
  30. Node* SearchHash(HashTable &H,int key){ //查找关键字为key的记录
  31. int p=H.hash(key,H.size); //key对应的初始存储位置
  32. Node *np;
  33. for(np=H.rcd[p];np!=NULL;np=np->next)
  34. if(key==np->r.key)return np;
  35. return NULL;
  36. }
  37. //链地址哈希表的插入(先查找再插入)
  38. Status InsertHash(HashTable &H,RcdType e){
  39. int p;
  40. Node *np;
  41. if((np=SearchHash(H,e.key))==NULL){ //若没找到,则np=NULL
  42. p=H.hash(e.key,H.size);
  43. np=(Node*)malloc(sizeof(Node)); //创建结点
  44. if(NULL==np)return OVERFLOW;
  45. np->r=e;
  46. np->next=H.rcd[p]; //插入
  47. H.rcd[p]=np; //头指针移动
  48. H.count++;
  49. return OK;
  50. }
  51. return ERROR;
  52. }
  53. //哈希表的销毁
  54. Status DestroyHash(HashTable &H){
  55. int i;
  56. Node *p,*q;
  57. for(i=0;i<H.size;i++){
  58. p=H.rcd[i];
  59. while(p!=NULL){
  60. q=p;
  61. p=p->next;
  62. free(q); //释放内存
  63. q=NULL; //指针置空
  64. }
  65. }
  66. free(H);
  67. H=NULL;
  68. return OK;
  69. }

4.2开放定址哈希表的实现

  1. typedef char KeyType;
  2. typedef struct{
  3. KeyType Key;
  4. ……
  5. }RcdType;
  6. typedef struct{
  7. RcdType *rcd;
  8. int size; //哈希表容量
  9. int count; //当前表内元素个数
  10. int *tag; //tag[]=0为空,1为有效,-1为已删除
  11. int (*hash)(KeyType key,int hashSize); //函数指针变量,指针指向哈希函数
  12. void (*collision)(int &hashValue,int hashSize); //处理冲突的函数
  13. }HashTable;
  14. //之所以tag[]=-1表示已删除,是因为如果删除一个记录后用tag[]=0表示的话,那么如果哈希表内
  15. //确定有关键字key,但不在初始位置hash(key,H.size)上,如果删除key前面一个记录,tag[]=0;
  16. //查找到前面那个结点时,认为有空闲位置,故查找结束,判断没有key这个关键字,就会导致出错。
  17. //线性探测法处理冲突
  18. void collision(int &hashValue,int hashSize){
  19. hashValue=(hashValue+1)%hashSize;
  20. }
  21. //开放定址哈希表的初始化
  22. Status InitHash(HashTable &H,int size,int *tag,int (*hash)(KeyType,int),void (*collision)(int &,int)){
  23. int i;
  24. H.rcd=(RcdType*)malloc(size*sizeof(RcdType));
  25. H.tag=(int*)malloc(size*sizeof(int));
  26. if(NULL==H.rcd||NULL==H.tag)return OVERFLOW;
  27. for(i=0;i<H.size;i++)H.tag[i]=0; //标记置空
  28. H.size=size;
  29. H.count=0;
  30. H.hash=hash;
  31. H.collision=collision;
  32. return OK;
  33. }
  34. //开放定址哈希表的查找
  35. Status SearchHash(HashTable H,KeyType key,int &p,int &c){
  36. //若查找成功,p表示查找记录在表中的位置,查找失败则为可插入位置
  37. //c表示发生冲突的次数,当达到一定阈值时,需要重新构造哈希表
  38. p=H.hash(key,H.size);
  39. while(H.tag[p]!=0){
  40. if(1==H.tag[p]&&H.rcd[p].key==key)return SUCCESS; //1==H.tag[p]不可省,因为删除并未置空
  41. collision(p,H.size);
  42. c++;
  43. }
  44. return UNSUCCESS;
  45. }
  46. //开放定址哈希表的插入
  47. //存疑,尽管是教科书上的代码,感觉应该tag[]=-1的内存也能用来插入新记录
  48. //开放定址法哈希表网上代码不同的定义太繁杂,只能存疑标一下,没找到能参考的同类定义
  49. Status InsertHash(HashTable &H,RcdType e){
  50. int j,c=0;
  51. if(SUCCESS==SearchHash(H,e.key,j,c))return -1;
  52. else{
  53. H.rcd[j]=e;tag[j]=1;count++;
  54. }
  55. return c;
  56. }
  57. //开放定址哈希表的删除
  58. Status DeleteHash(HashTable &H,RcdType e){
  59. int j,c=0;
  60. if(SearchHash(H,e.key,j,c)!=SUCCESS)return UNSUCCESS;
  61. else{
  62. e=H.rcd[j];H.tag[j]=-1;H.count--; //感觉要将H.rcd[j]置空
  63. }
  64. return SUCCESS;
  65. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/449849
推荐阅读
相关标签
  

闽ICP备14008679号