前言
散列表,又称哈希表(Hash table),相关知识已经很熟悉,不再赘述,备忘参考哈希表_百度百科
重心放在代码实现上
其实个人认为y总讲的所谓的存储结构就是解决冲突的方式,一般常用的是开放定址法和拉链法,开放定址法中又分为线性探测、再散列等。
有点类似离散化,将一个大范围内的数([−109∼109]),通过散列函数h(x),映射到一个较小范围上(h(x)∈(0,105))。但是离散化是需要保序的,哈希是无序的。
yxc:离散化是极其特殊的哈希方式。
算法思路
课程中默认的散列方法是除留余数法
EX:h(x)=xmodQ
注意:一般Q取一个质数,并且距离2
的整数次幂尽量远。
这样发生冲突的概率小
参照知乎问题-Hash时取模一定要模质数吗
Q取105,即xmod105,∈(0,105)
解决冲突的方法:
拉链法
期望算法,每个链表的长度都可以认为很小,整体的查询速度近乎O(1)
插入时,如果发生冲突,就将冲突的关键词挂到这个位置的同义词链表上。
例如上图,11
和23
都映射到了3
这个位置,发生冲突,11
和23
就是”同义词“。
1 2 3 4 5 6 7
| void insert(int x) { int k = (x % N + N) % N; e[idx] = x; ne[idx] = h[k]; h[k] = idx; idx ++; }
|
查询时,先将x
通过散列函数映射到散列表的对应位置k
上,然后遍历位置k
对应的链表,检查是否存在关键字x
1 2 3 4 5 6 7
| bool find(int x) { int k = (x % N + N) % N; for (int i = h[k]; i != -1; i = ne[i]) { if (e[i] == x) return true; } return false; }
|
删除元素比较复杂,真正实现的时候,也不会真的删除。一般会额外创建一个布尔数组来保存每个节点的状态,进行标记。
完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| #include <cstring> #include <iostream>
using namespace std;
const int N = 100003;
int h[N]; int e[N]; int ne[N], idx;
void insert(int x) { int k = (x % N + N) % N; e[idx] = x; ne[idx] = h[k]; h[k] = idx; idx ++; }
bool find(int x) { int k = (x % N + N) % N; for (int i = h[k]; i != -1; i = ne[i]) { if (e[i] == x) return true; } return false; }
int main() { int n; scanf("%d", &n);
memset(h, -1, sizeof(h)); while (n -- ) { char op[2]; int x; scanf("%s%d", op, &x); if (*op == 'I') insert(x); else { if (find(x)) puts("Yes"); else puts("No"); } }
return 0; }
|
关于以上代码
用 scanf
读取字符串的时候尽量用字符数组,这样scanf
会自动忽略掉回车、空格、指标符,降低出错的概率,例如
1 2
| char op[2]; scanf("%s", op);
|
int k = (x % N + N) % N;
加N的目的是让所有的余数都为正数。
例如:在c++中,负数取模相当于,先取绝对值,再取模,最后结果加个负号。
-10 % 3 = -1
后续的图论中,存点vertex的时候同拉链法类似——邻接表
开放定址法
yxc:我用的比较多的是开放定址法。
基本思想,一个散列表项,既向其同义词开放,又向其异义词开放。
这里默认的是开放定址法中的线性探测法。
开放定址法只需维护一个h(x)数组即可,一般来说,数组长度是数据范围的两到三倍。
删除时,同样的,查找到x
时,不会真的删除,而是在数组中打一个标记。
核心:find
函数
与拉链法略微不同,这里的find
函数,如果x
存在的话,返回的是x
存储的位置,如果不存在的话,返回的是x
应该存储的位置。
1 2 3 4 5 6 7 8 9 10
| const int null = 0x3f3f3f; memset(h, null, sizeof(h)); int find(int x) { int k = (x % N + N) % N; while (h[k] != null && h[k] != x) { k ++; if (k == N) k = 0; } return k; }
|
插入的话直接令h[find[x]] = x
即可。
完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| #include <cstring> #include <iostream>
using namespace std;
const int N = 200003, null = 0x3f3f3f3f;
int h[N];
int find(int x) { int k = (x % N + N) % N; while (h[k] != null && h[k] != x) { k ++; if (k == N) k = 0; } return k; }
int main() { memset(h, null, sizeof h);
int n; scanf("%d", &n);
while (n -- ) { char op[2]; int x; scanf("%s%d", op, &x); if (*op == 'I') h[find(x)] = x; else { if (h[find(x)] == null) puts("No"); else puts("Yes"); } }
return 0; }
|