第二章 数据结构 (二)(并查集、Trie树)

一、Trie树(用来高效存储和查找字符串集合的数据结构)

1、 用二维数组来构建一个树,第一维为结点下标,第二维为子节点,单个二维数组的值为子节点下标。构建字典树用于查询和插入。

#include<bits/stdc++.h>
//835 存储查询字符串
using namespace std;
const int N=1e5+10;
int son[N][26],cnt[N],idx;
char str[N];
//下标是0的节点既是根节点,又是空节点
//son数组第一维为父节点的下标,第二维度为某一个儿子的字母,其值为其这个儿子的下标
void insert(char str[])
{
    int p=0;//都是从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]++;//最后一个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);
    for(int i=0;i<n;i++)
    {
        char op[2];
        scanf("%s%s",op,str);
        //两个字符串输入的时候中间要有空格
        if(*op=='I')insert(str);
        else cout<<query(str)<<endl;
    }
}

二、并查集

1、并查集可以非常快速地执行将两个集合合并,或询问两个元素是否在同一个集合中两个操作

 优化:路径压缩优化

2、实现

#include<bits/stdc++.h>
//836 并查集 集合合并和判断
using namespace std;
const int N=1e5+10;
int p[N];//存父节点编号
int n,m;
int find(int x)//返回祖宗结点
{
    //递归求解
    if(p[x]!=x)p[x]=find(p[x]);
    return p[x];//递归终止条件
}
int main()
{
    cin>>n>>m;
    for(int i=0;i<n;i++)p[i]=i;
    while(m--)
    {
        char op[2];//char不会忽略字符和回车,读字符串会忽略字符和回车
        int a,b;
        cin>>op>>a>>b;//两种输入方式都可以
        //scanf("%s%d%d",op,&a,&b);
        if(op[0]=='M')p[find(a)]=find(b);//合并两个集合
        else
        {
            if(find(a)==find(b))puts("Yes");
            else puts("No");
        }
    }
}

3、并查集例题计算连通块中的点的数量以及合并连通块

维护一个size数组,只有根结点有效,初始化为1,每次合并两个树的时候,更新size。

#include<bits/stdc++.h>
//837 连通块中点的数量
using namespace std;
const int N=1e5+10;
int p[N];//存父节点编号
int n,m;
int size[N];//连通块中点的数量
//只有根结点中的size是有意义的
int find(int x)//返回祖宗结点
{
    //递归求解
    if(p[x]!=x)p[x]=find(p[x]);
    return p[x];//递归终止条件
}
int main()
{
    cin>>n>>m;
    for(int i=0;i<n;i++)p[i]=i,size[i]=1;
    while(m--)
    {
        char op[2];//char不会忽略字符和回车,读字符串会忽略字符和回车
        int a,b;
        cin>>op;//两种输入方式都可以
        if(op[0]=='C')
        {
            scanf("%d%d",&a,&b);
            if(find(a)==find(b))continue;
            //当要结合的两个点已经在同一个集合里面
            //下面不能再在size上加数了
            size[find(b)]+=size[find(a)];
            //先更新size
            p[find(a)]=find(b);
           
        }
        else if(op[1]=='1')
        {
            scanf("%d%d",&a,&b);
            if(find(a)==find(b))puts("Yes");
            else puts("No");
        }
        else
        {
            
            cin>>a;
            cout<<size[find(a)]<<endl;
        }
    }
}