数据结构 | 哈希表
哈希表 (散列表, Hash Table):根据键而直接访问在内存储存位置的数据结构。
哈希表
原理简述
如果我们使用朴素的方式储存数据,例如储存大量姓名,当我们要查找某个姓名时,只能够从头到尾遍历来查找,这样的查找效率非常低。但是我们可以将姓名进行分类储存,例如按姓氏分类,当查找时到对应姓氏的类别查找,这样就能大大提高查找效率。
哈希表的原理和上述例子类似,我们使用一个哈希函数(散列函数)
哈希函数
以下两种为整型数据的通用哈希方式,字符串哈希将会在单独的文章介绍:https://io.zouht.com/142.html。
取模法
我们可以非常简单地通过取模,将大范围的数据映射到
另外 C/C++ 负数取模得非正数,需要特殊处理,即程序中哈希函数需要写为
相乘法
其中
冲突处理
由于哈希函数将大范围数据映射到小范围内,因此冲突不可避免。如使用哈希函数
单独链表法 (Separate Chaining)
单独链表法即将同一位置的所有元素储存到一个链表内。用例子形象化表述:
使用哈希函数
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|
11 | 1 | 2 | 312 | 27 | 12341 | |||||
44 | 56 | 6547 | 654 | |||||||
23 |
哈希表中
代码
/* 单独链表法 使用数组模拟链表实现 */
const int SIZE = 100003;
// SIZE为取模的数,同时也是数组大小
int hs[SIZE], val[SIZE], nxt[SIZE], idx;
// hs哈希表储存链表头指针,val链表节点数据域,nxt链表节点指针域,idx链表长度
// !!!hs每一字节需初始化为-1!!!
inline int f(int x) // 哈希函数
{
return (x % SIZE + SIZE) % SIZE;
}
void insert(int x) // 将x插入表内
{
int y = f(x);
// 下面的操作为链表头插法
val[idx] = x;
nxt[idx] = hs[y];
hs[y] = idx++;
}
bool query(int x) // 查询x是否在表内
{
int y = f(x);
for (int i = hs[y]; i != -1; i = nxt[i]) // 遍历链表
if (val[i] == x)
return true;
return false;
}
(当需要删除操作时,通常不真正删除,而是使用一个 bool 数组指示元素的是否被删除)
开放寻址法 (Open Addressing)
开放寻址法当遇到冲突地址时,寻找下一个地址,直到找到可用地址储存数据。用例子形象化表述:
使用哈希函数
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|
1 | 2 | 56 | 23 | 19 |
可以发现奇怪的数据为
我们也能很明显发现问题,当哈希表接近满时,会频繁地出现冲突,甚至需要遍历大部分数组才可找到可用位,最坏情况时间复杂度会和遍历整个数组一样低。因此当使用开放寻址法时,经验规律得出数组大小最好开到数据规模的
代码
// 开放寻址法
const int SIZE = 200003, NONE = 0x3f3f3f3f;
// SIZE为取模的数,同时也是数组大小,NONE为定义的代表空位的数字,需要不在哈希函数值域内
int hs[SIZE];
// hs哈希表 !!!hs每一字节需初始化为0x3f!!!
inline int f(int x) // 哈希函数
{
return (x % SIZE + SIZE) % SIZE;
}
int find(int x) // 若x在表内,返回x的位置;若x不在表内,返回x应当插入的位置
{
int y = f(x);
while (hs[y] != NONE && hs[y] != x) // 当找到空位或找到了x就停下来
{
// 找不到就一直找下一位,找到最后一位再从第0位开始找
y++;
if (y == SIZE)
y = 0;
}
return y;
}
void insert(int x) // 将x插入表内
{
int k = find(x);
hs[k] = x;
}
bool query(int x) // 查询x是否在表内
{
int k = find(x);
return hs[k] == x;
}
(当需要删除操作时,通常不真正删除,而是使用一个 bool 数组指示元素的是否被删除)
本文采用 CC BY-SA 4.0 许可,本文 Markdown 源码:Haotian-BiJi