Hash表
简介
最主要的作用就是把一个比较大的值域映射到一个比较小的空间。一般就是有一个哈希函数定义一个规则,输入一个大的数,输出一个小的数(一般是直接取mol,一般是取成质数,离2远点,这样冲突概率最小)。
但是这个规则有可能会有冲突,比如两个数映射出来是同一个数,这时一般就会用到下面的两种方法
前面写的离散化是一种特殊的哈希方式。
存储方式
拉链法
就是开辟一个一维数组,这个数组的索引就是哈希化以后的结果,我们在这个索引的位置开一条链,下面加上所有的冲突的结果。
假如我们开辟的数组是P,然后11和21哈希化以后的结果都是1,那么就去P[1]处,用一条链式结构存储11和21
int N = 100010;
int h[N], e[N], ne[N], idx;
//插入元素
void insert(int x)
{
int k = (x % N + N) % N; //确保得到的是正值
//h中存储的是指向链表的起点的idx,就是在e里面的索引
//插入一个元素就相当于是插入一个新的头节点,再让h指向一下
//这样不用去找链表详细内容了
e[idx] = x;
ne[idx] = ne[k];
h[k] = 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 N = 100010, null = 0x3f3f3f;
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;
}
字符串哈希
全名叫字符串前缀哈希法,首先我们将这个字符串每一位字符分离出来,然后每一位看成一个p进制的数,再然后将他们转换为十进制的数(乘以一个p的n次方,n由位置决定),因为可能得到数很大,所以再取一个余数。
(一般情况下,0会被舍弃掉,不管是进制的时候还是数组索引的时候,都从1开始)
(一般情况下,进制用131或者13331,对2的64次方取余。这样冲突概率很小)
这个和数字哈希的区别是,数组哈希容忍冲突情况,这个字符串哈希是就当运气好没有冲突
而且我们还有一个好处,如果我们已知一个字符串的哈希值,我们可以算出这个字符串任意一个子串的哈希值.
假如我们已知1~R的哈希值(h[R])和1~L-1的哈希值(假设L在R左边),我们就可以用h[R] - H[L-1]*p的R-L+1次方,得到由L到R的哈希值(因为前缀的计算是从第一位往下算的,也就是说,前面计算的值不管是多少,都不影响需要得到的子串的哈希,只要将幂次方补上就行了)
//这段代码可以得到子串的哈希值
#include <iostream>
using namespace std;
typedef unsigned long long ULL; //定义足够长的长度,这样就不用取模了
const int N = 100010, P = 131;
int n, m;
char str[N];
ULL h[N], p[N];
//得到子串哈希值
ULL GetHash(int l, int r)
{
return h[r] - h[l - 1] * p[r - l + 1];
}
//预先初始化一下p的x次方数组和子串哈希值数组(数组索引对应前x+1个字符)
void Init(int n)
{
p[0] = 1;
for (int i = 1; i <= n; i++)
{
p[i] = p[i - 1] * P;
h[i] = h[i - 1] * P + str[i];
}
}
STL目录
vector变长数组
string字符串
queue队列
priority_queue优先队列(堆)
stack堆
deque双端队列
set,map,multiset,multimap树形结构(底层是红黑树)
unordered_set,unordersed_map,undordered_multiset,undordered_multimap哈希表
bitset压位