1 常用的构造hash函数的方法

1.1 直接定址法

  1、原理:取关键字或关键字的某个线性函数值为哈希值。、
  2、公式:H(key)=key或H(key)=a*key+b
  3、适合查找表较小且连续的情况
  4、优点:简单、均匀,不会产生冲突
  5、缺点:需要知道关键字的分布,现实中不常用

1.2 数字分析法

  1、原理:抽取关键字中的一部分来计算存储位置。假设关键字是以r为基(如:以10为基的十进制),并且哈希表中可能出现的关键字都是事先知道的,则可取关键字的若干数位组成哈希地址。
  2、适用于关键字较长的情况。

1.3 平方取中法

  1、原理:取关键字平方后的中间几位作为哈希地址。通常在选定哈希函数时不一定能知道关键字的全部情况,取其中哪几位也不一定合适,而一个数平方后的中间几位和数的每一位都相关,由此使得随机分布的关键字得到的哈希地址也是随机的。
  2、适用于不知道关键词分布,且位数不长的情况。比较常用。

1.4 折叠法

  1、原理:将关键字分割成位数相同的几部分(最后一部分的位数可以不同),然后取这几部分的叠加和(舍去进位)作为哈希地址。
  2、适用于不知道关键字分布情况,且位数很多,关键字的每一位上数字分布大致均匀时。
  3、在折叠法中数位叠加可以有移位叠加间界叠加两种方法。
  1)移位叠加:将分割后的每一部分的最低位对齐,然后相加;
  2)间界叠加:从一端向另一端沿分割界来回折叠,然后对齐相加。
  例如:每一种西文图书都有一个国际标准图书编号,它是一个10位的十进制数字,若要以它作为关键字建立一个哈希表,在馆藏图书不到10000时,可以采用折叠法构造一个四位的哈希函数。如国际标准图书编号0-442-20586-4的哈希地址分别如下图(a)(b).
      

1.5除留余数法

  1、原理:取关键字被某个不大于哈希表长m的数p除后所得的余数为哈希地址。即H(key)=key MOD p,p<=m
  2、这是最简单、最常用的一种方法。不仅可以对关键字直接取模,还可以在折叠、平方取中后取模。
  3、p取小于等于m的最小质数或者不包含小于20的质因数的合数,以减少冲突的情况。

1.6 随机数法

  1、原理:选择一个随机函数,取关键字的随机函数值作为它的哈希地址。即H(key)=random(key),其中random为随机函数。
  2、适用于关键字长度不等的情况。

  综上,实际工作中选择哈希函数的时候,要考虑的因素有:
  1)计算哈希函数所需时间;
  2)关键字的长度;
  3)哈希表的大小;
  4)关键字的分布情况;
  5)记录的查找频率。

2 常用的冲突处理方法

2.1 开放定址法

  1、原理:
其中:f(key)是哈希函数,m为哈希表表长,di为增量序列,可有以下三种取法:
  1)di = 1,2,3,…,m-1,称线性探测再序列;
    一旦发生冲突,就寻找下一个空的散列地址。只要哈希表未填满,总能找到一个不发生冲突的地址。
  2)(k<=m/2),称二次探测再序列;
    目的是不让关键字集中在某块区域,产生堆积。只有在哈希表长为形如4j+3(j为整数)的素数时,才有可能一定能找到一个不冲突的地址。
  3)di=伪随机数序列,称伪随机探测再散列。
    但查询时需要设置和插入时相同的随机种子。

2.2 再哈希法

  1、原理: RHi均是不同的哈希函数,即在同义词产生地址冲突时计算另一个哈希函数的地址,知道冲突不再发生。
  2、这种方法不易产生“聚集”,但是增加了计算的时间。

2.3 链地址法

  1、原理:将所有关键字为同义词的记录存储在同一线性链表中,在散列表中只存储所有同义词表的头指针。
  2、假设某哈希函数产生的哈希值在区间[0,m-1]上,则设立一个指针型向量Chain ChainHash[m]。其每个分享的初始状态都是空指针。凡哈希地址为i的记录都插入到头指针为ChainHash[i]的链表中。在链表中的插入位置可以在表头或表尾,也可以在中间。
  例如:已知一组关键字为(19,14,23,01,68,20,84,27,55,11,10,79),则按H(key)=key MOD 13和链地址法处理冲突构造所得的哈希表如下:

2.4 建立一个公共溢出区

  1、原理:为所有冲突的关键字开辟一个公共的溢出区来存放。
  假设哈希函数的值域为[0,m-1],则设向量HashTable[0..m-1]为基本表,每个分量存放一个记录,另设向量OverTable[0..v]为溢出表。所有关键字和基本表中关键字为同义词的记录,不管它们由哈希函数得到的哈希地址是什么,一旦发生冲突,都填入溢出表。
  2、适用于相对于基本表来说冲突数据较少的情况。

3 哈希表的查找

//--------开放定址哈希表的存储结构-------
    int hashsize[] = {997,...};        //哈希表容量递增表,一个合适的素数序列
    typdef struct{
            ElemType *elem;               //数据元素存储基址,动态分配地址
            int count;                           //当前数据元素个数
            int sizeindex;                     //hashsize[sizeindex]为当前容量
    }HashTable;

    #define SUCCESS 1
    #define UNSUCCESS 0
    #define DUPLICATE -1

    Status SearchHash(HashTable H,KeyType K,int &c){
            //在开放定址哈希表H中,查找关键码为K的元素,若查找成功,以p指示待查数据元素
            //在表中的位置,并返回SUCCESS;否则,以p指示插入位置,并返回c用以计冲突次数
            //其初值为零,供建表插入时参考
            p = Hash(K)                     //求哈希地址
            while(H.elem[p].key != NULLKEY 
                                    && !EQ(K,H.elem[p].key))   //该位置中填有记录并且关键字不相等
                    collision(p,++c);                        //求得下一个探测地址
            if EQ(K,H.elem[p].key)
                    return SUCCESS;                       //查找成功,p返回待查数据元素地址
            else
                return UNSUCCESS;                  //查找不成功,H.elem[p].key == NULLKEY;
    }//SearchHash

    Status InsertHash(HashTable &H,Elemtype e){
            //查找不成功时,插入数据元素e到开放定制哈希表中,并返回OK,若冲突次数过大,
            //则重建哈希表
            c = 0;
            if(SearchHash(H,e.key,p,c))
                    return DUPLICATE;                                     //表中已有与e有相同关键字的元素
          else if(c < hashsize[H.sizeinde] / 2){                 //冲突次数c未达到上限,(c的阈值可调)
                H.elem[p] = e; ++H.count; return OK;
          }
            else{
                    RecreateHashTable(H); return UNSUCCESS;    //重建哈希表
            }   
    }

  在一般情况下,处理冲突方法相同的哈希表,其平均查找长度依赖于哈希表的装填因子
  哈希表的装填因子定义为:
    α = 表中填入的记录数/哈希表的长度
  阿尔法标志着哈希表的装满程度。α越小,发生冲突的的可能性就越小。
线性探测再散列的哈希表查找成功时的平均查找长度为:Snl ≈ 1/2( 1 + 1/(1 - α))
随机探测再散列、二次探测再散列和再哈希的平均查找长度为:Snr ≈ -1/a ln(1 - a)
链地址法处理冲突的平均查找长度为:Snc ≈ 1 + a/2