在日常的工作中,经常会接触到散列表、MD5算法、哈希表等一些技术,这些都是运用了散列技术。
那么散列表是怎么查找的呢?怎么设计一个散列呢?接下来来聊聊散列表设计的常见手段和怎么解决散列冲突。
算列技术:记录的存储位置和它的关键字之间建立一个确定的对应关系F,使得关键字key
对应一个存储位置F(key)。查找时,根据这个对应关系,找到key
的映射F(key)
,若查找集合中存在这个记录,则必定在F(key)
的位置上。
设计散列函数时:
接下来,了解一下常见的散列函数设计方法:
优点:简单,均匀,不易产生冲突
确定:需要事先知道数据关键字的分布情况,适合查找表小且连续的情况。
用散列函数存储,F(key) = key
,则
假如查找3岁的人口,根据F(3) = 3
,直接找到为450W
。
可以设计F(key) = key - 1980
。
直接定址法可以设成一个线性函数,
F(key) = a * key + b
(其中a和b为常数)。
如果关键字是一个位数特别多的数字,比如手机号,找出数字的规律,尽可能利用这些数据来构造冲突几率较低的散列地址。
比如一个地区手机号,前面很多位都相同,不同可能是最后4位数,将这部分作为散列算法的依据。
取关键字平方后的中间几位作为散列地址。
先通过求关键字的平方值扩大相近数的差别,然后根据表长度取中间的几位数作为散列函数值。又因为一个乘积的中间几位数和乘数的每一位都相关,所以由此产生的散列地址较为均匀。
折叠法就是将关键字从左到右分割成位数相等的几部分(注意最后一部分位数不够可以稍微短些)。 然后将几部分叠加求和,并按散列表长,取后几位作为散列地址
例如:对数字9876543210
进行分割,三位一组,共四组,分别为987
、654
、321
和0
。然后对四个数字求和得到1962
,最终取和的后三位962
作为散列地址。
取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址。
f i ( k e y ) = k e y m o d p ( p < m ) f_i(key) = key\ mod\ p (p
开放定址法就是一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存⼊。
f i ( k e y ) = ( f ( k e y ) + d i ) M o d m ; ( d i = 1 2 , − 1 2 , 2 2 , − 2 2 , . . . , q 2 , − q 2 , q < m / 2 ) f_i(key) = (f(key) +d_i)\ Mod\ m;(d_i=1^2,-1^2,2^2,-2^2,...,q^2,-q^2,q
事先准备多个散列函数,当一个散列函数冲突时,用另一个散列函数。
f i ( k e y ) = R H i ( k e y ) ( i = 1 , 2 , 3... , k ) f_i(key) = RH_i(key)(i=1,2,3...,k) fi(key)=RHi(key)(i=1,2,3...,k)
RHi
指的是不同的散列函数
将所有的关键字为同义词的记录存储在一个单链表中,我们称这种为同义词子表,在散列表中只存储同义词子表的头指针。
例如:
关键字集合 { 12,67,56,16,25,37,22,29,15,47,48,34 }
表⻓为12
, 我们用散列函数 f ( key ) = key mod 12
另开一个公共的表存储冲突的数据。基本表没有查找到,就在溢出表中查找。空间浪费严重
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 100 //存储空间初始分配量
#define SUCCESS 1
#define UNSUCCESS 0
//定义散列表长为数组的长度
#define HASHSIZE 12
#define NULLKEY -32768
typedef struct
{
//数据元素存储基址,动态分配数组
int *elem;
//当前数据元素个数
int count;
}HashTable;
int m=0; /* 散列表表长,全局变量 */
//1.初始化散列表
Status InitHashTable(HashTable *H)
{
int i;
//1 设置H.count初始值; 并且开辟m个空间
m = HASHSIZE;
H->count = m;
H->elem = (int *)malloc(m*sizeof(int));
//2 为H.elem[i] 动态数组中的数据置空(-32768)
for(i = 0; i < m; i++)
H->elem[i] = NULLKEY;
return OK;
}
//2. 散列函数
int Hash(int key)
{
//除留余数法
return key % m;
}
//3. 插入关键字进散列表
void InsertHash(HashTable *H,int key)
{
//1. 求散列地址
int addr = Hash(key);
//2. 如果不为空,则冲突
while (H->elem[addr] != NULLKEY)
{
//开放定址法的线性探测
addr = (addr+1) % m;
}
//3. 直到有空位后插入关键字
H->elem[addr] = key;
}
//4. 散列表查找关键字
Status SearchHash(HashTable H,int key,int *addr)
{
//1. 求散列地址
*addr = Hash(key);
//2. 如果不为空,则冲突
while(H.elem[*addr] != key)
{
//3. 开放定址法的线性探测
*addr = (*addr+1) % m;
//4. H.elem[*addr] 等于初始值或者循环有回到了原点.则表示关键字不存在;
if (H.elem[*addr] == NULLKEY || *addr == Hash(key))
//则说明关键字不存在
return UNSUCCESS;
}
return SUCCESS;
}