哈希表
- 在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应的存储位置f(key)。查找时,根据这个确定的对应关系找到给定值key的映射f(key),若查找集合中存在这个记录,则必定在f(key)的位置上。这种对应关系f称为散列函数(哈希函数)。采用散列技术将记录存储在一块连续的存储空间中,这种连续存储空间称为散列表(哈希表)。
- 由于key的范围是接近无限的,而哈希表存储空间是有限的,因此难以避免的会碰到两个不同的关键字,经哈希函数后却获得相同的f(key),这种现象称为冲突,将这两个关键字叫做同义词。
构建方法
基本原则:
- 应满足计算简单,哈希函数的计算时间不应该超过其他查找技术与关键字比较的时间。
- 尽量让散列地址均匀地分布在存储空间中,这样可以保证存储空间的有效利用,并减少为处理冲突而耗费的时间。
直接定址法
取关键字的某个线性函数值为散列地址,即f(key)=a*key+b
此方法简单、均匀,且不会产生冲突,但需要事先知道关键字的分布情况,适合查找表较小且连续的情况。
数字分析法
抽取关键字中的部分来计算散列存储位置。
可对抽取出的数字进行反转(123变321)、右环位移(123变312)、左环位移、甚至前两位数与后两位数相加(1234变12+34=46)等。
平方取中法
对关键字(1234)进行平方(1522756),再抽取中间的3位,即227。
折叠法
将关键字从左到右分割成位数相等的几部分(最有一位数不够时可以短些),然后将这几部分叠加求和,并按散列表表长取后几位作为散列地址。
关键字为9876543210,散列表表长为3。将关键字分为四组987|654|321|0,四部分叠加987+654+321+0=1962,再取后三位为962。
除留余数法
f(key)=key mod p (p<=m)
若散列表表长为m,通常p为小于或等于表长的最小质数或不包含小于20质因子的合数。
伪随机数法
f(key)=random(key)
当关键字长度不一致时,此方法比较合适。
处理散列冲突的方法
开放定址法
开放定址法就是一旦发生了冲突,就是寻找下一个空的散列地址,只要表足够大,空的散列地址总能找到,并记录。
- 线性探测法:f(key)=(f(key) + di) mod m(di = 1,2,3,…,m-1)
在解决冲突时,会碰到本来不是同义词却需要争夺同一地址的情况,这种现象叫做堆积。 - 二次探测法:f(key)=(f(key) + di) mod m(di = 12, -12, 22, -22, …, q2, -q2) (q<=m/2)
二次探测法就是跳跃式探测方法,效率较高,但是会出现有空间探测不到的情况,因而存储失败。而线性探测法只要有空间就一定能探测成功。 - 随机探测法:f(key)=(f(key) + di) mod m(di为一个伪随机序列)
再散列函数法
事先准备多个散列函数,每当发生散列地址冲突时,就换一个散列函数计算。
链地址法
将所有的关键字为同义词的记录存储在一个单链表中,将这种表称为同义词子表。在散列表中只存储所有同义词子表的头指针。
链地址法提供了绝不会出现找不到地址的保障,但在查找时需要遍历单链表,会有性能损耗。
公共溢出区法
为所有的冲突关键字建立一个公共溢出区来存放。
在查找时,对给定值通过散列函数计算出散列地址后,先与基本表的相应位置进行比对,如果相等,则查找成功;如果不等,则到溢出表中进行顺序查找。
散列表的性能分析
如果没有冲突,查找的时间复杂度为o(1)。
装填因子α=填入表中的记录个数 / 散列表长度。
散列表的平均查找长度取决于装填因子,而不是查找集合中的记录个数。
代码实现
/*
* @Author: Xyh4ng
* @Date: 2022-11-28 20:15:29
* @LastEditors: Xyh4ng
* @LastEditTime: 2022-11-28 21:25:23
* @Description:
* Copyright (c) 2022 by Xyh4ng 503177404@qq.com, All Rights Reserved.
*/
#include <stdio.h>
#include <stdlib.h>
#define HASHSIZE 12
#define NULLKEY -32768
typedef struct HashTable
{
int *data;
int count;
} HashTable;
void InitHashTable(HashTable *H)
{
H->count = HASHSIZE;
H->data = (int *)malloc(sizeof(HashTable) * HASHSIZE);
for (int i = 0; i < H->count; i++)
{
H->data[i] = NULLKEY;
}
}
int HashFunc(int key)
{
return key % HASHSIZE;
}
// 采用开放定址法构建哈希表,线性探索解决哈希冲突
void InsertHashTable(HashTable *H, int key)
{
int addr = HashFunc(key);
while (H->data[addr] != NULLKEY)
{
addr = (addr + 1) % HASHSIZE;
}
H->data[addr] = key;
}
int SearchHashTable(HashTable H, int key, int *addr)
{
*addr = HashFunc(key);
while (H.data[*addr] != key)
{
*addr = (*addr + 1) % HASHSIZE;
if (H.data[*addr] == NULLKEY || *addr == HashFunc(key))
{
return 0;
}
}
return 1;
}
int main()
{
int a[HASHSIZE] = {12, 67, 56, 16, 25, 37, 22, 29, 15, 47, 48, 34};
HashTable H;
InitHashTable(&H);
for (int i = 0; i < HASHSIZE; i++)
{
InsertHashTable(&H, a[i]);
}
for (int i = 0; i < HASHSIZE; i++)
{
int addr;
SearchHashTable(H, a[i], &addr);
printf("查找 %d 的地址为:%d \n", a[i], addr);
}
return 0;
}