哈希表原理简介
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
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 (ix)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 应用的简单原则
什么时候适合应用哈希表呢?如果发现解决这个问题时经常要询问:“某个元素是否在已知集合中?”,也就是需要高效的数据存储和查找,则使用哈希表是最好不过的