来到数据结构章节的最后一节课,主要内容是哈希表和STL。
首先要理解什么是哈希。
其实之前的课讲过的离散化就是一种特殊的哈希方式,这里介绍的是一般的哈希方式。算法题中对哈希的操作一般只有添加和查找;删除一般不会涉及,非要进行删除操作的话,也不是真的删除,而是用一个bool变量进行标记即可。
//拉链法
#include
#include
using namespace std;
const int N = 100003;
int h[N], e[N], ne[N], idx;
//h[]是哈希函数的一维数组//e[]是链表中存的值//ne[]是指针存的指向的地址//idx是当前指针
void insert(int x)
{
int k = (x % N + N) % N;//为了让负数在整数有映射,负数的取模还是负数,加上maxn后为正,再%即可
/*如果不同单链表的idx都是从0开始单独计数,那么不同链表之间可能会产生冲突。
这里的模型是这样的:e[]和ne[]相当于一个大池子,里面是单链表中的节点,会被所有单点表共用,idx相当于挨个分配池子中的节点的指针。
比如如果第0个节点被分配给了第一个单链表,那么所有单链表就只能从下一个节点开始分配,所以所有单链表需要共用一个idx。*/
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])//所有槽都清空,对应的是单链表的头(head)[注:head存的是地址,详见单链表的课]指针为-1
{
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;
}
//开放寻址法
#include
#include
using namespace std;
const int N = 200003, null = 0x3f3f3f3f;
int h[N];
int find(int x)
{
int t = (x % N + N) % N;
while (h[t] != null && h[t] != x)
{
t++;
if (t == N) t = 0;
}
return t;
}
int main()
{
memset(h, 0x3f, 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;
}
题解
字符哈希串的意思 其实就是将字符串的前缀转换为数来存值
由于每位的权值是不一样的 所以每个前缀值都对应着唯一的一种字符串
所以相减后的值也应该是唯一的 从而利用相减后的值可以判断字符串的区间段是否相等
当然具体还有很多细节需要注意
#include
using namespace std;
typedef unsigned long long ULL; //由于前缀值的值会很大 所以应该将数组中的数据定义为ULL型
const int N = 1e5 + 10;
const int P = 131; //P为权重
//131为经验值 即P=131或13331时 哈希冲突的可能性最小
int n, m;
char str[N];
ULL h[N]; //h[]存放字符串的前缀值
ULL p[N]; //p[]存放各个位数的相应权值
ULL get(int l, int r)
{
return h[r] - h[l - 1] * p[r - l + 1]; //这步其实是将h[l-1]左移
} //其目的事实上是为了将h[l-1]的高位与h[r]相对齐从而才可以未完成计算
int main()
{
scanf("%d%d%s", &n, &m, str + 1);
p[0] = 1; //注意这步千万不要忘了 最开始的权值必须赋值为1 否则接下来就会出错
for (int i = 1; i <= n; i++)
{
p[i] = p[i - 1] * P; //计算每个位上的相应权值
h[i] = h[i - 1] * P + str[i]; //计算字符串前缀值
//最新加入的数的权值为p的0次 所以直接加上str[i]即可
}
while (m--)
{
int l1, r1, l2, r2;
scanf("%d%d%d%d", &l1, &r1, &l2, &r2);
if (get(l1, r1) == get(l2, r2)) puts("Yes");
else puts("No");
}
return 0;
}
vector, 变长数组,倍增的思想
size() 返回元素个数
empty() 返回是否为空
clear() 清空
front()/back()
push_back()/pop_back()
begin()/end()
[]
支持比较运算,按字典序
pair<int, int>
first, 第一个元素
second, 第二个元素
支持比较运算,以first为第一关键字,以second为第二关键字(字典序)
string,字符串
szie()/length() 返回字符串长度
empty()
clear()
substr(起始下标,(子串长度)) 返回子串
c_str() 返回字符串所在字符数组的起始地址
queue, 队列
size()
empty()
push() 向队尾插入一个元素
front() 返回队头元素
back() 返回队尾元素
pop() 弹出队头元素
priority_queue, 优先队列,默认是大根堆
push() 插入一个元素
top() 返回堆顶元素
pop() 弹出堆顶元素
定义成小根堆的方式:priority_queue<int, vector<int>, greater<int>> q;
stack, 栈
size()
empty()
push() 向栈顶插入一个元素
top() 返回栈顶元素
pop() 弹出栈顶元素
deque, 双端队列
size()
empty()
clear()
front()/back()
push_back()/pop_back()
push_front()/pop_front()
begin()/end()
[]
set, map, multiset, multimap, 基于平衡二叉树(红黑树),动态维护有序序列
size()
empty()
clear()
begin()/end()
++, -- 返回前驱和后继,时间复杂度 O(logn)
set/multiset
insert() 插入一个数
find() 查找一个数
count() 返回某一个数的个数
erase()
(1) 输入是一个数x,删除所有x O(k + logn)
(2) 输入一个迭代器,删除这个迭代器
lower_bound()/upper_bound()
lower_bound(x) 返回大于等于x的最小的数的迭代器
upper_bound(x) 返回大于x的最小的数的迭代器
map/multimap
insert() 插入的数是一个pair
erase() 输入的参数是pair或者迭代器
find()
[] 时间复杂度是 O(logn)
lower_bound()/upper_bound()
unordered_set, unordered_map, unordered_multiset, unordered_multimap, 哈希表
和上面类似,增删改查的时间复杂度是 O(1)
不支持 lower_bound()/upper_bound(), 迭代器的++,--
bitset, 圧位
bitset<10000> s;
~, &, |, ^
>>, <<
==, !=
[]
count() 返回有多少个1
any() 判断是否至少有一个1
none() 判断是否全为0
set() 把所有位置成1
set(k, v) 将第k位变成v
reset() 把所有位变成0
flip() 等价于~
flip(k) 把第k位取反