1、序

该篇分别讲了散列表的引出、散列函数的设计、处理冲突的方法。并给出一段简单的示例代码。

2、散列表的引出

给定一个关键字集合U={0,1......m-1},总共有不大于m个元素。如果m不是很大,我们可以定义一个数组T[0...(m-1)],把U映射到数组T上,每个位置对应U中的一个关键字,若U中没有关键字为k的元素,则T[k]=NULL。我们称T为直接寻址表,不管是插入、删除、查找,只需o(1)的时间。但是注意前提,当”m不是很大的时候“。显然这个前提限制性很大,m很大时,必然会浪费很多空间,那该怎么办呢?于是就有了散列表:给定n个元素、m个存放位置(也称槽位),通过散列函数把关键字和存储位置关联起来,使每一个关键字与结构中一个唯一的存储位置相对应。于是在查找时,根据散列函数查找关键字的位置,不需要比较就可以取得要查找的记录。散列表也称哈希表。

3、散列函数

散列函数有很多,好的散列函数特点是:(近似的)满足简单一致散列,即对于关键字集合U中的任何一个关键字,经散列函数映射到地址集合中任何一个地址的概率是相等的,此时可以称为均匀散列函数,也就是说使关键字经过散列函数得到一个“随机的地址”,以便使一组关键字的散列地址均匀分布在整个地址区间,减少冲突。很多时候我们将关键字解释为自然数。下面给出几种常用的散列函数。

(1) 除法散列法

散列函数:h(key)=key%p,其中p的取值很重要,这个函数得出的散列地址值不会超过p,同时可以选作p的值常常是与2的整数幂不太接近的质数。当存放位置m较大时,p不宜过小。

(2)乘法散列法

散列函数h(key)=[p*(key*A-(int)key*A)].其中0<A<1.(key*A-(int)key*A)是取key*A得小数部分,最后再乘上常数p,最后的值向下取整。一般p选择2的某个幂次,对p的选择并没有什么特别的要求。

(3)全域散列

在执行开始时,从一族仔细设计的函数中,随机地选择一个作为散列函数。这里的随机选择针对的是一次对散列表的应用,而不是一次简单的插入或查找操作。散列函数的确定性,是查找操作正确执行的保证。全域散列法确保,当key1 != key2时,两者发生碰撞的概率不大于1/m。设计一个全域散列函数类的方法如下,该方法中,散列表大小m的大小是任意的。

全域散列函数类类设计方法:选择一个足够大的质数p,使得每一个可能的关键字都落在0到p-1的范围内。设Zp表示集合{0, 1, …, p-1},Zp*表示集合{1, 2, …, p-1}。对于任何a∈Zp*和任何b∈Zp,定义散列函数ha,b (k)= ((ak+b) mod p) mod
m所有这样的散列函数构成的函数族为:Hp,m = {ha,b : a∈Zp*和b∈Zp}由于对a来说有p-1种选择,对于b来说有p种选择,因而,Hp,m中共有p(p-1)个散列函数。在一次散列表应用中,a、b是随机生成的在一定范围的数。举个例子:若p=17,m=6,此次散列应用中随机生成a=3,b=4.则h3,4(8)=5.

4、处理冲突的方法

当h(key1)==h(key2)时,两个不同的关键字对应得哈希地址相同,于是就产生了冲突,好的哈希函数只能避免冲突,不能完全消除冲突。那么该怎么样处理冲突呢?下面给出几种常用的方法。

(1)开放寻址法

在开发寻址法法中,所有的元素都存放在散列表里。插入一个元素时,可以连续地检查散列表的各项,直到找到一个空槽来放置待插入的关键字为止。对开发地址法来说,要求对每一个关键字k,探查序列必须是(0,1...m-1)的一个排列,即能够探测所有的槽。

H=(H(key)+d) mod m 其中m表示哈希表长度,d为增量序列,H(key)为哈希函数

线性探测再散列:当上式中d取值依次为1,2,3...m-1时,称为线性探测再散列。这种方法中,初始探查的位置确定了整个探测序列,比如第一个探测位置T[1],那么下一个位置是T[2],然后T[3]....故只有m种不同的探测序列。随着时间推移,连续被占用的槽不断增多,平均查找时间随之增加。这种现象称为集群现象。

二次探测再散列:d=1^2,(-1)^2,2^2,(-2)^2......这时就是二次探测再散列,初始探查的位置确定了整个探测序列,故只有m种不同的探测序列。但出现集群现象的概率降低了很多。

伪随机探测再散列:d=伪随机数序列。

双重散列:使用的函数:h(k,i) = (h1(k) + i h2(k)) mod m, i=0, 1, …, m-1

为能查找整个散列表,值h2(k)要与表的大小m互质。确保这一条件成立的一种方法是取m为2的幂,并设计一个总能产生奇数的h2。另一种方法是取m为质数,并设计一个总是产生较m小的正整数的h2。例如,可以取m为质数,h1(k)=k mod m , h2(k)=1+(k mod m’),m’=m-1。

(2)链地址法

把散列到同一个槽中的所有元素都放在一个链表中。相对于开放地址法,可能会增加存储空间。

(3)建立一个公共溢出区

若发生冲突,把key存入公共溢出区。

5、完全散列

如果某一种散列技术在进行查找时,其最坏情况内存访问次数为O(1)的话(没有冲突产生),则称其为完全散列(perfect hashing)。通常利用一种两级的散列方案,每一级上都采用全域散列,用一个二次散列表Sj存储所有散列到槽j中的关键字,就像是把链接法中的链表改成一个散列表。为了确保在第二级上不出现碰撞,需要让第二级散列表Sj的大小mj为散列到槽j中的关键字数nj的平方。如果利用从某一全域散列函数类中随机选出的散列函数h,来将n个关键字存储到一个大小为m=n的散列表中,并将每个二次散列表的大小置为mj=nj2 (j=0,
1, …, m-1),则在一个完全散列方案中,存储所有二次散列表所需的存储总量的期望值小于2n。

6、散列表性能分析

装填因子a=表中填入的记录数/散列表的长度。a标志着散列表的装满程度。散列表查找成功和不成功的平均查找长度分析很复杂,其中链接法处理冲突插入和删除时间为o(1),操作也很方便,适合经常有记录删除的哈希表。链接法对哈希函数的依赖很大,如果哈希函数不好,可能会浪费很多空间。而开放地址法的删除记录时可以把删除的位置赋一个特殊值以标识这个记录被删除了。这样就不会影响其他记录插入和查找。

7、附录

参考书籍:《算法导论》  《数据结构》

哈希表的应用实例:

  1. /*
  2. * 题目:给定一个全部由字符串组成的字典,字符串全部由大写字母构成。其中为每个字符串编写密码,编写的
  3. *       方式是对于 n 位字符串,给定一个 n 位数,大写字母与数字的对应方式按照电话键盘的方式:
  4. *         2: A,B,C     5: J,K,L    8: T,U,V
  5. *         3: D,E,F     6: M,N,O    9: W,X,Y,Z
  6. *         4: G,H,I     7: P,Q,R,S
  7. * 题目给出一个1--12位的数,找出在字典中出现且密码是这个数的所有字符串。字典中字符串的个数不超过5000。
  8. *
  9. * 思路:1.回溯法找出所有可能的字符串
  10. *       2.在字典中查找此字符串是否存在。(字典存储采用哈希表存储)
  11. *
  12. */
  13. #include<stdio.h>
  14. #include<stdlib.h>
  15. #include<string.h>
  16. #define HASHTABLE_LENGTH 5001  //哈希表长度
  17. #define STRING_LENGTH   13     //单词最大长度
  18. //字符串
  19. typedef struct
  20. {
  21. char str[STRING_LENGTH];
  22. int length;
  23. }HString;
  24. HString string={'\0',0};             //暂存可能的字符串
  25. HString hashTable[HASHTABLE_LENGTH]; //哈希表
  26. //hash函数,构造哈希表
  27. void createHashTable(char *str)
  28. {
  29. int i,key,step=1;
  30. i=key=0;
  31. while(str[i]){
  32. key+=str[i++]-'A';
  33. }
  34. key%=HASHTABLE_LENGTH;
  35. while(1){
  36. if(hashTable[key].length==0){
  37. hashTable[key].length=strlen(str);
  38. strcpy(hashTable[key].str,str);
  39. break;
  40. }
  41. key=(key+step+HASHTABLE_LENGTH)%HASHTABLE_LENGTH;
  42. //处理冲突,线性探测再散列
  43. if(step>0)
  44. step=-step;
  45. else{
  46. step=-step;
  47. step++;
  48. }
  49. }
  50. }
  51. //从文件中读字典
  52. void readString()
  53. {
  54. int i;
  55. char str[STRING_LENGTH];
  56. char ch;
  57. FILE *fp;
  58. if((fp=fopen("document/dictionary.txt","r"))==NULL){
  59. printf("can not open file!\n");
  60. exit(0);
  61. }
  62. i=0;
  63. while((ch=getc(fp))!=EOF){
  64. if(ch=='\n'){//读完一个字符串
  65. str[i]='\0';
  66. createHashTable(str);
  67. i=0;
  68. continue;
  69. }
  70. str[i++]=ch;
  71. }
  72. if(fclose(fp)){
  73. printf("can not close file!\n");
  74. exit(0);
  75. }
  76. }
  77. //在哈希表中查找是否存在该字符串,存在返回1,不存在返回0
  78. int search(char *str)
  79. {
  80. int i,key,step=1;
  81. i=key=0;
  82. while(str[i]){
  83. key+=str[i++]-'A';
  84. }
  85. key%=HASHTABLE_LENGTH;
  86. while(1){
  87. if(hashTable[key].length==0)
  88. return 0;
  89. if(strcmp(hashTable[key].str,str)==0){
  90. return 1;
  91. }
  92. key=(key+step+HASHTABLE_LENGTH)%HASHTABLE_LENGTH;
  93. //处理冲突,线性探测再散列
  94. if(step>0)
  95. step=-step;
  96. else{
  97. step=-step;
  98. step++;
  99. }
  100. }
  101. return 0;
  102. }
  103. //求所有可能的字符串
  104. void getString(char* num)
  105. {
  106. int i,digit,max;
  107. if(*num==0){//递归出口,字符串已到末尾
  108. string.str[string.length]='\0';
  109. if(search(string.str))//这个字符串存在于字典中,输出
  110. puts(string.str);
  111. return;
  112. }
  113. digit=*num-'0';//取第一位字符,转成数字
  114. if(digit>=2&&digit<=6){
  115. i=(digit-2)*3+'A';
  116. max=(digit-2)*3+'A'+3;
  117. }
  118. else if(digit==7){
  119. i='P';
  120. max='P'+4;
  121. }
  122. else if(digit==8){
  123. i='T';
  124. max='T'+3;
  125. }
  126. else if(digit==9){
  127. i='W';
  128. max='W'+4;
  129. }
  130. for(i;i<max;i++){
  131. string.str[string.length++]=i;
  132. getString(num+1); //递归
  133. string.length--;
  134. }
  135. }
  136. void main()
  137. {
  138. char num[STRING_LENGTH];   //由于输入的数字超出了unsigned long的范围,所以用字符串来存储
  139. readString();              //把字典从文件中读入内存
  140. printf("please inputer an number(1--12位,不能有0或1)\n");
  141. scanf("%s",num);
  142. getString(num);
  143. }

哈希表(散列表),Hash表漫谈的更多相关文章

  1. Java 集合 散列表hash table

    Java 集合 散列表hash table @author ixenos 摘要:hash table用链表数组实现.解决散列表的冲突:开放地址法 和 链地址法(冲突链表方式) hash table 是 ...

  2. 散列表(Hash Table)

    散列表(hash table): 也称为哈希表. 根据wikipedia的定义:是根据关键字(Key value)而直接访问在内存存储位置的数据结构.也就是说,它通过把键值通过一个函数的计算,映射到表 ...

  3. 散列表(Hash table)及其构造

    散列表(Hash table) 散列表,是根据关键码值(Key value)而直接进行访问的数据结构.它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度.这个映射函数叫做散列函数,存放记录 ...

  4. [转载] 散列表(Hash Table)从理论到实用(上)

    转载自:白话算法(6) 散列表(Hash Table)从理论到实用(上) 处理实际问题的一般数学方法是,首先提炼出问题的本质元素,然后把它看作一个比现实无限宽广的可能性系统,这个系统中的实质关系可以通 ...

  5. [转载] 散列表(Hash Table)从理论到实用(中)

    转载自:白话算法(6) 散列表(Hash Table)从理论到实用(中) 不用链接法,还有别的方法能处理碰撞吗?扪心自问,我不敢问这个问题.链接法如此的自然.直接,以至于我不敢相信还有别的(甚至是更好 ...

  6. [转载] 散列表(Hash Table) 从理论到实用(下)

    转载自: 白话算法(6) 散列表(Hash Table) 从理论到实用(下) [澈丹,我想要个钻戒.][小北,等等吧,等我再修行两年,你把我烧了,舍利子比钻戒值钱.] ——自扯自蛋 无论开发一个程序还 ...

  7. JavaScript 哈希表(散列表)应用

    查找的效率与比较次数密切相关.基于比较的程序,运算效率是比较低的.比如平时可以通过indexOf查找一个数据.但这是一个基于比较的一个实现.如果是淘宝那样有上亿个商品,那么用indeOf 来查数据就会 ...

  8. JavaScript 哈希表(散列表)实现和应用

    查找的效率与比较次数密切相关.基于比较的程序,运算效率是比较低的.比如平时可以通过indexOf查找一个数据.但这是一个基于比较的一个实现.如果是淘宝那样有上亿个商品,那么用indeOf 来查数据就会 ...

  9. 线性表 & 散列表

    线性表: 数据排成一条线一样的机构,每个线性表上的数据最多只有前后两个方向, 包括 数组,链表,队列,栈. 非线性表 : 数据之间并不是简单的前后关系,有二叉树.图等. 散列表(基于 数组支持按照下标 ...

  10. 散列(Hash)表入门

    一.概述 以 Key-Value 的形式进行数据存取的映射(map)结构 简单理解:用最基本的向量(数组)作为底层物理存储结构,通过适当的散列函数在词条的关键码与向量单元的秩(下标)之间建立映射关系 ...

随机推荐

  1. Appium Python API 中文版

    Appium_Python_Api文档 1.contextscontexts(self): Returns the contexts within the current session. 返回当前会 ...

  2. C++ Primer 笔记——union

    1.union是一种特殊的类.一个union可以有多个数据成员,但是在任意时刻,只有一个数据成员可以有值.当我们给union的某个成员赋值之后,该union的其他成员就变成未定义的状态了.分配给一个u ...

  3. Spring Cloud Eureka简介及原理

    Eureka是Netflix开发的服务发现组件,本身是一个基于REST的服务.Spring Cloud将它集成在其子项目spring-cloud-netflix中,以实现Spring Cloud的服务 ...

  4. python 在WINDOS虚拟环境部署

    #查看电脑的版本 C:\Users\lys>pip -V pip 8.1.1 from e:\python\python3.5\lib\site-packages (python 3.5) #安 ...

  5. 20165323《Java程序设计》第九周学习总结

    一.教材内容学习总结 URL类 1.URL 类是 java.net 包中的一个重要的类,使用 URL 创建对象的应用程序称为客户端程序. 2.一个 URL 对象通常包含最基本的三部分信息:协议.地址和 ...

  6. P1036 选数 题解

    题目链接https://www.luogu.org/problemnew/show/P1036 题目描述 已知 nnn 个整数 x1,x2,-,xnx_1,x_2,-,x_nx1​,x2​,-,xn​ ...

  7. python的学习和使用

    1.python的官网:https://www.python.org/ Python简介: .Python 是一个高层次的结合了解释性.编译性.互动性和面向对象的脚本语言. .Python 的设计具有 ...

  8. CentOS6 YUM安装MariaDB10.3.10

    1.先新增加一个MariaDB.repo vi /etc/yum.repos.d/MariaDB.repo [mariadb] name = MariaDB baseurl = http://mirr ...

  9. VS2013 快捷键 与 RESHARPER 冲突(转)

    1.VS设置工具-->选项-->环境-->键盘-->重置 2.RESHARPER -->Options-->Environment → Keyboard & ...

  10. BZOJ4456/UOJ#184[Zjoi2016]旅行者 分治 最短路

    原文链接http://www.cnblogs.com/zhouzhendong/p/8682133.html 题目传送门 - BZOJ4456 题目传送门 - UOJ#184 题意 $n\times ...