1、浅谈竞赛中哈希表的应用第 1 页 共 27 页浅谈竞赛中哈希表的应用哈尔滨市第三中学 刘翀关键词 应用 哈希表 数据结构 摘要 哈希表是一种高效的数据结构。本文分五个部分:首先提出了哈希表的优点,其次介绍了它的基础操作,接着从简单的例子中作了效率对比,指出其适用范围以及特点,然后通过例子说明了如何在题目中运用哈希表以及需要注意的问题,最后总结全文。正文1. 引言哈希表(Hash Table)的应用近两年才在 NOI 中出现,作为一种高效的数据结构,它正在竞赛中发挥着越来越重要的作用。 哈希表最大的优点,就是把数据的存储和查找消耗的时间大大降低,几乎可以看成是常数时间;而代价仅仅是消耗比较多的内
2、存。然而在当前可利用内存越来越多的情况下,用空间换时间的做法是值得的。另外,编码比较容易也是它的特点之一。哈希表又叫做散列表,分为“开散列” 和“闭散列” 。考虑到竞赛时多数人通常避免使用动态存储结构,本文中的“哈希表”仅指“闭散列” ,关于其他方面读者可参阅其他书籍。2. 基础操作2.1基本原理我们使用一个下标范围比较大的数组来存储元素。可以设计一个函浅谈竞赛中哈希表的应用第 2 页 共 27 页数(哈希函数, 也叫做散列函数) ,使得每个元素的关键字都与一个函数值(即数组下标)相对应,于是用这个数组单元来存储这个元素;也可以简单的理解为,按照关键字为每一个元素“分类” ,然后将这个元素存储
3、在相应“类”所对应的地方。但是,不能够保证每个元素的关键字与函数值是一一对应的,因此极有可能出现对于不同的元素,却计算出了相同的函数值,这样就产生了“冲突” ,换句话说,就是把不同的元素分在了相同的“类”之中。后面我们将看到一种解决“冲突”的简便做法。总的来说, “直接定址”与“解决冲突”是哈希表的两大特点。2.2函数构造构造函数的常用方法(下面为了叙述简洁,设 h(k) 表示关键字为 k 的元素所对应的函数值):a) 除余法:选择一个适当的正整数 p ,令 h(k ) = k mod p 这里, p 如果选取的是比较大的素数,效果比较好。而且此法非常容易实现,因此是最常用的方法。b) 数字选
4、择法:如果关键字的位数比较多,超过长整型范围而无法直接运算,可以选择其中数字分布比较均匀的若干位,所组成的新的值作为关键字或者直接作为函数值。2.3冲突处理线性重新散列技术易于实现且可以较好的达到目的。令数组元素个数为 S ,则当 h(k) 已经存储了元素的时候,依次探查 (h(k)+i) mod S , i=1,2,3 ,直到找到空的存储单元为止(或者从头到尾扫描一圈仍未发现空单元,这就是哈希表已经满了,发生了错误。当然这是可以通过扩大数组范围避免的) 。2.4支持运算哈希表支持的运算主要有:初始化(makenull)、哈希函数值的运算(h(x)、插入元素(insert)、查找元素(memb
5、er)。设插入的元素的关键字为 x ,A 为存储的数组。初始化比较容易,例如const empty=maxlongint; / 用非常大的整数代表这个位置没有存储元素p=9997; / 表的大小procedure makenull;var i:integer;beginfor i:=0 to p-1 doAi:=empty;End;浅谈竞赛中哈希表的应用第 3 页 共 27 页哈希函数值的运算根据函数的不同而变化,例如除余法的一个例子:function h(x:longint):Integer;beginh:= x mod p;end;我们注意到,插入和查找首先都需要对这个元素定位,即如果这个
6、元素若存在,它应该存储在什么位置,因此加入一个定位的函数 locate function locate(x:longint):integer;var orig,i:integer;beginorig:=h(x);i:=0;while (ix)and(A(orig+i)mod S 5000,但是 262 1 then begintmp:=tmp*27+ord(s1)-64;for i:=1 downto 0 dotmp:=tmp*27+ord(slength(s)-i)-64; 取第一位和后两位end浅谈竞赛中哈希表的应用第 8 页 共 27 页else for i:=1 to 3 dotmp:
7、=tmp*27+ord(s1)-64;当长度为 1 的时候特殊处理hash:=tmp mod 13883;end;值得指出的是,本题给出的字符串大都没有什么规律,用哈希表可以做到近似“平均” ,但是对于大多数情况,字符串是有规律的(例如英文单词) ,这个时候用哈希表反而不好(例如英语中有很多以 con 开头的单词) ,通常用检索树解决这样的查找问题。4.3 在广度优先搜索中应用的例子在广度优先搜索中,一个通用而且有效的剪枝就是在拓展节点之前先判重。而判重的本质也是数据的存储与查找,因此哈希表大有用武之地。来看下面的例子:转花盆题意描述:给定两个正 6 边形的花坛,要求求出从第一个变化到第二个的
8、最小操作次数以及操作方式。一次操作是:选定不在边上的一盆花,将这盆花周围的 6 盆花按照顺时针或者逆时针的顺序依次移动一个单位。限定一个花坛里摆放的不同种类的花不超过 3 种,对于任意两种花,数量多的花的盆数至少是数量少的花的 2 倍 这是 SGOI-8 的一道题分析: 首先确定本题可以用广度优先搜索处理,然后来看问题的规模。正 6 边形共有 19 个格子可以用来放花,而且根据最后一句限定条件,至多只能存在 C(2,19) * C(5,17) = 1058148 种状态,用搜索完全可行。然而操作的时候,可以预料产生的重复节点是相当多的,需要迅速判重才能在限定时间内出解,因此想到了哈希表。那么这
9、个哈希函数如何设计呢?注意到 19 个格子组成 6 边形是有顺序的,而且每一个格子只有 3 种可能情况,那么用 3 进制 19 位数最大 320-1=3486784400 用 Cardinal 完全可以承受。于是我们将每一个状态与一个整数对应起来,使用除余法就可以了。4.4 小结从这两个例子可以发现,对于字符串的查找,哈希表虽然不是最好的方法,但是每个字符都有“天生”的 ASCII 码,在设计哈希函数的时候可以直接利用。而其他方法,例如利用检索树的查找,编写代码不如哈希表简洁。至于广度优先搜索中的判重更是直接利用了哈希表的特点。浅谈竞赛中哈希表的应用第 9 页 共 27 页另外,我们看到这两个
10、题目都是设计好哈希函数之后,直接利用前面的基本操作就可以了,因此重点应该是在哈希函数的设计上(尽管这两个例子的设计都很简单) ,需要注意题目本身可以利用的条件,以及估计值域的范围。下面我们看两个需要在哈希表基础上作一些变化的例子。4.5 需要微小变化的例子下面,我们来分析一道 NOI 的试题:=方程的解数问题描述已知一个 n 元高次方程:12. 0npppkxkx其中:x 1, x2, ,xn 是未知数,k 1,k2,kn 是系数,p 1,p2,pn 是指数。且方程中的所有数均为整数。假设未知数 1 xi M, i=1,n,求这个方程的整数解的个数。约束条件1n6;1M 150;2 31.2n
11、pppkkM方程的整数解的个数小于 231。本题中,指数 Pi(i=1,2,n)均为正整数。这个是 NOI 2001 的第二试中的方程的解数 。分析: 初看此题,题目要求出给定的方程解的个数,这个方程在最坏的情况下可以有 6 个未知数,而且次数由输入决定。这样就不能利用数学方法直接求出解的个数,而且注意到解的范围最多 150 个数,因此恐怕只能使用枚举法了。最简单的思路是穷举所有未知数的取值,这样时间复杂度是 O(M6) ,无法承受。因此我们需要寻找更好的方法,自然想到能否缩小枚举的范围呢?但是发现这样也有很大的困难。我们再次注意到 M 的范围,若想不超时,似乎算法的复杂度上限应该是 O(M3
12、) 左右,这是因为 1503 0)and(ea,(t+i)mod p,1rc)or(ea,(t+i)mod p,30)and(indextmp=value;repeatdec(j);until dajj then locate:=jelse locate:=j-1;exit;end;end;end;beginif mmid)and(ex) dobegin浅谈竞赛中哈希表的应用第 16 页 共 27 页if x=damid then beginmember:=true;exit;end;if x1 then begintmp:=tmp*27+ord(s1)-64;for i:=1 downto
13、0 dotmp:=tmp*27+ord(slength(s)-i)-64;endelse for i:=1 to 3 dotmp:=tmp*27+ord(s1)-64;hash:=tmp mod 13883;end;function locate(s:string):integer;var tmp,i:integer;begintmp:=hash(s);i:=0;while (index(i+tmp)mod 13883empty) doi:=(i+23)mod 13883;locate:=(i+tmp)mod 13883;end;procedure int(s:string);var tmp:
14、integer;begintmp:=locate(s);indextmp:=s;end;procedure init;var s:string;i:integer;浅谈竞赛中哈希表的应用第 18 页 共 27 页beginassign(fin,d:namenum.txtnamenum.in);assign(fout,d:namenum.outnamenum.out);reset(fin);rewrite(fout);assign(dict,d:dict1.txtdict.txt);reset(dict);for i:=0 to 13882 doindexi:=empty;while not e
15、of(dict) dobeginreadln(dict,s);int(s);end;close(dict);readln(fin,quest);close(fin);end;function member(s:string):boolean;var tmp:integer;begintmp:=locate(s);if indextmp=s then member:=trueelse member:=false;end;procedure work;var st:string;j:integer;procedure examin(t:integer;ch:string);var i:intege
16、r;beginif t=length(quest) then beginst:=st+ch;if member(st) then beginwriteln(fout,st);check:=true;end;exit;end;st:=st+ch;for i:=1 to 3 dobeginexamin(t+1,valueord(questt+1)-ord(0),i);delete(st,length(st),1);浅谈竞赛中哈希表的应用第 19 页 共 27 页end;end;begincheck:=false;for j:=1 to 3 dobeginst:=;examin(1,valueord
17、(quest1)-ord(0),j);end;if not check then writeln(fout,NONE);close(fout);end;begininit;work;end.4 转花盆的程序 (这个程序是 SGOI-8 Flowers 的标准程序)program flowers;constsize=1058148;base=262143;circle:array17,16 of longint=(1,2,6,10,9,4),(2,3,7,11,10,5),(4,5,10,14,13,8),(5,6,11,15,14,9),(6,7,12,16,15,10),(9,10,15,1
18、8,17,13),(10,11,16,19,18,14);x:array17 of longint=(2,2,3,3,3,4,4);y:array17 of longint=(2,3,2,3,4,2,3);InputFn=flowers.in;OutputFn=flowers.out;varlast,next,q:array1size of longint;id:array1size of shortint;hash:array0base of longint;step,i,j,k,start,target,qs,l,r:longint;bit,s,t:array119 of longint;
19、d:array07 of longint;nowlast,nowid:longint;f,fo:text;procedure init;浅谈竞赛中哈希表的应用第 20 页 共 27 页vard:array05 of longint;i,j:longint;beginassign(f,InputFn);reset(f);for i:=1 to 19 doread(f,si);for i:=1 to 19 doread(f,ti);close(f);d0:=0;for i:=1 to 19 dobegininc(d0); dd0:=si;for j:=1 to d0 doif dj=si then
20、 break;si:=j-1;if jd0 then dec(d0);end;fillchar(next,sizeof(next),0);fillchar(hash,sizeof(hash),0);end;function change(a,b:longint; plus:longint):longint;vari:longint;beginfor i:=1 to 6 dodi:=(a div bitcircleb,i) mod 3;d7:=d1; d0:=d6;for i:=1 to 6 doa:=a+(di+plus-di)*bitcircleb,i;change:=a;end;proce
21、dure out;var浅谈竞赛中哈希表的应用第 21 页 共 27 页i,j,dep:longint;stack:array120 of longint;begini:=qs; dep:=0;while i0 thenwriteln(fo,xstacki, ,ystacki, ,1)else writeln(fo,x-stacki, ,y-stacki, ,0);end;procedure insert(now:longint);vari:longint;beginif now=target thenbeginassign(fo,OutputFn);rewrite(fo);writeln(f
22、o,step);inc(qs); qqs:=now;lastqs:=nowlast; idqs:=nowid;out;close(fo);halt;end;i:=now and base;if hashi=0 thenbegininc(qs); qqs:=now;lastqs:=nowlast; idqs:=nowid;hashi:=qs;endelsebegini:=hashi;while nexti0)and(e(t+i)mod max,10)and(ea,(t+i)mod p,1rc)or(ea,(t+i)mod p,3rdatai,2 then beginfor j:=2 to 4 doedatai,1,t,j-1:=rdatai,j;edatai,1,t,4:=i;endelse begindec(ans);ri:=edatai,1,t,4;for j:=1 to i-1 dofor k:=2 to 4 doif dataj,k=i thendataj,k:=edatai,1,t,4;end;end;end;procedure out;beginwriteln(fout,ans);close(fout);end;begininit;main;浅谈竞赛中哈希表的应用第 27 页 共 27 页out;end.