字符串Hash散列表设计

转载 2007年10月14日 22:23:00
原贴:http://wangfulong888.spaces.live.com/Blog/cns!1prHQ8LyFhaJkVAwBy_o8DHw!126.entry

                字符串Hash散列表设计
                                             By WangFL

一、常见字符串hash函数
1、ASCII码相加
基本思想:将字符串的每一个字符的ASCII码相加,得出的值即为hash函数值。
代码举例:
    num:=0;
    for I:=1 to length(s) do inc(num,ord(s[I]));
冲突:例如’abc’与’bca’等元素相同而排列不同的字符串,显然会造成冲突。
改进:可以将字符的ASCII码乘上一个位权,然后再相加,这样可以避免一部分冲突。还可采取另外类似的附加权值方法。
代码举例:
    num:=0;
    for I:=1 to length(s) do inc(num,ord(s[I])*I);
2、ElfHash函数
基本思想:利用位运算,产生一个hash函数值。
代码举例:
    num:=0;
    for I:=1 to length(s) do begin
      num:=(num shl 4)+ord(s[I]);
      g:=num and ($f0000000);
      if g>0 then num:=num xor (g shr 24);
      num:=num and (not g);
    end;
优点:压缩极为密集,是一个很好用的产生冲突极少的函数。
压缩方法
经过处理之后生成的hash函数可能比较零散,或者跨越区间比较大。此时可以采取生成整型数hash函数的经典方法。比如可以将生成的hash函数mod一个数而产生新的hash函数值。但这样会使产生冲突的概率增加。
冲突的解决
(1)二维数组静态拉链(自己第一次用,发明的垃圾算法)
基本思想:
生成hash:计算hash值后,将未被改变的hash赋为true,并将字符串存入list[对应hash值]中。
查找:读入关键字,如果对应的hash值为true,则在list[对应hash值]中进行查找。
代码举例:
    list:array[1..maxn,1..maxl] of string;
    hash:array[1..maxn] of boolean; (maxn为hash表长度,maxl为估计最大冲突个数)
缺点:浪费大量空间且紧缩时无法准确把握限度,效果不大。
2、动态链表拉链
基本思想:
对于每一个hash地址,产生一个链表存储冲突的字符串。
   优点:可动态安排存储冲突的空间,防止空间浪费。
   缺点:属于动态数据结构的操作,对于不熟悉的操作者容易造成失误。
3、静态一维数组模拟链表
基本思想:类似于动态链表拉链法。
代码举例:
    list:array[1..maxn] of string;
    next:array[1..maxn] of integer;
    hash:array[1..maxn] of integer; (maxn为hash表长度)
    其中,list记录所有的字符串。Hash中存储的为每个hash函数值所对应的第一个字符串的存储地址。Next(i)表示字符串list(I)的后继字符串为next(I)。经过next连接的所有字符串均为冲突字符串。
二、程序举例
[文明的复兴(From TJU)]
Problem
战 神Prince&Gush回归了,但许多原先的上层精灵越来越不安分。他们无法忍受失去权力的空虚感,开始重新寻找新的途径获取权利。他们直率急 躁的领导人King_Bette开始公开抨击权威,并散布谣言。 权利的统治需要统一,需要强硬,被逼无奈下正义的领袖开始收缴反动的资料,清除世界的毒瘤,借以踏上快速发展之路。
不良信息指的是一组单词,每个单词均为不良信息。
不良信息文本是指包含一系列的单词,且其中包含有不良信息。发布信息者经常在单词中加些字母以外的字符以搅乱正义的视线,于是Prince想请你为他写一个能够将这些不良信息屏蔽掉的工具。但是为了尽量降低误删率,他提出了下面一个要求
你只需要将字母完全匹配的单词屏蔽掉即可。
例如:
sex为不良信息时,
sex8,sex$,se#x均为不良信息
sexx 则不属于不良信息.
Input
第一行为一整数n,表示有n组测试数据。
每组测试数据第一行为一个正数k <= 10000,表示有k个需要被屏蔽的词语,均为小写字母。
以下k行每行一个单词。
最后一行为输入需要处理的文本(文本长度<=100000),单词与单词之间空格分开且所有字母为小写字母。
Output
每组测试数据输出一行和输入格式一致的文本,被屏蔽的单词的字母以*代替原字母。
Sample Input
2
1
sex
sex sex8 sex$ s&&ex sexx aaa bbb
2
sex
sexy
sex sexy&
Sample Output
*** ***8 ***$ *&&** sexx aaa bbb
*** ****&
    粗略分析题目,可知本题的意思就是输入一些“不良信息”的单词,建立一个等待查找的数据结构,然后对于随后输入的每个单词,去除除了字母之外的无关信息之外,在建立好的查找表中进行查找。如果找到,则将该单词中所有的字母改变为’*’即可。
1、直接顺序查找
    最简单的思路就是存储所有不良信息于一个线性表,然后每读入一个单词,就遍历一遍这个线性表。假设不良信息个数为n,单词的个数为m,则时间复杂度为O (m*n)(仅考虑查找算法,而不考虑字符串的处理)。由于n<=10000,m<=100000,时间复杂度上限O(10^9),显然是要 超时的。而且经过代码验证,确实超时(-_-):
program Information;
const
  maxn=10000;
var
  hash:array[1..maxn] of ansistring;
  len,n,inp,data:integer;
  h,g,i,j:longint;
  s,st:ansistring;
  c:char;
function judge(x:ansistring):boolean;
  var
    ii:integer;
  begin
    judge:=false;
    for ii:=1 to len do
      if hash[ii]=x then begin
         judge:=true;exit;
      end;
  end;
begin
  readln(data);
  for inp:=1 to data do begin
    readln(n);len:=0;
    for i:=1 to n do begin
      readln(s);
      inc(len);
      hash[len]:=s;
    end;
    readln(s);h:=1;st:='';
    for i:=1 to length(s) do begin
      if s[i]<>' ' then begin
         if (ord(s[i])>=97)and(ord(s[i])<=122) then st:=st+s[i];
      end;
      if (s[i]=' ')or(i=length(s)) then begin
        if judge(st) then
           for j:=h to i do
             if (ord(s[i])>=97)and(ord(s[i])<=122) then s[j]:='*';
        h:=i;st:='';
      end;
    end;
    writeln(s);
  end;
end.
2、快速排序+二分查找
3、使用堆结构或Trie结构
4、字符串Hash
    在查找算法中,Hash的算法为最高。每次查找只需要O(1)的时间。对于这道题,时间复杂度可以粗略的估计为O(m+n)。但是Hash的一个弊端是容 易产生冲突。虽然ElfHash函数产生冲突的概率极小,但也是不可避免的。所以,这道题如果使用hash表,就必须处理冲突。我第一次看这题,首先映射 的就是hash。马上编了一个不处理冲突的采用ASCII码相加产生hash函数的方法的hash查找,并逐渐加大hash表的容量,结果还是1ms就 WA掉。随后加以改进,采用了ElfHash函数(仍然不处理冲突),到29ms的时候WA。可见,冲突是不可避免的。而处理冲突适宜运用拉链法:
program Information;
const
  maxn=40000;
var
  list:array[1..maxn] of ansistring;
  next:array[1..maxn] of integer;
  hash:array[1..maxn] of integer;
  loc,n,inp,data,len:integer;
  num,h,g,i,j:longint;
  s,st:ansistring;
  c:char;
  d:boolean;
begin
  readln(data);
  for inp:=1 to data do begin
    readln(n);loc:=0;
    fillchar(hash,sizeof(hash),0);
    fillchar(next,sizeof(next),0);
    for i:=1 to n do begin
      readln(s);
      num:=0;
      for j:=1 to length(s) do begin
        num:=(num shl 4)+ord(s[j]);
        g:=num and ($f0000000);
        if g>0 then num:=num xor (g shr 24);
        num:=num and (not g);
      end;
      if hash[num mod maxn]=0 then begin
         inc(loc);
         hash[num mod maxn]:=loc;
         list[loc]:=s;
      end
      else begin
        j:=hash[num mod maxn];
        while next[j]<>0 do j:=next[j];
        inc(loc);
        list[loc]:=s;
        next[j]:=loc;
      end;
    end;
    readln(s);h:=1;num:=0;st:='';
    for i:=1 to length(s) do begin
      if s[i]<>' ' then begin
         if (ord(s[i])>=97)and(ord(s[i])<=122) then begin
            st:=st+s[i];
            num:=(num shl 4)+ord(s[i]);
            g:=num and ($f0000000);
            if g>0 then num:=num xor (g shr 24);
            num:=num and (not g);
         end;
      end;
      if (s[i]=' ')or(i=length(s)) then begin
        if hash[num mod maxn]<>0 then begin
           d:=false;j:=hash[num mod maxn];
           repeat
             if list[j]=st then begin
                d:=true;break;
             end;
             j:=next[j];
           until next[j]=0;
           if d then
             for j:=h to i do
               if (ord(s[i])>=97)and(ord(s[i])<=122) then s[j]:='*';
        end;
        h:=i;num:=0;st:='';
      end;
    end;
    writeln(s);
  end;
end.
     这个程序用的是静态数组模拟链表进行的拉链。我曾经用的是第一种二维数组的拉链法,自己在fp下测试没有问题。但到了toj上面就Re216,可能是这样占用的空间太大,而toj对数组大小的要求比较严格。
四、小结
    运用Hash表是时间效率最高的查找算法(基本可以说是用空间换的),但每个hash函数都不可能是完美无缺的。尤其对于数据范围比较大的题,我们必须找 到一个最合适的hash函数。对于字符串来说,ElfHash函数无疑是最合适的选择(是否有更好的hash函数,当然还要因题而异)。
    但是,如果处理冲突比较繁琐,或者hash表+冲突处理所占用的空间超过了题目限制,就不如改用其它的数据结构(例如堆、Trie、平衡二叉树等)了。总 之,应对类似类型的题目,都要学会合理将时间与空间分配均匀、达到最佳效果。无论是采用哪种数据结构,(引用一下jyy的名言)AC就是硬道理。
 
        参考文献:李羽修冬令营论文
 

String类

-
  • 1970年01月01日 08:00

散列表之完美散列

散列表之完美散列 完美散列perfect hashing 两级散列法 gperf工具来自动生成完美散列函数 gperf的安装 gperf关键字文件的书写格式 gperf应用举例 注意本文中的所有代码你...
  • ii1245712564
  • ii1245712564
  • 2015-07-27 11:56:54
  • 1223

hash实例讲解

from http://blog.jobbole.com/49229/ 说明:本文分为三部分内容,第一部分为一道百度面试题Top K算法的详解;第二部分为关于Hash表算法的详细阐述;第三部分为打造...
  • basycia
  • basycia
  • 2016-07-11 16:33:27
  • 3884

散列表的原理与实现

散列表的原理与实现 absfree ...
  • TuxedoLinux
  • TuxedoLinux
  • 2018-04-17 14:43:00
  • 10

Java String的hashCode实现

String类中的HashCode实现函数: /** * Returns a hash code for this string. The hash code for a ...
  • woliuyunyicai
  • woliuyunyicai
  • 2015-09-16 09:44:17
  • 906

散列表总结

如果要转载,需要注明出处: http://blog.csdn.net/xiazdong 本文整理自《算法导论》第11章,由于本章有一些概率论知识,因此理解起来比较...
  • xiazdong
  • xiazdong
  • 2013-01-31 15:02:49
  • 7098

哈希表对字符串的高效处理

哈希表对字符串的高效处理         哈希表(散列表)是一种非常高效的查找数据结构,在原理上也与其他的查找不尽相同,它回避了关键字之间反复比较的繁琐,而是直接一步到位查找结果。当然,这也带来了记录...
  • wojiushiwo987
  • wojiushiwo987
  • 2012-09-27 21:45:51
  • 8885

数组、字符串、向量与<em>哈希表</em>

数组在Java中频繁使用,想到重要包装类理解<em>String</em>类和<em>String</em>Buffer类向量与<em>哈希表</em>... 数组在Java中频繁使用,想到重要包装类理解<em>String</em>类和<em>String</em>Buffer类向量与<em>哈希表</em> ...
  • 2018年04月14日 00:00

【数据结构】回顾散列表

1.散列表(hash table)的实现成为散列(hashing),是一种以常数平均时间执行输入、删除和查找的技术。但是那些需要元素间任何排序信息的数操作将不会得到有效的支持。2.散列函数示例int ...
  • NoMasp
  • NoMasp
  • 2015-05-09 17:31:13
  • 1540

数据结构与算法——散列表类的C++实现(分离链接散列表)

散列表类的C++实现(分离链接散列表)
  • Linux_ever
  • Linux_ever
  • 2016-04-12 15:30:56
  • 4195
收藏助手
不良信息举报
您举报文章:字符串Hash散列表设计
举报原因:
原因补充:

(最多只允许输入30个字)