快去自定义签名吧~|

张詠然

园龄:3年6个月粉丝:3关注:3

基础数据结构(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可以自动过滤一些没有用的字符

并查集

  1. 将两个集合合并
  2. 询问两个元素是否在一个集合当中

基本原理:每一个集合用一棵树来表示。树根的编号就是整个集合的编号。

每个点存储它的父节点,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 中国大陆许可协议进行许可。

posted @   张詠然  阅读(28)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起