基础数据结构(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号
浙公网安备 33010602011771号