基础数据结构(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]);
}

 

posted @ 2022-07-20 10:38  张詠然  阅读(39)  评论(0)    收藏  举报