散列表查找(哈希表)
- 存储位置 = f(关键字)
- 散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f(key)。
- 我们把这种对应关系f称为散列函数,又称为哈希(Hash)函数。
- 采用散列技术将记录存储在一块连续的存储空间中,这块连续存储空间称为散列表或者哈希表。
- 散列技术既是一种存储方法也是一种查找方法。但是它主要是面向查找的存储结构。
- 散列技术最适合求解问题是查找与给定值相等的记录。
散列函数构造方法
- 直接定址法:f(key) = a x key + b; (a, b 为常数) 适合与查找表较小且连续,而且知道关键字分布的情况
- 数字分析法:适合于处理关键字位数较大,又知道关键字的发布且关键字若干为分布较均匀
- 平方取中法:比如关键字是1234,它的平分为1522756 再抽取中间的三位即227 用作散列地址,比较适合于不知道关键字的分布,而位数又不是很大的情况
- 折叠法:将关键字从左到右分割成位数相等的几部分,然后将这几部分叠加求和,并按散列表表长,取后几位作为散列地址。其不需要事先知道关键字的分布,适合关键字位数较多的情况
- 除留余数法:f(key) = key mod p (p <= m, m为散列表长)。通常p为小于或等于表长(最好接近m)的最小质数或不包含小于20质因子的合数
- 随机数法: f(key) = random(key),当关键字长度不等时,采用这个方法比较合适
散列冲突
当两个关键字key1 != key2,但是却有f(key1) == f(key2),这种现象我们称为冲突(collision),并把key1和key2称为这个散列函数的同义词。
散列冲突处理
- 开放定址法:一旦发生冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址一定能找到,并将记录存入。公式为:fi(key) = ( f(key) + di) MOD m (di = 1, 2, 3, 4…, m -1)。这种公式对应的开放定址法称为线性探测法。容易发生堆积现象(不同义的词争夺一个地址)
- 增加平方运算,不让关键字都聚集在某一块区域,二次探测法: fi(key) = ( f(key) + di) MOD m (di = 1^2, (-1)^2, 2^2, (-2)^2 …)
- 还有一种是对于位移量di采用随机函数计算得到,称为随机探测法
- 再散列函数法:事先准备多个散列函数,每当发送冲突就换一个散列函数计算,这样也不会导致关键字产生聚集。
- 链地址法:将所有关键字为同义词的记录存储在一个单链表中,散列表中只存储所有同义词子表的头指针
- 公共溢出区法:为所有冲突的关键字建立一个公共的溢出区来存放
散列表查找实现
#define UNSUCCESS 0
#define HASHSIZE 12 /*定义散列表长为数组的长度*/
#define NULLKEY -32768
typedef struct
{
int *elem;/*数据元素存储基址,动态分配数组*/
int count;/*当前数据元素个数*/
}HashTable;
int m = 0; /*散列表表长,全局变量*/
/*初始化散列表*/
Status InitHashTable(HashTable *H)
{
int i;
m = HASHSIZE;
H->count = m;
H->elem = (int *)malloc(sizeof(int) * m);
for(i = 0; i < m; i++)
{
H->elem[i] = NULLKEY;
}
return OK;
}
/*散列函数*/
int Hash(int key)
{
return key % m; /*除留取余法*/
}
/*插入关键字进入散列表*/
void InsertHash(HashTable *H, int key)
{
int addr = Hash(key);
while(H->elem[addr] != NULLKEY)
{
addr = (addr + 1) % m;/*开放定址法的线性探测*/
}
H->elem[addr] = key;
}
/*散列表查找关键字*/
Status SearchHash(HashTable H, int key, int *addr)
{
*addr = Hash(key);
while(H.ele[*addr] != key)
{
*addr = (*addr + 1) % m;
if(H.elem[*addr] == NULLKEY || *addr == Hash(key))
{
/*如果循环回到原点说明关键字不存在*/
return UNSUCCESS;
}
}
return SUCCESS;
}
散列表的装填因子
装填因子α = 填入表中的记录个数 / 散列表长度。α越大,填入表中的记录越多,产生冲突的可能性就越大。