基础数据结构(2)
基础数据结构(2)
Trie树
高效地存储和查找字符串集合的数据结构
存储方式如图

在每一个单词的结尾打一个标记
例题代码如下:
#include <iostream>
using namespace std;
const int N = 100010;
int son[N][26], cnt[N], idx;
char str[N];
void insert(char str[])
{
int p = 0;
for (int i = 0; str[i]; i ++ )
{
int u = str[i] - 'a';
if (!son[p][u]) son[p][u] = ++ idx;
p = son[p][u];
}
cnt[p] ++ ;
}
int query(char str[])
{
int p = 0;
for (int i = 0; str[i]; i ++ )
{
int u = str[i] - 'a';
if (!son[p][u]) return 0;
p = son[p][u];
}
return cnt[p];
}
int main()
{
int n;
scanf("%d", &n);
while (n -- )
{
char op[2];
scanf("%s%s", op, str);
if (op[0] == 'I') insert(str);
else cout << query(str) << endl;
}
return 0;
}
用%s可以自动过滤一些没有用的字符
并查集
- 将两个集合合并
- 询问两个元素是否在一个集合当中
基本原理:每一个集合用一棵树来表示。树根的编号就是整个集合的编号。

每个点存储它的父节点,p[x]表示x的父节点
判断树根:if (p[x] == x)
求x的集合编号:while (p[x] != x) x = [x];
合并两个集合:p[x] = y;
优化(路径压缩):
此时时间复杂度在求x的编号那步特别高
我们可以将搜索路径上的所有点都直接指向根节点
例题代码:
#include <iostream>
using namespace std;
const int N = 100010;
int q[N], n, m;
int find(int x) // 返回x的祖宗节点 + 路径压缩
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ ) p[i] = i;
while (m -- )
{
char op[2];
int a, b;
scanf("%s%d%d", op, &a, &b);
if (op[0] == 'M') p[find(a)] = find(b);
else
{
if (find(a) == find(b)) printf("yes\n");
else printf("No\n");
}
}
return 0;
}
堆
堆是完全二叉树

堆可以用一个数组存
h[1] 表示根节点,一个点的左儿子的下表是 2x ,右儿子是 2x + 1 。
我们一般维护的是小根堆,小根堆是父节点小于等于子节点,所以 h[1] 就是整个堆中的最小值
小根堆常见的几种操作

维护堆有两个函数,一个是 up() ,一个是 down() 。他们的作用是将当前的元素与父节点交换还是子节点交换。
因为删除和修改过后可能有两种情况,直接全做一遍就行了,但只会执行一个函数。
例题代码:
#include <iostream>
using namespace std;
const int N = 100010;
int h[N], s; // 定义堆和堆的大小
int n, m;
void down(int u)
{
int t = u;
if (u * 2 <= s && h[u * 2] < h[t]) t = u * 2;
if (u * 2 + 1 <= s && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
if (t != u)
{
swap(h[t], h[u]);
down(t);
}
}
void up(int u)
{
while (u / 2 && h[u / 2] > h[u])
{
swap(h[u / 2], h[u]);
u /= 2;
}
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ ) scanf("%d", &h[i]);
s = n; // 将堆的大小复制成初始范围
for (int i = n / 2; i; i -- ) down(i); // 时间复杂度为 O(n)
while (m -- )
{
printf("%d ", h[1]);
h[1] = h[s];
s -- ;
down(1);
}
return 0;
}
为什么那一步是 O(n) 的:
n / 2 代表先从倒数第二层开始,所以从倒数第三层开始算,倒数第三层是n / 3
所以式子如下

堆还有另一个操作
删除第k个插入的元素
想要知道第k个插入的元素是什么,就需要一个数组ph来存第k个插入元素在数组中的下标是什么
因为涉及交换数组中两个元素的操作,所以还需要一个数组hp存数组中对应的元素是第几个插入的
ph[i] = j, hp[j] = i;
代码如下:
void heap_swap(int a, int b)
{
swap(ph[hp[a]], ph[hp[b]]);
swap(hp[a], hp[b]);
swap(h[a], h[b]);
}

浙公网安备 33010602011771号