哈希表工作原理

1. 引言 
       哈希表(Hash Table)的应用近两年才在NOI中出现,作为一种高效的数据结构,它正在竞赛中发挥着越来越重要的作用。 
哈希表最大的优点,就是把数据的存储和查找消耗的时间大大降低,几乎可以看成是常数时间;而代价仅仅是消耗比较多的内存。然而在当前可利用内存越来越多的情况下,用空间换时间的做法是值得的。另外,编码比较容易也是它的特点之一。 
       哈希表又叫做散列表,分为“开散列” 和“闭散列”。考虑到竞赛时多数人通常避免使用动态存储结构,本文中的“哈希表”仅指“闭散列”,关于其他方面读者可参阅其他书籍。

2. 基础操作 
2.1 基本原理 
       我们使用一个下标范围比较大的数组来存储元素。可以设计一个函数(哈希函数, 也叫做散列函数),使得每个元素的关键字都与一个函数值(即数组下标)相对应,于是用这个数组单元来存储这个元素;也可以简单的理解为,按照关键字为每一 个元素“分类”,然后将这个元素存储在相应“类”所对应的地方。 
但是,不能够保证每个元素的关键字与函数值是一一对应的,因此极有可能出现对于不同的元素,却计算出了相同的函数值,这样就产生了“冲突”,换句话说,就是把不同的元素分在了相同的“类”之中。后面我们将看到一种解决“冲突”的简便做法。 
总的来说,“直接定址”与“解决冲突”是哈希表的两大特点。 

2.2 函数构造 
       构造函数的常用方法(下面为了叙述简洁,设 h(k) 表示关键字为 k 的元素所对应的函数值): 
a) 除余法: 
       选择一个适当的正整数 p ,令 h(k ) = k mod p ,这里, p 如果选取的是比较大的素数,效果比较好。而且此法非常容易实现,因此是最常用的方法。 
b) 数字选择法: 
       如果关键字的位数比较多,超过长整型范围而无法直接运算,可以选择其中数字分布比较均匀的若干位,所组成的新的值作为关键字或者直接作为函数值。 

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

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

[cpp] view plaincopy

 

 

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

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

[cpp] view plaincopy

 

 

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

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

[cpp] view plaincopy

 

 

  1. function locate(x:longint):integer;  
  2. var orig,i:integer;  
  3. begin  
  4. orig:=h(x);  
  5. i:=0;  
  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;   

插入元素 

[cpp] view plaincopy

 

 

  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;   

查找元素是否已经在表中 

[cpp] view plaincopy

 

 

  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% ,效果比较好(这个仅仅是经验,没有严格证明)。 

4. 应用举例 
4.1 应用的简单原则 
       什么时候适合应用哈希表呢?如果发现解决这个问题时经常要询问:“某个元素是否在已知集合中?”,也就是需要高效的数据存储和查找,则使用哈希表是最好不过的了!那么,在应用哈希表的过程中,值得注意的是什么呢? 
哈希函数的设计很重要。一个不好的哈希函数,就是指造成很多冲突的情况,从前面的例子已经可以看出来,解决冲突会浪费掉大量时间,因此我们的目标 就是尽力避免冲突。前面提到,在使用“除余法”的时候,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 的约数越多,发生这种余数分布不均匀的情况就越频繁,冲突的几率越高。而素数的约数是最少的,因此我们选用大素数。记住“素数是我们的得力助手”。 
       另一方面,一味的追求低冲突率也不好。理论上,是可以设计出一个几乎完美,几乎没有冲突的函数的。然而,这样做显然不值得,因为这样的函数设计 很浪费时间而且编码一定很复杂,与其花费这么大的精力去设计函数,还不如用一个虽然冲突多一些但是编码简单的函数。因此,函数还需要易于编码,即易于实 现。 
       综上所述,设计一个好的哈希函数是很关键的。而“好”的标准,就是较低的冲突率和易于实现。 
       另外,使用哈希表并不是记住了前面的基本操作就能以不变应万变的。有的时候,需要按照题目的要求对哈希表的结构作一些改进。往往一些简单的改进就可以带来巨大的方便。 
这些只是一般原则,真正遇到试题的时候实际情况千变万化,需要具体问题具体分析才行。

时间: 2024-09-27 16:12:26

哈希表工作原理的相关文章

路由选择表 工作原理

问题描述 我有一个关于路由选择表的问题.如下是一个路由选择表(routingtable)的例子:其实这是个网络课程练习题里的例子.例子中说,网络地址172.51.1.253match选择表中的第5行,而地址196.168.1.132不match任何一行.我问题是,为什么172.51.1.253match第5行?我试过如下运算,可是无法得出与第五行一样的networkaddress(172.32.0.0):172.51.1.253=10101100001100110000000111111101n

php内核解析:PHP中的哈希表_php技巧

PHP中使用最为频繁的数据类型非字符串和数组莫属,PHP比较容易上手也得益于非常灵活的数组类型. 在开始详细介绍这些数据类型之前有必要介绍一下哈希表(HashTable). 哈希表是PHP实现中尤为关键的数据结构. 哈希表在实践中使用的非常广泛,例如编译器通常会维护的一个符号表来保存标记,很多高级语言中也显式的支持哈希表. 哈希表通常提供查找(Search),插入(Insert),删除(Delete)等操作,这些操作在最坏的情况下和链表的性能一样为O(n). 不过通常并不会这么坏,合理设计的哈希

php如何在数组和哈希表上工作

在数组和哈希表上工作 在C语言中, 有两种不同的基础方法用来在一个结构体中存储任意数量的独立数据元素. 两种方法都有赞成者和反对者. 向量 Vs. 链表 应用的编写通常基于特定类型数据的特性的选择, 需要存储多少数据, 以及需要多快速度的检索. 为了能够有对等的认知, 我们先来看看简单的看看这些存储机制. 向量 向量是一块连续的内存空间, 它们包含的数据有规律的间隔. 向量最常见的例子就是字符串变量(char *或char []), 它包含了一个接着一个的字符(字节)序列. char foo[4

jsp中form表单的工作原理?

问题描述 jsp中form表单的工作原理? 1.为什么form表单可以提交到下个页面并且传值? 2.在form表单jsp页面转换成servlet页面后,里面只有一堆out.print().怎么不见跳转,或者传值类的东西? 求各位大神解惑,谢谢大家 解决方案 是HTML标签,是浏览器所识别的,所以浏览器会自动识别这个东西,知道是http请求服务器的,并通过tcp/ip协议传输,和servlet没有关系,jsp是可以在html中嵌套java代码并最终生成servlet的.

PHP哈希表碰撞攻击原理

最近哈希表碰撞攻击(Hashtable collisions as DOS attack)的话题不断被提起,各种语言纷纷中招.本文结合PHP内核源码,聊一聊这种攻击的原理及实现. 哈希表碰撞攻击的基本原理 哈希表是一种查找效率极高的数据结构,很多语言都在内部实现了哈希表.PHP中的哈希表是一种极为重要的数据结构,不但用于表示Array数据类型,还在Zend虚拟机内部用于存储上下文环境信息(执行上下文的变量及函数均使用哈希表结构存储). 理想情况下哈希表插入和查找操作的时间复杂度均为O(1),任何

PHP内核探索:哈希表碰撞攻击原理_php实例

下面通过图文并茂的方式给大家展示PHP内核探索:哈希表碰撞攻击原理. 最近哈希表碰撞攻击(Hashtable collisions as DOS attack)的话题不断被提起,各种语言纷纷中招.本文结合PHP内核源码,聊一聊这种攻击的原理及实现.  哈希表碰撞攻击的基本原理 哈希表是一种查找效率极高的数据结构,很多语言都在内部实现了哈希表.PHP中的哈希表是一种极为重要的数据结构,不但用于表示Array数据类型,还在Zend虚拟机内部用于存储上下文环境信息(执行上下文的变量及函数均使用哈希表结

Uber首席系统架构师Matt Ranney:可伸缩的软件系统工作原理

据报导,在短短四年间,Uber已经惊人地增长了38倍.现在,Uber的首席系统架构师Matt Ranney 在他的报告"可伸缩Uber实时市场平台"中,对Uber软件系统的工作原理进行了一个有趣而又详细的介绍. 如果你对Uber迅猛增长的单价感兴趣,这个并没有在报告中涉及.但是我们可以了解Uber的调度系统,怎样实行地理空间索引,怎样规划他们的系统,怎样实行高利用率和怎样处理失败,包括令人惊讶的方式处理数据中心故障,使用驱动的手机作为恢复外部分布式存储系统. 在Matt的报告中,给人印

浏览器的工作原理:新式网络浏览器幕后揭秘

英文原文地址:http://blog.csdn.net/wdzxl198/article/details/8992280:http://taligarsiel.com/Projects/howbrowserswork1.htm#Resources: 转载源地址:http://www.html5rocks.com/zh/tutorials/internals/howbrowserswork/: 序言 这是一篇全面介绍 Webkit 和 Gecko 内部操作的入门文章,是以色列开发人员塔利·加希尔大

浏览器的工作原理:新式网络浏览器幕后揭秘{转}

//我是 "转"的~这么大牛的文章, 我会慢慢理解和回味~ http://taligarsiel.com/Projects/howbrowserswork1.htm http://www.html5rocks.com/zh/tutorials/internals/howbrowserswork/ 浏览器的渲染原理简介 Introduction The browsers we will talk about The browser's main functionality The bro