散列表学习记录

散列表

已知的查找方式:

查找方式 时间复杂度 缺陷
顺序查找 \(O(N)\) 效率低
二分查找 \(O(log_2N)\) 只适合静态查找
二叉搜索树 \(O(h)\) 只适合静态查找
AVL树 \(O(log_2N)\) 涉及字符串比较

查找的本质:已知对象找位置

  • 有序安排对象:全序(二分查找),半序(查找树)
  • 直接算出位置(散列查找)

散列查找的基本工作

  1. 计算位置:构造散列函数
  2. 解决冲突:应用某种策略

时间复杂度\(O(1)\),即查找时间与问题规模无关,散列方法以空间换时间

如果没有冲突:\(T_{查询}=T_{插入}=T_{删除}=O(1)\)

抽象数据结构

类型名称:符号表(SymbolTable
数据对象集:符号表是"名字(Name)- 属性(Attribute)"对的集合
操作集:Table ∈SymbolTable , Name ∈ NameType , Attr ∈ AttributeType

SymbolTable InitializeTable( int TableSize );                               //创建一个长度为TableSize的符号表
Boolean IsIn( SymbolTable Table, NameType Name );                           //查找特定的名字Name是否在符号表Table中
AttributeType Find( SymbolTable Table, NameType Name );                     //获取Table中指定名字Name对应的属性
SymbolTable Modefy( SymbolTable Table, NameType Name , AttributeType Attr); //将Table中指定名字Name的属性修改为Attr
SymbolTable Insert( SymbolTable Table, NameType Name , AttributeType Attr); //向Table中插入一个新名字Name及其属性Attr
SymbolTable Delete( SymbolTable Table, NameType Name , AttributeType Attr); //从Table中删除一个名字Name及其属性

装填因子

设散列表空间为\(m\),填入元素个数为\(n\),则\(α=n/m\)\(α\)为散列表的装填因子

散列查找的基本思想:

  1. 以关键字\(key\)为自变量,通过一个确定的散列函数\(h\),计算出对应的函数值\(h( key )\)作为数据对象的存储地址
  2. 当不同关键字映射到相同的散列地址上,即 \(h(key_i) = h(key_j),(key_i ≠key_j)\) 时,需要某种解决策略

散列函数的考虑因素

  • 计算简单,以提高转换速度
  • 关键词对应的地址空间分布应尽量均匀

数字关键词构造

  1. 直接定址法:

    \(h(key)=a*key+b\)

  2. 除留余数法:

    \(h(key) = key\text{ }mod\text{ }p\)

    \(p\) 一般取\(TableSize\),且为了分布均匀, \(p\) 取素数

  3. 数字分析法:

    分析对象关键字较为随机的部分取为关键字作为地址

    eg:对于11位手机号,选取更为随机的后4位:\(h(key) = atoi(key + 7)\text{ }(char* key)\)

  4. 折叠法:

    把关键字分割成位数相同的部分后叠加

    eg:\(h(56793542) = 542 + 793 + 056 = 391 (千位去除)\)

  5. 平方取中法

    eg:\(h(56793542) = 641 (3225506412905764取中间部分)\)

字符关键词构造

  1. ASCII码加和法:

    \(h(key)=(\text{ }\sum_{i=1}^{n}key[i]\text{ })\text{ }mod\text{ }TableSize\)

    *冲突严重,ASCII编码范围0127,而对于例如10位数的字符串,变化非常多,但ASCII码加和范围仅为01270,计算结果窄,关键词变化宽

  2. 简单改进,前3个字符位移:

    eg:\(h(key)=(key[0]*27^2+key[1]*27^1+key[2]*27^0)\text{ }mod\text{ }TableSize\)

  3. 好的散列函数,位移法:

    涉及所有\(n\)个字符,且分布均匀:\(h(key)=(\text{ }\sum_{i=0}^{n-1}key[n-i-1]*32^i\text{ })\text{ }mod\text{ }TableSize\)

    eg:h("abcde") = \(a*32^4+b*32^3+c*32^2+d*32^1+e*32^0\)

    如何快速计算该式子,减少乘法次数?

    h("abcde") = \((((a*32+b)*32+c)*32+......\)

    且有:\(x*32=x<<5\)

    Index Hash(const char *key,int TableSize)
    {
        unsigned int h = 0;
        while( *key != '\0')
            h = ( h << 5 ) + *key++;
        return h % TableSize;
    }
    

冲突处理

开放地址法(Open Addressing):换个位置

产生冲突时,按某种规则去寻找另一空地址

若发生了第\(i\)次冲突,试探得下一个地址将增加di,基本公式是:\(h(key)=(h(key)+d_i)\)

不同的解决冲突方案决定了\(d_i\)

  1. 线性探测(Linear Probing):\(d_i=i\)

    以增量序列\(1,2...(TableSize-1)\)循环试探下一个存储地址

    线性探测冲突时,易形成聚集现象

  2. 平方探测(Quadratic Probing):\(d_i=±i^2\)

    以增量序列\(1^2,-1^2,2^2,-2^2,...,q^2,-q^2\)\(q≤\lfloor TableSize/2 \rfloor\)循环试探下一个存储地址

    有定理显示:如果散列表常地\(TableSize\)是某个\(4k+3\)\(k\)是整数)形式得素数时,平方探测法就可以探查到整个散列表空间

  3. 双散列(Double Hashing):\(d_i=i*h_2(key)\)\(h_2(key)\)是另一个散列函数

    探测序列:\(h_2(key),2h_2(key),3h_2(key),...\)

    对任意的\(key\),有\(h_2(key)≠0\)

    探测序列还应该保证所有的散列存储单元都应该能被探测到,选择以下形式被证明有良好效果:

\[h_2(key)=p-(key \text{ }mod\text{ }p)\\(p<TableSize,p和TableSize为素数) \]

  1. 再散列(Rehashing)

    当散列表元素太多,即装填因子\(α\)太大时,查找效率会下降,可以用再散列加倍扩大散列表

    散列表扩大时,原有元素需要重新计算放置到新表中

    实用最大装填因子一般取\(0.5≤α≤0.85\)

分离链接法(Separate Chaining):将相应位置上冲突的所有关键词存储在同一个单链表中

散列表的性能分析

平均查找长度(ASL)用来度量散列表查找效率:成功、不成功

关键词的比较次数,取决于产生冲突的多少

影响产生冲突多少有一下三个因素:

  1. 散列函数是否均匀
  2. 处理冲突的方法
  3. 散列表的装填因子α,当装填因子α<0.5时,各种探测法的期望探测次数都不大,也比较接近

线性探测法的查找性能:

\[p=\left\{ \begin{aligned} ASLs & = \frac12(1+\frac1{1-α})\\ ASLu & = \frac12[1+\frac1{(1-α)^2}]\\ \end{aligned} \right. \]

平方探测法和双散列探测法的查找性能:

\[p=\left\{ \begin{aligned} ASLs & = -\frac1αln(1-α)\\ ASLu & = \frac1{1-α}\\ \end{aligned} \right. \]

分离链接法的查找性能:

\[p=\left\{ \begin{aligned} ASLs & = 1+\fracα2\\ ASLu & = α+e^{-α}\\ \end{aligned} \right. \]

posted @ 2020-12-07 21:33  StanOswald  阅读(79)  评论(0编辑  收藏  举报