用数学关系式表示为:
index = function(key)
数序上有不同的映射关系,不同的key,有可能会获取相同的index,这个时候的index就是有重码,也就是collosion,这就导致了Hash函数的不唯一性,从而在查找index下的关键字时也是有冲突的。
目前一些常用的数学映射关系为:
1、直接定址法,就是直接使用key作为index
2、数字分析法,取key中的若干位数作为index,有较多冲突
3、平法取中法,取key的平方,然后取中间几位作为index(index与key值密切相关),
4、折叠法,将key从中间分割成几个部分,然后按照一定规则相加获取的结果为index。
5、除留余数法,将key对某个数值m求余,获取的余数即为index,显然indxe<m,也就是如果key>m,必然会有n-m个冲突存在(n为key关键字的个数)。
6、随机数法,将key值通过随机数求取index,即function(key)=random(key),伪随机数的均匀性较好,当关键字长度不相等时,用此法构造较为恰当。
解决冲突:
其实并不是将冲突去掉,而是通过一种变通的方法,将冲突变得可以唯一查找,而不是有冲突就没有唯一的index位置了。
解决方法有:
一、开放定址法:
index = (function(key)+di)mod m
di:增长序列,m哈希表长,d的求取方法有下面集中。
1、线性探测再散列,就是查找或插入时,如果发生冲突,就线性排列下去,下面还有冲突,继续排列下去,直到没有冲突时,查找结束或添加index。d=1,2,3,……,m-1
2、二次探测再散列,d=1^2,-1^2,2^2,-2^2,……,±k^2(k<=m/2)
3、伪随机探测再散列,d=random(key)
二、再Hash法
即将index冲突的key关键字进行另一种Hash算法,得到key关键的index2索引值,从而来降低和避免冲突,当然要将冲突完全避免,则需要多个Hash算法隐含在其中。
三、链地址法
当Index发生冲突时,将同一个index下冲突的key值都放在以index开头的链表中,这样index开头的链表中的key都可以比较方便的查找出来。
四、建立溢出表
建立一个和key关键字一样多的index索引,当index发生冲突时,将所有发生冲突的key或index都放在溢出表中。
Hash表查找:
// -------开放定址Hash表的存储结构------------
int hashsize[]={997,...}; //Hash表容量递增表,一个合适的素数序列
typedef struct{
ElemType *elem; //数据元素存储基址,动态分配数组
int count; // 当前数据元素的个数
int sizeIndex; //hashsize[sizeIndex]为当前容量
}HashTable;
//--------链接地址Hash表的存储结构---------------
typedef struct childchain
{
ElemType data; // 数据值
struct childchain *next; // 下一个childchain值,NULL结束
}ChildChainHash
typedef struct mainchain{
int index; // 主链的索引值
ChildChainHash *child; // 指向冲突的child子链,当没有冲突时,给予NULL值。
struct mainchain *main; //
}MainChainHash;
#define SUCCESS 1
#define UNSUCCESS 0
#define DUPLICATE -1
int hashsize[]={997,...}; //Hash表容量递增表,一个合适的素数序列
typedef struct{
ElemType *elem; //数据元素存储基址,动态分配数组
int count; // 当前数据元素的个数
int sizeIndex; //hashsize[sizeIndex]为当前容量
}HashTable;
//--------链接地址Hash表的存储结构---------------
typedef struct childchain
{
ElemType data; // 数据值
struct childchain *next; // 下一个childchain值,NULL结束
}ChildChainHash
typedef struct mainchain{
int index; // 主链的索引值
ChildChainHash *child; // 指向冲突的child子链,当没有冲突时,给予NULL值。
struct mainchain *main; //
}MainChainHash;
#define SUCCESS 1
#define UNSUCCESS 0
#define DUPLICATE -1
// 采用开放式哈希表的存储结构
Status SearchHash(HashTable H, KeyType k, int &p, int &c)
{
// 开放地址哈希表H中查找关键字k的元素,若查找成功,以p指示待查数据,
// 元素在表中位置,并返回SUCESS,否则,以p指示插入位置,并返回UNSUCESS,
// c用以计冲突次数,其初值置零,供建表插入时参考
p = Hash(k); // 求得hash地址表
while( H.elem[p].key != NULLKEY && //该位置中填有记录
!EQ( k,H.elem[p].key) // 并且关键字不相等
collision( p, ++c) ; // 求得下一探索地址p
if (EQ(K, H.elem[p].key ) )
return SUCCESS; // 查找成功,p返回待查数据元素位置
else
return UNSUCESS; // 查找不成功.( H.elem[p].key == NULLKEY )
// p返回的是插入的位置
}// SearchHash
Status InsertHash( HashTable &H, ElemType e)
{
// 查找不成功时,插入数据元素e到开放定址Hash表H中,并返回OK,
// 若冲突次数过大,需重新构建Hash表
c = 0;
if( SearchHash( H,e.key,p,c ))
return DUPLICATE; // 表中已有与e有相同关键字的元素
else if ( c <hashsize[H.sizeindex]/2 ) //冲突次数c未达到上限时,(c的阈值可调)
{
H.elem[p] = e; ++H.count; return OK; // 插入e
}
else
{
RecreateHashTable(H);
return UNSUCESS; // 重建hash表
}
} // InsertHash
摘抄自严蔚敏的C数据结构中Hash章节。
Status SearchHash(HashTable H, KeyType k, int &p, int &c)
{
// 开放地址哈希表H中查找关键字k的元素,若查找成功,以p指示待查数据,
// 元素在表中位置,并返回SUCESS,否则,以p指示插入位置,并返回UNSUCESS,
// c用以计冲突次数,其初值置零,供建表插入时参考
p = Hash(k); // 求得hash地址表
while( H.elem[p].key != NULLKEY && //该位置中填有记录
!EQ( k,H.elem[p].key) // 并且关键字不相等
collision( p, ++c) ; // 求得下一探索地址p
if (EQ(K, H.elem[p].key ) )
return SUCCESS; // 查找成功,p返回待查数据元素位置
else
return UNSUCESS; // 查找不成功.( H.elem[p].key == NULLKEY )
// p返回的是插入的位置
}// SearchHash
Status InsertHash( HashTable &H, ElemType e)
{
// 查找不成功时,插入数据元素e到开放定址Hash表H中,并返回OK,
// 若冲突次数过大,需重新构建Hash表
c = 0;
if( SearchHash( H,e.key,p,c ))
return DUPLICATE; // 表中已有与e有相同关键字的元素
else if ( c <hashsize[H.sizeindex]/2 ) //冲突次数c未达到上限时,(c的阈值可调)
{
H.elem[p] = e; ++H.count; return OK; // 插入e
}
else
{
RecreateHashTable(H);
return UNSUCESS; // 重建hash表
}
} // InsertHash
摘抄自严蔚敏的C数据结构中Hash章节。
Hash表最大的特点:
不管n有多大,即表有多长,我们总可以选择一个合适的装填因子以便将来平均查找长度限定在一个范围之内。
查找长度
链地址:
S=1+α /2
线性探测:
S = 1/2 (1+1/(1-α ) )
伪随机探测:
S = -1/α ln(1-α )