原理

介绍

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

哈希表hash table(key,value) 的做法其实很简单,就是把Key通过一个固定的算法函数既所谓的哈希函数转换成一个整型数字,然后就将该数字对数组长度进行取余,取余结果就当作数组的下标,将value存储在以该数字为下标的数组空间里。

而当使用哈希表进行查询的时候,就是再次使用哈希函数将key转换为对应的数组下标,并定位到该空间获取value,如此一来,就可以充分利用到数组的定位性能进行数据定位。

哈希表最大的优点,就是把数据的存储和查找消耗的时间大大降低,几乎可以看成是常数时间;而代价仅仅是消耗比较多的内存。然而在当前可利用内存越来越多的情况下,用空间换时间的做法是值得的。另外,编码比较容易也是它的特点之一。 哈希表又叫做散列表,分为“开散列” 和“闭散列”。

我们使用一个下标范围比较大的数组来存储元素。可以设计一个函数(哈希函数, 也叫做散列函数),使得每个元素的关键字都与一个函数值(即数组下标)相对应,于是用这个数组单元来存储这个元素;也可以简单的理解为,按照关键字为每一 个元素“分类”,然后将这个元素存储在相应“类”所对应的地方。

但是,不能够保证每个元素的关键字与函数值是一一对应的,因此极有可能出现对于不同的元素,却计算出了相同的函数值,这样就产生了“冲突”,换句话说,就是把不同的元素分在了相同的“类”之中。后面我们将看到一种解决“冲突”的简便做法。 总的来说,“直接定址”与“解决冲突”是哈希表的两大特点。

哈希函数构造

就是映射函数构造,看某个元素具体属于哪一个类别。 
除余法: 选择一个适当的正整数 p ,令 h(k ) = k mod p ,这里, p 如果选取的是比较大的素数,效果比较好。而且此法非常容易实现,因此是最常用的方法。最直观的一种,上图使用的就是这种散列法,公式: 
index = value % 16 
学过汇编的都知道,求模数其实是通过一个除法运算得到的,所以叫“除法散列法”。

平方散列法 
求index是非常频繁的操作,而乘法的运算要比除法来得省时(对现在的CPU来说,估计我们感觉不出来),所以我们考虑把除法换成乘法和一个位移操作。公式: 
index = (value * value) >> 28 ( 右移,除以2^28。记法:左移变大,是乘。右移变小,是除)

数字选择法: 如果关键字的位数比较多,超过长整型范围而无法直接运算,可以选择其中数字分布比较均匀的若干位,所组成的新的值作为关键字或者直接作为函数值。

斐波那契(Fibonacci)散列法:平方散列法的缺点是显而易见的,所以我们能不能找出一个理想的乘数,而不是拿value本身当作乘数呢?答案是肯定的。 
1,对于16位整数而言,这个乘数是40503 
2,对于32位整数而言,这个乘数是2654435769 
3,对于64位整数而言,这个乘数是11400714819323198485 
这几个“理想乘数”是如何得出来的呢?这跟一个法则有关,叫黄金分割法则,而描述黄金分割法则的最经典表达式无疑就是著名的斐波那契数列,即如此形式的序列:0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233,377, 610, 987, 1597, 2584, 4181, 6765, 10946,…。另外,斐波那契数列的值和太阳系八大行星的轨道半径的比例出奇吻合。 
对我们常见的32位整数而言,公式: 
index = (value * 2654435769) >> 28

冲突处理

线性重新散列技术易于实现且可以较好的达到目的。令数组元素个数为 S ,则当 h(k) 已经存储了元素的时候,依次探查 (h(k)+i) mod S , i=1,2,3…… ,直到找到空的存储单元为止(或者从头到尾扫描一圈仍未发现空单元,这就是哈希表已经满了,发生了错误。当然这是可以通过扩大数组范围避免的)。

举例

哈希表支持的运算主要有:初始化(makenull)、哈希函数值的运算(h(x))、插入元素(insert)、查找元素(member)。 设插入的元素的关键字为 x ,A 为存储的数组。

伪代码 
初始化:

  1. const empty=maxlongint; // 用非常大的整数代表这个位置没有存储元素
  2. p=; // 表的大小
  3. procedure makenull;
  4. var i:integer;
  5. begin
  6. for i:= to p- do
  7. A[i]:=empty;
  8. End;

哈希函数值的运算根据函数的不同而变化,例如除余法的一个例子:

  1. function h(x:longint):Integer;
  2. begin
  3. h:= x mod p;
  4. end;

我们注意到,插入和查找首先都需要对这个元素定位,即如果这个元素若存在,它应该存储在什么位置,因此加入一个定位的函数 locate:

  1. function locate(x:longint):integer;
  2. var orig,i:integer;
  3. begin
  4. orig:=h(x);
  5. i:=;
  6. while (i<S)and(A[(orig+i)mod S]<>x)and(A[(orig+i)mod S]<>empty) do
  7. inc(i);
  8. //当这个循环停下来时,要么找到一个空的存储单元,要么找到这个元
  9. //素存储的单元,要么表已经满了
  10. locate:=(orig+i) mod S;
  11. end;

插入元素 :

  1. procedure insert(x:longint);
  2. var posi:integer;
  3. begin
  4. posi:=locate(x); //定位函数的返回值
  5. if A[posi]=empty then A[posi]:=x
  6. else error; //error 即为发生了错误,当然这是可以避免的
  7. end;

查找元素是否已经在表中:

  1. procedure member(x:longint):boolean;
  2. var posi:integer;
  3. begin
  4. posi:=locate(x);
  5. if A[posi]=x then member:=true
  6. else member:=false;
  7. end;

当数据规模接近哈希表上界或者下界的时候,哈希表完全不能够体现高效的特点,甚至还不如一般算法。但是如果规模在中央,它高效的特点可以充分体现。试验表明当元素充满哈希表的 90% 的时候,效率就已经开始明显下降。这就给了我们提示:如果确定使用哈希表,应该尽量使数组开大,但对最太大的数组进行操作也比较费时间,需要找到一个平衡点。通常使它的容量至少是题目最大需求的 120% ,效果比较好(这个仅仅是经验,没有严格证明)。

什么时候适合应用哈希表呢?如果发现解决这个问题时经常要询问:“某个元素是否在已知集合中?”,也就是需要高效的数据存储和查找,则使用哈希表是最好不过的了!那么,在应用哈希表的过程中,值得注意的是什么呢?

哈希函数的设计很重要。一个不好的哈希函数,就是指造成很多冲突的情况,从前面的例子已经可以看出来,解决冲突会浪费掉大量时间,因此我们的目标 就是尽力避免冲突。前面提到,在使用“除余法”的时候,h(k)=k mod p ,p 最好是一个大素数。这就是为了尽力避免冲突。为什么呢?假设 p=1000 ,则哈希函数分类的标准实际上就变成了按照末三位数分类,这样最多1000类,冲突会很多。一般地说,如果 p 的约数越多,那么冲突的几率就越大。

简单的证明:假设 p 是一个有较多约数的数,同时在数据中存在 q 满足 gcd(p,q)=d >1 ,即有 p=a*d , q=b*d, 则有 q mod p= q – p* [q div p] =q – p*[b div a] . ① 其中 [b div a ] 的取值范围是不会超过 [0,b] 的正整数。也就是说, [b div a] 的值只有 b+1 种可能,而 p 是一个预先确定的数。因此 ① 式的值就只有 b+1 种可能了。这样,虽然mod 运算之后的余数仍然在 [0,p-1] 内,但是它的取值仅限于 ① 可能取到的那些值。也就是说余数的分布变得不均匀了。容易看出, p 的约数越多,发生这种余数分布不均匀的情况就越频繁,冲突的几率越高。而素数的约数是最少的,因此我们选用大素数。记住“素数是我们的得力助手”。

另一方面,一味的追求低冲突率也不好。理论上,是可以设计出一个几乎完美,几乎没有冲突的函数的。然而,这样做显然不值得,因为这样的函数设计 很浪费时间而且编码一定很复杂,与其花费这么大的精力去设计函数,还不如用一个虽然冲突多一些但是编码简单的函数。因此,函数还需要易于编码,即易于实 现。综上所述,设计一个好的哈希函数是很关键的。而“好”的标准,就是较低的冲突率和易于实现。另外,使用哈希表并不是记住了前面的基本操作就能以不变应万变的。有的时候,需要按照题目的要求对哈希表的结构作一些改进。往往一些简单的改进就可以带来巨大的方便。

这些只是一般原则,真正遇到试题的时候实际情况千变万化,需要具体问题具体分析才行。

当然,以上讲解的都是闭散列,如果使用链表,做开散列的话就可以更方便存储和删除了。其实这个和之前做18-600的malloc里面说的东西很类似。

拉链法

上面的方法使用数组实现的,其实很多时候需要使用数组链表来做。开一个数组,数组每个元素都是一个链表。(hash函数选择,针对字符串,整数,排列,具体相应的hash方法。 碰撞处理,一种是open hashing,也称为拉链法;另一种就是closed hashing,也称开地址法,opened addressing。)

使用除法散列: 

使用斐波那契散列: 

使用扩展法: 
d-left hashing中的d是多个的意思,我们先简化这个问题,看一看2-left hashing。2-left hashing指的是将一个哈希表分成长度相等的两半,分别叫做T1和T2,给T1和T2分别配备一个哈希函数,h1和h2。在存储一个新的key时,同时用两个哈希函数进行计算,得出两个地址h1[key]和h2[key]。这时需要检查T1中的h1[key]位置和T2中的h2[key]位置,哪一个位置已经存储的(有碰撞的)key比较多,然后将新key存储在负载少的位置。如果两边一样多,比如两个位置都为空或者都存储了一个key,就把新key 存储在左边的T1子表中,2-left也由此而来。在查找一个key时,必须进行两次hash,同时查找两个位置。

hash索引跟B树索引的区别。

Hash 索引结构的特殊性,其检索效率非常高,索引的检索可以一次定位,不像B-Tree 索引需要从根节点到枝节点,最后才能访问到页节点这样多次的IO访问,所以 Hash 索引的查询效率要远高于 B-Tree 索引。

(1)Hash 索引仅仅能满足”=”,”IN”和”<=>”查询,不能使用范围查询。 
由于 Hash 索引比较的是进行 Hash 运算之后的 Hash 值,所以它只能用于等值的过滤,不能用于基于范围的过滤,因为经过相应的 Hash 算法处理之后的 Hash 值的大小关系,并不能保证和Hash运算前完全一样。

(2)Hash 索引无法被用来避免数据的排序操作。 
由于 Hash 索引中存放的是经过 Hash 计算之后的 Hash 值,而且Hash值的大小关系并不一定和 Hash 运算前的键值完全一样,所以数据库无法利用索引的数据来避免任何排序运算;

(3)Hash 索引不能利用部分索引键查询。 
对于组合索引,Hash 索引在计算 Hash 值的时候是组合索引键合并后再一起计算 Hash 值,而不是单独计算 Hash 值,所以通过组合索引的前面一个或几个索引键进行查询的时候,Hash 索引也无法被利用。

(4)Hash 索引在任何时候都不能避免表扫描。 
前面已经知道,Hash 索引是将索引键通过 Hash 运算之后,将 Hash运算结果的 Hash 值和所对应的行指针信息存放于一个 Hash 表中,由于不同索引键存在相同 Hash 值,所以即使取满足某个 Hash 键值的数据的记录条数,也无法从 Hash 索引中直接完成查询,还是要通过访问表中的实际数据进行相应的比较,并得到相应的结果。

(5)Hash 索引遇到大量Hash值相等的情况后性能并不一定就会比B-Tree索引高。 
对于选择性比较低的索引键,如果创建 Hash 索引,那么将会存在大量记录指针信息存于同一个 Hash 值相关联。这样要定位某一条记录时就会非常麻烦,会浪费多次表数据的访问,而造成整体性能低下。

实现

问题描述:设计哈希表实现电话号码查询系统,实现下列功能: 
(1) 假定每个记录有下列数据项:电话号码、用户名、地址。 
(2) 一是从数据文件old.txt(自己现行建好)中读入各项记录,二是由系统随机产生各记录,并且把记录保存到new.txt文件中以及显示到屏幕上,记录条数不要少于30,然后分别以电话号码和用户名为关键字建立哈希表。 
(3) 分别采用伪随机探测再散列法和再哈希法解决冲突。 
(4) 查找并显示给定电话号码的记录;查找并显示给定用户名的记录。 
(5) 将没有查找的结果保存到结果文件Out.txt中,显示查找结果前,要有提示语句。

  1. // MyHashTable.cpp : 定义控制台应用程序的入口点。
  2. ////设计哈希表实现电话号码查询系统
  3. //说明:一是从文件old.txt中读取的数据自己在程序运行前建立,
  4. // 二是由系统随机生成数据,在程序运行由随机数产生器生成,并且将产生的记录保存到 new.txt文件。
  5.  
  6. //存在的问题:使用随机产生的文件,在显示时出现乱码
  7.  
  8. #include "stdafx.h"
  9. #include<fstream>//文件流
  10. #include<iostream>
  11. #include <string>
  12. using namespace std;
  13.  
  14. const int D[] = {,,,,,,,};//预定再随机数
  15. const int HASH_MAXSIZE = ;//哈希表长度
  16.  
  17. //记录信息类型
  18. class DataInfo
  19. {
  20. public:
  21. DataInfo();//默认构造函数
  22. friend ostream& operator<<(ostream& out, const DataInfo& dataInfo); //重载输出操作符
  23. //friend class HashTable;
  24.  
  25. //private:
  26. string name;//姓名
  27. string phone;//电话号码
  28. string address;//地址
  29. char sign;//冲突的标志位,'1'表示冲突,'0'表示无冲突
  30. };
  31.  
  32. DataInfo::DataInfo():name(""), phone(""), address(""), sign('')
  33. {
  34.  
  35. }
  36.  
  37. ostream& operator<<(ostream& out, const DataInfo& dataInfo) //重载输出操作符
  38. {
  39. cout << "姓名:" << dataInfo.name << " 电话:" << dataInfo.phone
  40. << " 地址:" << dataInfo.address << endl;
  41. return out;
  42. }
  43.  
  44. //存放记录的哈希表类型
  45. class HashTable
  46. {
  47. public:
  48. HashTable();//默认构造函数
  49. ~HashTable();//析构函数
  50. int Random(int key, int i);// 伪随机数探测再散列法处理冲突
  51. void Hashname(DataInfo *dataInfo);//以名字为关键字建立哈希表
  52. int Rehash(int key, string str);// 再哈希法处理冲突 注意处理冲突还有链地址法等
  53. void Hashphone(DataInfo *dataInfo);//以电话为关键字建立哈希表
  54. void Hash(char *fname, int n);// 建立哈希表
  55. //fname 是数据储存的文件的名称,用于输入数据,n是用户选择的查找方式
  56.  
  57. int Findname(string name);// 根据姓名查找哈希表中的记录对应的关键码
  58. int Findphone(string phone);// 根据电话查找哈希表中的记录对应的关键码
  59. void Outhash(int key);// 输出哈希表中关键字码对应的一条记录
  60. void Outfile(string name, int key);// 在没有找到时输出未找到的记录
  61. void Rafile();// 随机生成文件,并将文件保存在 new.txt文档中
  62. void WriteToOldTxt();//在运行前先写入数据
  63.  
  64. //private:
  65. DataInfo *value[HASH_MAXSIZE];
  66. int length;//哈希表长度
  67. };
  68.  
  69. HashTable::HashTable():length()//默认构造函数
  70. {
  71. //memset(value, NULL, HASH_MAXSIZE*sizeof(DataInfo*));
  72. for (int i=; i<HASH_MAXSIZE; i++)
  73. {
  74. value[i] = new DataInfo();
  75. }
  76. }
  77.  
  78. HashTable::~HashTable()//析构函数
  79. {
  80. delete[] *value;
  81. }
  82.  
  83. void HashTable::WriteToOldTxt()
  84. {
  85. ofstream openfile("old.txt");
  86. if (openfile.fail())
  87. {
  88. cout << "文件打开错误!" << endl;
  89. exit();
  90. }
  91.  
  92. string oldname;
  93. string oldphone;
  94. string oldaddress;
  95.  
  96. for (int i=; i<; i++)
  97. {
  98. cout << "请输入第" << i+ << "条记录:" << endl;
  99. cin >> oldname ;
  100. cin >> oldphone;
  101. cin >> oldaddress;
  102. openfile << oldname << " " << oldphone << " " << oldaddress << "," << endl;
  103. }
  104. openfile.close();
  105. }
  106.  
  107. int HashTable::Random(int key, int i)// 伪随机数探测再散列法处理冲突
  108. {//key是冲突时的哈希表关键码,i是冲突的次数,N是哈希表长度
  109. //成功处理冲突返回新的关键码,未进行冲突处理则返回-1
  110. int h;
  111. if(value[key]->sign == '')//有冲突
  112. {
  113. h = (key + D[i]) % HASH_MAXSIZE;
  114. return h;
  115. }
  116. return -;
  117. }
  118.  
  119. void HashTable::Hashname(DataInfo *dataInfo)//以名字为关键字建立哈希表
  120. {//利用除留取余法建立以名字为关键字建立的哈希函数,在发生冲突时调用Random函数处理冲突
  121. int i = ;
  122. int key = ;
  123.  
  124. for (int t=; dataInfo->name[t]!='\0'; t++)
  125. {
  126. key = key + dataInfo->name[t];
  127. }
  128. key = key % ;
  129. while(value[key]->sign == '')//有冲突
  130. {
  131. key = Random(key, i++);//处理冲突
  132. }
  133. if(key == -) exit();//无冲突
  134. length++;//当前数据个数加
  135. value[key]->name = dataInfo->name;
  136. value[key]->address = dataInfo->address;
  137. value[key]->phone = dataInfo->phone;
  138. value[key]->sign = '';//表示该位置有值
  139. //cout << value[key]->name << " " << value[key]->phone << " " << value[key]->address << endl;
  140. }
  141.  
  142. int HashTable::Rehash(int key, string str)// 再哈希法处理冲突
  143. {//再哈希时使用的是折叠法建立哈希函数
  144. int h;
  145. int num1 = (str[] - '') * + (str[] - '') * + (str[] - '') * + (str[] - '');
  146. int num2 = (str[] - '') * + (str[] - '') * + (str[] - '') * + (str[] - '');
  147. int num3 = (str[] - '') * + (str[] - '') * + (str[] - '');
  148. h = num1 + num2 + num3;
  149. h = (h + key) % HASH_MAXSIZE;
  150. return h;
  151. }
  152.  
  153. void HashTable::Hashphone(DataInfo *dataInfo)//以电话为关键字建立哈希表
  154. {//利用除留取余法建立以电话为关键字建立的哈希函数,在发生冲突时调用Rehash函数处理冲突
  155. int key = ;
  156. int t;
  157.  
  158. for(t=; dataInfo->phone[t] != '\0'; t++)
  159. {
  160. key = key + dataInfo->phone[t];
  161. }
  162. key = key % ;
  163. while(value[key]->sign == '')//有冲突
  164. {
  165. key = Rehash(key, dataInfo->phone);
  166. }
  167. length++;//当前数据个数加
  168. value[key]->name = dataInfo->name;
  169. value[key]->address = dataInfo->address;
  170. value[key]->phone = dataInfo->phone;
  171. value[key]->sign = '';//表示该位置有值
  172. }
  173.  
  174. void HashTable::Outfile(string name, int key)//在没有找到时输出未找到的记录
  175. {
  176. ofstream fout;
  177. if((key == -)||(value[key]->sign == ''))//判断哈希表中没有记录
  178. {
  179. fout.open("out.txt",ios::app);//打开文件
  180.  
  181. if(fout.fail())
  182. {
  183. cout << "文件打开失败!" << endl;
  184. exit();
  185. }
  186. fout << name << endl;//将名字写入文件,有个问题,每次写入的时候总是将原来的内容替换了
  187. fout.close();
  188. }
  189. }
  190.  
  191. void HashTable::Outhash(int key)//输出哈希表中关键字码对应的记录
  192. {
  193. if((key==-)||(value[key]->sign==''))
  194. cout << "没有找到这条记录!" << endl;
  195. else
  196. {
  197. for(unsigned int i=; value[key]->name[i]!='\0'; i++)
  198. {
  199. cout << value[key]->name[i];
  200. }
  201.  
  202. for(unsigned int i=; i<; i++)
  203. {
  204. cout << " ";
  205. }
  206.  
  207. cout << value[key]->phone;
  208.  
  209. for(int i=; i<; i++)
  210. {
  211. cout << " ";
  212. }
  213.  
  214. cout << value[key]->address << endl;
  215. }
  216. }
  217.  
  218. void HashTable::Rafile()//随机生成文件,并将文件保存在new.txt文档中
  219. {
  220. ofstream fout;
  221. fout.open("new.txt");//打开文件,等待写入
  222. if(fout.fail())
  223. {
  224. cout << "文件打开失败!" << endl;
  225. exit();
  226. }
  227. for(int j=; j<; j++)
  228. {
  229. string name = "";
  230. for(int i=; i<; i++)//随机生成长个字的名字
  231. {
  232. name += rand() % + 'a';//名字是由个字母组成
  233. }
  234. fout << name << " ";//将名字写入文件
  235.  
  236. string phone = "";
  237. for(int i=; i<; i++)//随机生成长位的电话号码
  238. {
  239. phone += rand() % + '';//电话号码是纯数字
  240. }
  241. fout << phone << " ";//将电话号码写入文件
  242.  
  243. string address = "";
  244. for(int i=; i<; i++)//随机生成长个字的名字
  245. {
  246. address += rand() % + 'a';//地址是由个字母组成
  247. }
  248. address += ',';
  249. fout << address << endl;//将地址写入文件
  250. }
  251. fout.close();
  252. }
  253.  
  254. void HashTable::Hash(char *fname, int n)//建立哈希表
  255. //fname是数据储存的文件的名称,用于输入数据,n是用户选择的查找方式
  256. //函数输入数据,并根据选择调用Hashname或Hashphone函数进行哈希表的建立
  257. {
  258. ifstream fin;
  259. int i;
  260. fin.open(fname);//读文件流对象
  261. if(fin.fail())
  262. {
  263. cout << "文件打开失败!" << endl;
  264. exit();
  265. }
  266. while(!fin.eof())//按行读入数据
  267. {
  268. DataInfo *dataInfo = new DataInfo();
  269. char* str = new char[];
  270. fin.getline(str, , '\n');//读取一行数据
  271.  
  272. if(str[] == '*')//判断数据结束
  273. {
  274. break;
  275. }
  276.  
  277. i = ;//记录字符串数组的下标
  278. //a-z:97-122 A-Z:65-90
  279. //本程序的姓名和地址都使用小写字母
  280. while((str[i] < ) || (str[i] > ))//读入名字
  281. {
  282. i++;
  283. }
  284.  
  285. for(; str[i]!=' '; i++)
  286. {
  287. dataInfo->name += str[i];
  288. }
  289.  
  290. while(str[i] == ' ')
  291. {
  292. i++;
  293. }
  294.  
  295. for(int j=; str[i]!=' '; j++,i++)//读入电话号码
  296. {
  297. dataInfo->phone += str[i];
  298. }
  299.  
  300. while(str[i] == ' ')
  301. {
  302. i++;
  303. }
  304.  
  305. for(int j=; str[i]!=','; j++,i++)//读入地址
  306. {
  307. dataInfo->address += str[i];
  308. }
  309.  
  310. if(n == )
  311. {
  312. Hashname(dataInfo);
  313. }
  314. else
  315. {
  316. Hashphone(dataInfo);//以电话为关键字
  317. }
  318.  
  319. delete []str;
  320. delete dataInfo;
  321. }
  322. fin.close();
  323. }
  324.  
  325. int HashTable::Findname(string name)//根据姓名查找哈希表中的记录对应的关键码
  326. {
  327. int i = ;
  328. int j = ;
  329. int t;
  330. int key = ;
  331.  
  332. for(key=, t=; name[t] != '\0'; t++)
  333. {
  334. key = key + name[t];
  335. }
  336. key = key % ;
  337. while((value[key]->sign == '') && (value[key]->name != name))
  338. {
  339. key = Random(key, i++);
  340. j++;
  341. if(j >= length) return -;
  342. }
  343. return key;
  344. }
  345.  
  346. int HashTable::Findphone(string phone)//根据电话查找哈希表中的记录对应的关键码
  347. {
  348. int key = ;
  349. int t;
  350.  
  351. for(t=; phone[t] != '\0' ; t++)
  352. {
  353. key = key + phone[t];
  354. }
  355. key = key % ;
  356. int j = ;
  357. while((value[key]->sign == '') && (value[key]->phone != phone))
  358. {
  359. key = Rehash(key, phone);
  360. j++;
  361. if(j >= length)
  362. {
  363. return -;
  364. }
  365. }
  366. return key;
  367. }
  368.  
  369. void main()
  370. {
  371. //WriteToOldTxt();
  372. int k;
  373. int ch;
  374. char *Fname;
  375. HashTable *ht = new HashTable;
  376. while()
  377. {
  378. system("cls");//cls命令清除屏幕上所有的文字
  379. cout << "欢迎使用本系统!" << endl << endl;
  380. cout << "请选择数据" << endl;
  381. cout << "1.使用已有数据文件" << endl;
  382. cout << "2.随机生成数据文件" << endl;
  383. cout << "0.结束" << endl;
  384. cout << "输入相应序号选择功能:";
  385. cin >> k;
  386. switch(k)
  387. {
  388. case :
  389. return;
  390. case :
  391. Fname = "old.txt";//从数据文件old.txt(自己现行建好)中读入各项记录
  392. break;
  393. case :
  394. ht->Rafile();
  395. Fname = "new.txt";//由系统随机产生各记录,并且把记录保存到new.txt文件中
  396. break;
  397. default:
  398. cout << "输入序号有误,退出程序。" << endl;
  399. return;
  400. }
  401.  
  402. do
  403. {
  404. system("cls");
  405. cout << " 请选择查找方式" << endl;
  406. cout << "1.通过姓名查找" << endl;
  407. cout << "2.通过电话查找" << endl;
  408. cout << "输入相应序号选择功能:";
  409. cin >> ch;
  410. if((ch != ) && (ch != ))
  411. cout << "输入序号有误!" << endl;
  412. }while((ch != ) && (ch != ));
  413.  
  414. ht->Hash(Fname, ch);
  415. while(ch == )
  416. {
  417. int choice;
  418. cout << endl << "请选择功能" << endl;
  419. cout << "1.输入姓名查找数据" << endl;
  420. cout << "2.显示哈希表" << endl;
  421. cout << "0.退出"<<endl;
  422. cout << "输入相应序号选择功能:";
  423. cin >> choice;
  424. switch(choice)
  425. {
  426. case :
  427. {//注意此处应该加上大括号
  428. int key1;
  429. string name;
  430. cout << "请输入姓名:";
  431. cin >> name;
  432. key1 = ht->Findname(name);
  433. ht->Outfile(name, key1);
  434. ht->Outhash(key1);
  435. }
  436. break;
  437.  
  438. case :
  439. {
  440. for(int i=; i<HASH_MAXSIZE; i++)
  441. {
  442. if(ht->value[i]->sign!='')
  443. {
  444. ht->Outhash(i);
  445. }
  446. }
  447. }
  448. break;
  449.  
  450. default:
  451. cout << endl << "您的输入有误!" << endl;
  452. }
  453.  
  454. if(choice == )
  455. {
  456. return;
  457. }
  458. }
  459.  
  460. while(ch == )
  461. {
  462. int choice;
  463. cout << endl << "请选择功能" << endl;
  464. cout << "1.输入电话查找数据" << endl;
  465. cout << "2.显示哈希表"<<endl;
  466. cout << "0.退出"<<endl;
  467. cout << "输入相应序号选择功能:";
  468. cin >> choice;
  469. switch(choice)
  470. {
  471. case :
  472. {
  473. int key2;
  474. string phone;
  475. cout << "请输入11位的电话号码:";
  476.  
  477. do
  478. {
  479. cin >> phone;
  480. if(phone.length() != )
  481. {
  482. cout << "电话号码应为11位!\n请重新输入:";
  483. }
  484.  
  485. }while(phone.length() != );
  486.  
  487. key2 = ht->Findphone(phone);
  488. ht->Outfile(phone, key2);
  489. ht->Outhash(key2);
  490. }
  491. break;
  492.  
  493. case :
  494. {
  495. for(int i=; i<HASH_MAXSIZE; i++)
  496. {
  497. if(ht->value[i]->sign != '')
  498. {
  499. ht->Outhash(i);
  500. }
  501. }
  502. }
  503. break;
  504.  
  505. default:
  506. cout << endl << "您的输入有误!" << endl;
  507. }
  508.  
  509. if(choice == )
  510. {
  511. return;
  512. }
  513. }
  514.  
  515. while((ch != ) && (ch != ))
  516. {
  517. cout << "您的输入有误!请输入相应需要选择功能:";
  518. }
  519. }
  520. system("pause");
  521. }

代码实现来源:

  1. http://blog.csdn.net/htyurencaotang/article/details/7881427

原理说明来源:

  1. http://www.tuicool.com/articles/BvI3Ir
  2. http://blog.csdn.net/nju_yaho/article/details/7402208
  3. http://blog.csdn.net/duan19920101/article/details/51579136
  4. http://blog.sina.com.cn/s/blog_6776884e0100pko1.html
  5. http://blog.csdn.net/v_july_v/article/details/6256463

哈希表(Hash Table)原理及其实现的更多相关文章

  1. 算法与数据结构基础 - 哈希表(Hash Table)

    Hash Table基础 哈希表(Hash Table)是常用的数据结构,其运用哈希函数(hash function)实现映射,内部使用开放定址.拉链法等方式解决哈希冲突,使得读写时间复杂度平均为O( ...

  2. PHP关联数组和哈希表(hash table) 未指定

    PHP有数据的一个非常重要的一类,就是关联数组.又称为哈希表(hash table),是一种很好用的数据结构. 在程序中.我们可能会遇到须要消重的问题,举一个最简单的模型: 有一份username列表 ...

  3. 词典(二) 哈希表(Hash table)

    散列表(hashtable)是一种高效的词典结构,可以在期望的常数时间内实现对词典的所有接口的操作.散列完全摒弃了关键码有序的条件,所以可以突破CBA式算法的复杂度界限. 散列表 逻辑上,有一系列可以 ...

  4. 什么叫哈希表(Hash Table)

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

  5. 数据结构 哈希表(Hash Table)_哈希概述

    哈希表支持一种最有效的检索方法:散列. 从根来上说,一个哈希表包含一个数组,通过特殊的索引值(键)来访问数组中的元素. 哈希表的主要思想是通过一个哈希函数,在所有可能的键与槽位之间建立一张映射表.哈希 ...

  6. 哈希表(Hash table)

  7. Redis原理再学习04:数据结构-哈希表hash表(dict字典)

    哈希函数简介 哈希函数(hash function),又叫散列函数,哈希算法.散列函数把数据"压缩"成摘要,有的也叫"指纹",它使数据量变小且数据格式大小也固定 ...

  8. Hash表 hash table 又名散列表

    直接进去主题好了. 什么是哈希表? 哈希表(Hash table,也叫散列表),是根据key而直接进行访问的数据结构.也就是说,它通过把key映射到表中一个位置来访问记录,以加快查找的速度.这个映射函 ...

  9. 哈希表(Hash)的应用

    $hs=@() #定义数组 $hs=@{} #定义Hash表,使用哈希表的键可以直接访问对应的值,如 $hs["王五"] 或者 $hs.王五 的值为 75 $hs=@''@ #定义 ...

  10. (四)Redis哈希表Hash操作

    Hash全部命令如下: hset key field value # 将哈希表key中的字段field的值设为value hget key field # 返回哈希表key中的字段field的值val ...

随机推荐

  1. 欧拉函数  已经优化到o(n)

    欧拉函数 ψ(x)=x*(1-1/pi)  pi为x的质数因子 特殊性质(图片内容就是图片后面的文字) 欧拉函数是积性函数——若m,n互质, ψ(m*n)=ψ(m)*ψ(n): 当n为奇数时,   ψ ...

  2. log4net 自定义日志级别记录多个日志

    程序中原来只记录一个日志,现在我要写一个用户操作日志,需要与原来的日志分开,在config文件中一阵折腾无果(要么写不全,要么写重了,反正没办法完美分离,要么与现存代码没办法完美兼容),差点放弃准备自 ...

  3. centos7安装部署mysql5.7服务器

    因为自带源没有最新版的mysql,所以我们需要自己下载rpm包,先下载下面的rpm包源 https://repo.mysql.com//mysql57-community-release-el7-11 ...

  4. 简单 babel plugin 开发-使用lerna 工具

    babel在现在的web 应用开发上具有很重要的作用,帮助我们做了好多事情,同时又有 比较多的babel plugin 可以解决我们实际开发上的问题. 以下只是学习下如果编写一个简单的babel pl ...

  5. 使用rclone 进行minio 文件同步

    rclone 是一个开源的就有命令行的同步工具,主要是面向云存储的数据同步 安装 mac 系统 操作 cd && curl -O https://downloads.rclone.or ...

  6. YUM安装调试以及命令具体解释

    背景,须要安装cacti,google了非常多安装资料.须要先yum安装一些准备lib包,比方snmp以及openssl等等. [root@mysqlvm2 ~]# yum install net-s ...

  7. 数学的语言 化无形为可见 (Keith Devlin 著)

    第一章 数字为何靠的住 (已看) 第二章 心智的模式 (已看) 第三章 动静有数 (已看) 第四章 当数学成型 (已看) 第五章 数学揭开美之本质 (已看) 第六章 当数学到位 (已看) 第七章 数学 ...

  8. JSON字符串互相转换的三种方式和性能比较

    C# 对象与JSON字符串互相转换的三种方式 JSON(JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式. 关于内存对象和JSON字符串的相互转换, ...

  9. oracle之 安装oracle指定jdk 或者如何解决提示框显示不全

    在centos7下,安装oracle 11g. gnome的桌面.各个参数配置好后,运行runInstaller命令.此时弹出安装界面,在一次次点击[下一步]的时候,中间会弹出对话框,可是对话框显示不 ...

  10. webpack 相关插件及作用(表格)

    webpack 相关插件及作用: table th:first-of-type { width: 200px; } table th:nth-of-type(2) { width: 140px; } ...