首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

从头到尾彻底解析Hash表算法(转)(2)

从头到尾彻底解析Hash表算法(转)(2)

元素特征转变为数组下标的方法就是散列法。散列法当然不止一种,下面列出三种比较常用的:  1,除法散列法
  最直观的一种,上图使用的就是这种散列法,公式:
      index = value % 16
  学过汇编的都知道,求模数其实是通过一个除法运算得到的,所以叫“除法散列法”。
  2,平方散列法
  求index是非常频繁的操作,而乘法的运算要比除法来得省时(对现在的CPU来说,估计我们感觉不出来),所以我们考虑把除法换成乘法和一个位移操作。公式:
      index = (value * value) >> 28   右移,除以2^28。记法:左移变大,是乘。右移变小,是除。
  如果数值分配比较均匀的话这种方法能得到不错的结果,但我上面画的那个图的各个元素的值算出来的index都是0——非常失败。也许你还有个问题,value如果很大,value * value不会溢出吗?答案是会的,但我们这个乘法不关心溢出,因为我们根本不是为了获取相乘结果,而是为了获取index。
  3,斐波那契(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
  如果用这种斐波那契散列法的话,那上面的图就变成这样了:


  很明显,用斐波那契散列法调整之后要比原来的取摸散列法好很多。
  适用范围
  快速查找,删除的基本数据结构,通常需要总数据量可以放入内存。
  基本原理及要点
  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 表在海量数据处理中有着广泛的应用,下面,请看另一道百度面试题:
  题目:海量日志数据,提取出某日访问百度次数最多的那个IP。
  方案:IP的数目还是有限的,最多2^32个,所以可以考虑使用hash将IP直接存入内存,然后进行统计。
  第三部分、最快的Hash表算法
  接下来,咱们来具体分析一下一个最快的Hash表算法。
  我们由一个简单的问题逐步入手:有一个庞大的字符串数组,然后给你一个单独的字符串,让你从这个数组中查找是否有这个字符串并找到它,你会怎么做?有一个方法最简单,老老实实从头查到尾,一个一个比较,直到找到为止,我想只要学过程序设计的人都能把这样一个程序作出来,但要是有程序员把这样的程序交给用户,我只能用无语来评价,或许它真的能工作,但...也只能如此了。
  最合适的算法自然是使用HashTable(哈希表),先介绍介绍其中的基本知识,所谓Hash,一般是一个整数,通过某种算法,可以把一个字符串"压缩" 成一个整数。当然,无论如何,一个32位整数是无法对应回一个字符串的,但在程序中,两个字符串计算出的Hash值相等的可能非常小,下面看看在MPQ中的Hash算法:
  函数一、以下的函数生成一个长度为0x500(合10进制数:1280)的cryptTable[0x500]
void prepareCryptTable(){     unsigned long seed = 0x00100001, index1 = 0, index2 = 0, i;    for( index1 = 0; index1 < 0x100; index1++ )    {         for( index2 = index1, i = 0; i < 5; i++, index2 += 0x100 )        {             unsigned long temp1, temp2;            seed = (seed * 125 + 3) % 0x2AAAAB;            temp1 = (seed & 0xFFFF) << 0x10;             seed = (seed * 125 + 3) % 0x2AAAAB;            temp2 = (seed & 0xFFFF);             cryptTable[index2] = ( temp1 | temp2 );        }    }}
  函数二、以下函数计算lpszFileName字符串的hash值,其中dwHashType为hash的类型(在下面的函数三GetHashTablePos函数中调用此函数二),其可以取的值为0、1、2;该函数返回lpszFileName 字符串的hash值:
unsigned long HashString( char *lpszFileName, unsigned long dwHashType ){     unsigned char *key  = (unsigned char *)lpszFileName;    unsigned long seed1 = 0x7FED7FED;    unsigned long seed2 = 0xEEEEEEEE;    int ch;    while(*key != 0)    {         ch = toupper(*key++);        seed1 = cryptTable[(dwHashType << 8) + ch] ^ (seed1 + seed2);        seed2 = ch + seed1 + seed2 + (seed2 << 5) + 3;     }    return seed1; }
  Blizzard的这个算法是非常高效的,被称为"One-Way Hash"(A one-way hash is a an algorithm that is constructed in such a way that deriving the original string (set of strings, actually) is virtually impossible)。举个例子,字符串"unitneutralacritter.grp"通过这个算法得到的结果是0xA26067F3。
继承事业,薪火相传
返回列表