基础数据结构(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]); }
本文作者:张詠然
本文链接:https://www.cnblogs.com/zyrddd/p/16470794.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步