Hash查找是一种效率很高的查找方法,以下将Hash查找的hash函数设计方法,以及冲突解决方法列举:
hash函数设计:
(1)直接定址法:
H(key)=key 或者 H(key)=a*key+b
适用于地址集合的大小==关键字集合的大小
(2)除留余数法:
H(key)=key Mod p
其中模p的大小满足:p为不大于M的素数 或者 不含20一下的质因子
int h(int x) { return(x % 16); }
(3)平方取中法:
以关键字的平方值的中间几位作为存 储地址。求“关键字的平方值”的目的 是“扩大差别”,同时平方值的中间各 位又能受到整个关键字中各位的影响
此方法适合于: 关键字中的每一位都有某些数字重复 出现频度很高的现象
(4)折叠法:
将关键字分割成若干部分,然后取它 们的叠加和为哈希地址。有两种叠加处 理的方法:移位叠加和间界叠加。
此方法适合于: 关键字的数字位数特别多。
int h(char* x)
{
int i, sum;
for (sum=0, i=0; x[i] != '\0'; i++)
sum += (int) x[i];
return(sum % M);
}
(5)数字分析法
假设关键字集合中的每个关键字都是 由 s 位数字组成 (u1, u2, …, us),分析关 键字集中的全体,并从中提取分布均匀 的若干位或它们的组合作为地址
此方法仅适合于: 能预先估计出全体关键字的每一位 上各种数字出现的频度。
int ELFhash(char* key)
{
unsigned long h = 0;
while(*key)
{
h = (h << 4) + *key++;//使得不同位置的字符权值不同
unsigned long g = h & 0xF0000000L;
if (g) h ^= g >> 24;
h &= ~g;
}
return h % M;
}
(6)随机数法:
H(key) = Random(key) 其中,Random 为伪随机函数
通常,此方法用于对长度不等的关键 字构造哈希函数。
hash函数冲突解决:
处理冲突的方法一般有两种:
(1)闭散列方法(开地址方法):把冲突记录存储在表中另一个槽中。 “内部问题内部解决”
(2)开散列方法(单链方法):吧冲突记录存储在表外,内部问题外部解决。
闭散列方法:
• 把所有记录直接存储在散列表中
• 每条记录i有一个基位置h(ki),即根据散列函数算出来的槽
• 当要插入一条记录R时,如计算出来的基位置已经被 另一条记录占据,则根据冲突解决策略来决定把R存储在表中的其他槽内;
• 检索方法也相似,先根据关键字值计算基位置,当未找到时再根据冲突解决策略在表中其他相应槽中来寻找
闭散列方法主要有2种:
(1) 桶式哈希:
桶式哈希的查询
• 当检索某条记录时,首先计算哈希函数确定记
录所在的桶,然后在该桶中检索记录。 • 若在该桶中找到记录,则返回成功; • 若在该桶中没有找到记录,且该桶未满,则返
回未查找到该记录; • 若在该桶中没有找到记录,但该桶已满,则还
需检索溢出桶,直到找到记录或者溢出桶的所
有记录都已被检索为止。
(2)探查序列方法:
为冲突的地址H(key)求一个地址序列:
H0,H1,H2,…Hs 1<=s<=m-1 其中H0=H(key)
Hi=(H(key)+di)%M, i=1,2,3,…
对增量di的三种取法:
◼ 线性探测再散列
d i = c i 最简单的情况 c=1
探查函数是p(K,i) = ic
◼ 平方探测再散列
d i = 1^ 2 , - 1^2 ,- 2^2 , …
探查函数是
p(K,2i-1) = i * i
p(K,2i) = - i * i
◼ 随机探测再散列
d i 是一组伪随机数列 或者 d i =i × H 2 (key) (又称双散列函数探测)
探查函数P(k,i)=rand() 或P= i*H2(key)
开散列方法
将所有哈希地址相同的记录 都链接在同一链表中。
算法思想
开散列的查询和构造一致,查找过程则有:
对于给定值 K,计算哈希地址 i = H(K)
若 r[i] = NULL 则查找不成功
若 r[i].key = K 则查找成功
否则求下一地址 Hi:
直至 r[Hi] = NULL (查找不成功) 或 r[Hi].key = K (查找成功) 为止。
具体实现
// Insert e into hash table HT
template <typename Key, typename E> bool hashdict<Key, E>:: hashInsert(const key& e, const E& e)
{
int home; // Home position for e
int pos = home = h(k); // Init
for (int i=1; EMPTYKEY!=(HT[pos]).key(); i++)
{
pos = (home + p(k,i)) % M;
Assert(k!= HT[pos].key(),”Duplicates not allowed”);
}
Kvpair<Key,E> temp(k,e);
HT[pos] = temp; // Insert e
}
// Search for the record with Key K
template <typename Key, typename E> E hashdict<Key, E>:: hashSearch(const Key& k) const
{
int home; // Home position for K
int pos = home = h(k); // Initial posit
for (int i = 1; (k!=( HT[pos].key()) && (EMPTYKEY!=( HT[pos]).key()); i++)
{
pos = (home + p(k, i)) % M; // Next
if (k==(HT[pos]).key())
{ // Found it
return( HT[pos]).value();
}
else return NULL; // K not in hash table
}
}
散列方法的效率分析
◼ 衡量标准:插入、删除和检索操作所需要的记录访 问次数
◼ 散列表的插入和删除操作都是基于检索进行的
◼ 删除:必须先找到该记录
◼ 插入:必须找到探查序列的尾部,即对这条记录进行 一次不成功的检索
◼ 对于不考虑删除的情况,是尾部的空槽
◼ 对于考虑删除的情况,也要找到尾部,才能确定是 否有重复记录
哈希表查找的分析:
◼ 选用的哈希函数;
◼ 选用的处理冲突的方法;
◼ 哈希表饱和的程度,装载因子α=n/m 值的大小 (n—记录数,m—表的长度) 决定哈希表查找的ASL的因素: 哈希表查找的分析 一般情况下,可以认为选用的哈希函数是“均匀” 的,则在讨论ASL时,可以不考虑它的因素。
因此,哈希表的ASL是处理冲突方法和装载因子的 函数
影响检索的效率的重要因素
◼ 散列方法预期的代价与负载因子 α= N/M有关
◼ α 较小时,散列表比较空,所插入的记录比较容易插 入到其空闲的基地址
◼ α 较大时,插入记录很可能要靠冲突解决策略来寻找 探查序列中合适的另一个槽
◼ 随着α增加,越来越多的记录有可能放到离其基地 址更远的地方
查找成功时有下列结果
内容来自湖南大学数据结构课件