数据结构——全面学习哈希

数据结构——全面学习哈希

2015年08月18日 11:42:09

阅读数:1125

    相信学计算机的童鞋对于“哈希”这个词会很熟悉,但是能明明白白的说清楚,并且用程序来描述的人还是比较少的。这里,我们就全面学习这个重要的数据结构,以及它的思想和应用。

    首先,我们来学习一下几个基本概念。

    哈希(hash)

    是一种数据编码方式。将大尺寸的数据(如一句话,一张图片,一段音乐、一个视频等)浓缩到一个数字中,从而方便地实现数据匹配和查找的功能。

    哈希表(hash table)

    一种数据表结构。hash表,有时候也被称为散列表。个人认为,hash表是介于链表和二叉树之间的一种中间结构。链表使用十分方便,但是数据查找十分麻烦;二叉树中的数据严格有序,但是这是以多一个指针作为代价的结果。hash表既满足了数据的查找方便,同时不占用太多的内容空间,使用也十分方便。

    打个比方来说,所有的数据就好像许许多多的书本。如果这些书本是一本一本堆起来的,就好像链表或者线性表一样,整个数据会显得非常的无序和凌乱,在你找到自己需要的书之前,你要经历许多的查询过程;而如果你对所有的书本进行编号,并且把这些书本按次序进行排列的话,那么如果你要寻找的书本编号是n,那么经过二分查找,你很快就会找到自己需要的书本;但是如果你每一个种类的书本都不是很多,那么你就可以对这些书本进行归类,哪些是文学类,哪些是艺术类,哪些是工科的,哪些是理科的,你只要对这些书本进行简单的归类,那么寻找一本书也会变得非常简单,比如说如果你要找的书是计算机方面的书,那么你就会到工科一类当中去寻找,这样查找起来也会显得麻烦。

    映像

    由哈希函数得到的哈希表是一个映像。

    冲突

    如果两个关键字的哈希函数值相等,这种现象称为冲突。

    哈希算法

    并不是一个特定的算法而是一类算法的统称。 哈希算法也叫散列算法,一般来说满足这样的关系:f(data)=key,输入任意长度的data数据,经过哈希算法处理后输出一个定长的数据key。同时这个过程是不可逆的,无法由key逆推出data。

    处理冲突的几个方法(后面的程序会详解)

  1.     开放地址法:用开放地址处理冲突就是当冲突发生时,形成一个地址序列,沿着这个序列逐个深测,直到找到一个“空”的开放地址,将发生冲突的关键字值存放到该地址中去。 例如:hash(i)=(hash(key)+d(i)) MOD m (i=1,2,3,......,k(k<m-1)) d为增量函数,d(i)=d1,d2,d3,...,dn-1 根据增量序列的取法不同,可以得到不同的开放地址处理冲突探测方法。 有线性探测法、二次方探测法、伪随机探测法。
  2.     链地址法:把所有关键字为同义词的记录存储在一个线性链表中,这个链表成为同义词链表,即把具有相同哈希地址的关键字值存放在同义链表中。
  3.     二次哈希表:费时间的一种方法 如果是一个data数据集,经过哈希算法处理后得到key的数据集,然后将keys与原始数据进行一一映射就得到了一个哈希表。一般来说哈希表M符合M[key]=data这种形式。 哈希表的好处是当原始数据较大时,我们可以用哈希算法处理得到定长的哈希值key,那么这个key相对原始数据要小得多。我们就可以用这个较小的数据集来做索引,达到快速查找的目的。

    稍微想一下就可以发现,既然输入数据不定长,而输出的哈希值却是固定长度的,这意味着哈希值是一个有限集合,而输入数据则可以是无穷多个。那么建立一对一关系明显是不现实的。所以"碰撞"(不同的输入数据对应了相同的哈希值)是必然会发生的,所以一个成熟的哈希算法会有较好的抗冲突性。同时在实现哈希表的结构时也要考虑到哈希冲突的问题。

    密码上常用的MD5,SHA都是哈希算法,因为key的长度(相对大家的密码来说)较大所以碰撞空间较大,有比较好的抗碰撞性,所以常常用作密码校验。

    下面,我们通过一个例子,来自己实现一个完整的哈希表

    问题描述:针对某个集体(比如你所在的班级)中的“人名”设计一个哈希表,使得平均查找长度不超过R,完成相应的建表和查表程序。

    思路:假设人名为中国人姓名的汉语拼音形式。待填入哈希表的人名共有30个,取平均查找长度的上限为2。哈希函数用除留余数法构造,用伪随机探测再散列法处理冲突。

    代码如下 :

 
  1. #include <stdio.h>

  2. #include <stdlib.h>

  3. #include <string>

  4. #include <iostream>

  5. #ifndef _HashTest_H_

  6. #define _HashTest_H_

  7. #define NAME_NO 30

  8. #define HASH_LENGTH 50

  9. #define M 50;

  10.  
  11. using namespace std;

  12.  
  13. typedef struct

  14. {

  15. char *py; //名字的拼音

  16. int k; //拼音所对应的整数

  17. }NAME;

  18.  
  19. typedef struct //哈希表

  20. {

  21. char *py; //名字的拼音

  22. int k; //拼音所对应的整数

  23. int si; //查找长度

  24. }HASH;

  25.  
  26. #endif

  27.  
  28. HASH HashList[HASH_LENGTH],NameList[HASH_LENGTH];

  29.  
  30.  
  31. void InitNameList()

  32. {

  33. char *f;

  34. int r,s0,i;

  35. NameList[0].py="zhoujielun";

  36. NameList[1].py="naying";

  37. NameList[2].py="halin";

  38. NameList[3].py="wangfeng";

  39. NameList[4].py="fenghanzao";

  40. NameList[5].py="fuzongkai";

  41. NameList[6].py="hujingbin";

  42. NameList[7].py="huangjianwu";

  43. NameList[8].py="lailaifa";

  44. NameList[9].py="lijiahao";

  45. NameList[10].py="liangxiaocong";

  46. NameList[11].py="linchunhua";

  47. NameList[12].py="liujianhui";

  48. NameList[13].py="luzhijian";

  49. NameList[14].py="luonan";

  50. NameList[15].py="quegaoxiang";

  51. NameList[16].py="sugan";

  52. NameList[17].py="suzhiqiang";

  53. NameList[18].py="taojiayang";

  54. NameList[19].py="wujiawen";

  55. NameList[20].py="xiaozhuomin";

  56. NameList[21].py="xujinfeng";

  57. NameList[22].py="yanghaichun";

  58. NameList[23].py="yeweixiong";

  59. NameList[24].py="zengwei";

  60. NameList[25].py="zhengyongbin";

  61. NameList[26].py="zhongminghua";

  62. NameList[27].py="chenliyan";

  63. NameList[28].py="liuxiaohui";

  64. NameList[29].py="panjinmei";

  65. for(i=0;i<NAME_NO;i++)

  66. {

  67. s0=0;

  68. f=NameList[i].py;

  69. for(r=0;*(f+r)!='\0';r++)/*将字符串的各个字符所对应的ASCII码相加,所得的整数做为哈希表的关键字*/

  70. s0=*(f+r)+s0;

  71. NameList[i].k=s0;

  72. }

  73. }

  74.  
  75. void CreateHashList()

  76. {

  77. int i;

  78. for(i=0; i<HASH_LENGTH;i++)

  79. {

  80. HashList[i].py="";

  81. HashList[i].k=0;

  82. HashList[i].si=0;

  83. }

  84. for(i=0;i<NAME_NO;i++)

  85. {

  86. int sum=0;

  87. int adr=(NameList[i].k)%M;//哈希函数

  88. int d=adr;

  89. if(HashList[adr].si==0)//如果不冲突

  90. {

  91. HashList[adr].k=NameList[i].k;

  92. HashList[adr].py=NameList[i].py;

  93. HashList[adr].si=1;

  94. }

  95. else//冲突

  96. {

  97. do

  98. {

  99. d=(d+NameList[i].k%10+1)%M;//伪随机探测再散列法处理冲突

  100. sum=sum+1; //查找次数加1

  101. }while (HashList[d].k!=0); //找到一个空位置

  102. HashList[d].k=NameList[i].k;

  103. HashList[d].py=NameList[i].py;

  104. HashList[d].si=sum+1;

  105. }

  106. }

  107. }

  108.  
  109.  
  110.  
  111. void FindList() //查找

  112. {

  113. char name[20]={0};

  114. int s0=0,r,sum=1,adr,d;

  115. printf("input name:");

  116. scanf("%s",name);

  117. for(r=0;r<20;r++) //求出姓名的拼音所对应的整数(关键字)

  118. s0+=name[r];

  119. adr=s0%M; //使用哈希函数

  120. d=adr;

  121. if(HashList[adr].k==s0) //分3种情况进行判断

  122. printf("\nname:%s key:%d find times: 1",HashList[d].py,s0);

  123. else if (HashList[adr].k==0)

  124. printf("no records!");

  125. else

  126. {

  127. int g=0;

  128. do

  129. {

  130. d=(d+s0%10+1)%M; //伪随机探测再散列法处理冲突

  131. sum=sum+1;

  132. if(HashList[d].k==0)

  133. {

  134. printf("no records! ");

  135. g=1;

  136. }

  137. if(HashList[d].k==s0)

  138. {

  139. printf("\nname:%s key:%d find times:%d",HashList[d].py,s0,sum);

  140. g=1;

  141. }

  142. }while(g==0);

  143. }

  144. }

  145.  
  146. void Display()

  147. {

  148. int i;

  149. float average=0;

  150. printf("\naddress\tkey\t\ttimes\tH(key)\t name\n"); //显示的格式

  151. for(i=0; i<50; i++)

  152. {

  153. printf("%d ",i);

  154. printf("\t%d ",HashList[i].k);

  155. printf("\t\t%d ",HashList[i].si);

  156. printf("\t\t%d ",HashList[i].k%30);

  157. printf("\t %s ",HashList[i].py);

  158. printf("\n");

  159. }

  160. for(i=0;i<HASH_LENGTH;i++)

  161. average+=HashList[i].si;

  162. average/=NAME_NO;

  163. }

  164.  
  165. int main()

  166. {

  167. char ch1;

  168. InitNameList();

  169. CreateHashList ();

  170. do

  171. {

  172. printf("D. show hashTable\nF. find\nQ. quit\nplease choose");

  173. cin>>&ch1;

  174. switch(ch1)

  175. {

  176. case 'D':Display(); cout<<endl;break;

  177. case 'F':FindList(); cout<<endl;break;

  178. case 'Q':exit(0);

  179. }

  180. cout<<"come on !(y/n):";

  181. cin>>&ch1;

  182. }while(ch1!='n');

  183. return 0;

  184. }

    我们执行这段程序,会出现如下结果:

    

    根据输出我们可以看到四个值:

  •     地址
  •     关键字
  •     搜索长度 H(key)
  •     姓名

    关键字就是拼音的ascll码之和。然后通过哈希函数形成了一个值,我们叫做value这段程序是用50取余数之后得到的,所以key值在0~50之间。这个value值决定了数据存放的位置,所以能看到value值跟地址很多是一样的。这里是如何解决“冲突”的呢。如果有两个元素的经过哈希函数之后的结果是一样的呢?这里定义了一个si的值,意思为搜索次数,如果没有冲突意思是一次性找到了位置。

    我们来看这一段函数 

 
  1. else //冲突,d的初始值为adr,adr意思是address,也是value值赋予的

  2. {

  3. do

  4. {

  5. d=(d+NameList[i].k%10+1)%M;

  6. sum=sum+1;

  7. }while(HashList[d].k!=0); //找到空位

  8. HashList[d].k=NameList[i].k; //放入元素

  9. HashList[d].py=NameList[i].py;

  10. HashList[d].si=sum+1;

  11. }

    其中 

d=(d+NameList[i].k%10+1)%M

    叫做伪随机探测再散列法处理冲突,就是将散列值(value),d,重新散列一次重新获得一次d的值找到一个空的位置,把元素塞进去。

    再看一看key是如何得到的,也就是姓名的ascll码之和是如何得到的。  

 
  1. for(i=0;i<NAME_NO;i++)

  2. {

  3. S0=0;

  4. f=NameList[i].py;

  5. for(r=0;*(f+r)!='\0';r++)

  6. s0=*(f+r)+s0;

  7. NameList[i].k=s0;

  8. }

    这里注意一下,f是字符类型,r是int型,有一个类型转换在这里。

猜你喜欢

转载自blog.csdn.net/weixin_42246997/article/details/81396647