Tire(字典树) -并查集(3种应用)-堆

  • Trie树

作用:快速的【存储】和【查找】字符串集合的数据结构。
辅助称呼:前缀树,多叉树
结构:树
有一个根节点。
在这里插入图片描述
在每个串的结尾单词处进行标记。查找会更方便。

835:

维护一个字符串集合,支持两种操作:

I x 向集合中插入一个字符串 x;
Q x 询问一个字符串在集合中出现了多少次。
共有 N 个操作,输入的字符串总长度不超过 105,字符串仅包含小写英文字母

用代码实现:

trie树
Trie树中有个二维数组 son[N][26],表示当前结点的儿子,如果没有的话,可以等于++idx。
Trie树本质上是一颗多叉树,对于字母而言最多有26个子结点。
所以这个数组包含了两条信息。
比如:son[1][0]=2表示1结点的一个值为a的子结点为结点2;如果son[1][0] = 0,则意味着没有值为a子结点。这里的son[N][26]相当于链表中的ne[N]
son[x][0] :x的第0个儿子
son[x][1]:x的第1个儿子
1.son[n][26]//存储的是n个节点的儿子是二维数组
son[1][1] =2//表示第一个节点的第第一个儿子是存在节点2,也就是index=2
index = d当前用到那个节点。
2.cnt[n]//一当前为结尾节点的点有多少
3.idx//开辟的新空姐 0:跟节点,空节点。
存储的是数字,所以存储的时候-‘ a’

模板:

int son[N][26], cnt[N], idx;
// 0号点既是根节点,又是空节点
// son[][]存储树中每个节点的子节点
// cnt[]存储以每个节点结尾的单词数量

// 插入一个字符串
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] ++ ;
}

代码:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Main {
    //前缀树,
    //根节点为0
    //son[][],nump[],index
    static int N =1000100;
    static int[][] son = new int[N][26];//因为字母就26个
    static int[] nump = new int[N];
    static int index;
    public static void insert(String x){
        int p = 0 ;
        //因为是一个字符串
        for(int i=0;i<x.length();i++){
            char s = x.charAt(i);
            int j = s-'a';
            //判断当前值
            if(son[p][j] == 0){
                index++;
                son[p][j] = index;//当前节点
            }
            p = son[p][j];
        }
        //当一个字符串结束的时候
        nump[p]++;//p的节点
    }

    public static int query(String x) {
        //不更改树
        int p=0;
        for(int i=0;i<x.length();i++){
            char s = x.charAt(i);
            int j = s-'a';
            if(son[p][j] == 0){
                return 0;
            }
            p = son[p][j];//走到儿子哪里,继续下一个
        }
        return nump[p];
    }

    public static void main(String[] args) throws IOException {
        BufferedReader reader =new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.valueOf(reader.readLine());
        for(int i=0;i<n;i++){
            String[] str = reader.readLine().split(" ");
            if(str[0].equals("I")){
                String action = str[1];
                insert(action);
            }else {
                String action2 = str[1];
                System.out.println(query(action2));
            }
        }
    }
}

  • 并查集

1.快速的操作
1.将两个集合合并。
2.询问两个元素是否在一个集合中。

暴力解决:
1.数组存储
2.belong
3.合并元素比较费时间

解决方法:
1.快速的进行合并
2.快速的查找
3.近乎O1
基本原理
1.用树来维护集合-多叉树
2.集合的编号是代表元素,当前集合的编号就是根节点的编号
3.连续的网上找father,直到找到编号。
每个节点存储它的父节点。p[x] 存储的x的父节点

关键:

1.判断树根: if(p[x] = x)
2.怎么求x的集合编号 while()//遍历-可以优化
3.合并集合 p  让某一个集合p【】

在这里插入图片描述

具体过程

find函数是最重要的。
1.初始化
在这里插入图片描述

1.初始化,所有的节点都指向自己
p[x] = x

2.找祖宗+路径压缩
路径压缩在回溯中起到作用。

在这里插入图片描述

2.找祖宗时,使用递归的方式进行回溯。并且进行路径的压缩
find(x){
	if(x != p[x]){
		p[x] = find(p[x])
	}
	return p[x]
}

3.合并
到各种题目中合并的条件不同。
在这里插入图片描述
3.题目:
合并时,按照条件

p[x] = p[y]

模板:

  • (1)朴素并查集:
int p[N]; //存储每个点的祖宗节点

// 返回x的祖宗节点
int find(int x)
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ ) p[i] = i;

// 合并a和b所在的两个集合:
p[find(a)] = find(b);

题目:

package 大学菜;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class 并查集合并集合 {
    static int N = 100010;
    static int[] p =  new int[N];
    public static int find(int x){
        if(p[x] !=x ){
            p[x]=find(p[x]);
        }
        return p[x];
    }
    public static void merge(int a,int b){
        int x = find(a);//a的爸爸
        int y = find(b);//b的爸爸
        //p[find(a)] = find(b);
        p[x] = y;//让a的爸爸⬇x,让x的爸爸指向y

    }
    public static boolean query(int a,int b){
        if(find(a)==find(b))return true;
        return false;
    }
    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        String[] str = reader.readLine().split(" ");
        int n =Integer.valueOf(str[0]);
        int m = Integer.valueOf(str[1]);
        //初始化,指向自己
        for(int i=0;i<n;i++){
            p[i]= i;
        }
        //输入
        for(int i=0;i<m;i++){
            String[] s = reader.readLine().split(" ");
            String action = s[0];
            if(action.equals("Q")){
                int a = Integer.valueOf(s[1]);
                int b = Integer.valueOf(s[2]);
                boolean A= query(a,b);
                if(A){System.out.println("YES");}else {System.out.println("NO");}
            }else {
                int a = Integer.valueOf(s[1]);
                int b = Integer.valueOf(s[2]);
                merge(a,b);
            }
        }
    }
}

  • 维护连通点个数的并查集,也就是集合个数

模板:
int p[N], size[N];
//p[]存储每个点的祖宗节点, size[]只有祖宗节点的有意义,表示祖宗节点所在集合中的点的数量

// 返回x的祖宗节点
int find(int x)
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ )
{
    p[i] = i;
    size[i] = 1;
}

题目:837. 连通块中点的数量
代码:
iava

1.size[]
2.p[]
3.初始化, 自己指向自己,size =1
4.判读插入的时候特判。
package 大学菜;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

//给定一个包含 n 个点(编号为 1∼n)的无向图,初始时图中没有边。
//现在要进行 m 个操作,操作共有三种:
//C a b,在点 a 和点 b 之间连一条边,a 和 b 可能相等;
//Q1 a b,询问点 a 和点 b 是否在同一个连通块中,a 和 b 可能相等;
//Q2 a,询问点 a 所在连通块中点的数量;
public class 并查集连通点2 {
    //连一条边--画在一个集合里
    //询问是否在一个连通块--询问是否在一个集合里
    static int N = 100010;
    static int[] p = new int[N];
    //static int[] ss = new int[N];
    static int size[] = new int[N];//集合中点的数量。
    public static int find(int x){
        if(p[x] != x) {
            return find(p[x]);
        }else return p[x];

    }
    public static void Cinsert(int a,int b){
        // 合并a和b所在的两个集合:
        int x = find(a);
        int y = find(b);
        size[y] = size[y]+size[x];//在合并的时候直接加上合并
        //再进行合并
        p[find(a)] = find(b);

    }
    public static boolean Qsame(int a,int b){
        if(find(a) == find(b))return true;
        else return false;
    }

    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        String[] str = reader.readLine().split(" ");
        int n = Integer.valueOf(str[0]);
        int m = Integer.valueOf(str[1]);
        //初始化
        for(int i=0;i<=n;i++){
            p[i] = i;
            size[i] = 1;//初始化。
        }
        for(int i=0;i<m;i++){
            String[] s = reader.readLine().split(" ");
            String action = s[0];
            if(action.equals("C")){
                int a = Integer.valueOf(s[1]);
                int b = Integer.valueOf(s[2]);
                if(find(a) == find(b)) continue;//当两个元素相同时就不用那个了
                Cinsert(a,b);
            }else if(action.equals("Q1")){
                int a = Integer.valueOf(s[1]);
                int b = Integer.valueOf(s[2]);
                if(Qsame(a,b)){
                    System.out.println("Yes");
                }else{
                    System.out.println("No");
                };
            }else {
                int a = Integer.valueOf(s[1]);
                System.out.println(size[find(a)]);
            }
        }
    }

}

  • 堆:


 -
1.如何建堆(大顶,小顶)
2.求集合中的最小值。
3.删除最小值。
4.删除任意一个值
5.修改人一个值

结构:
1.完全二叉树。
除了最后一层,其他的节点都是满的,且都是从左到右依次排列的。

小顶堆,根节点是最小的值。

2.堆存储
全新的存储方式,使用的是一维数组存储堆。
根,左右,根左右

down()往下调整 大的往下走
up()往上调整 小的往上走

插入的方法:
1.最后一个位置++,放置元素,进行换的操作
删除最小的元素
2.将最后的一个元素放置到堆顶,因为堆顶是最小的元素,heap[size] = heap[1] 进行覆盖,再进行down操作。

3.删除任意一个元素k时,heap[k] = heap[size] 看大小进行选择down或者是up

下标注意:从1开始
在这里插入图片描述
1/从最后一个非也节点开始进行调整
2x=1,2x=2

模拟堆:

package 大学菜;

import java.io.*;

public class 模拟堆找错误 {
    static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
    static BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out));
    static int N = 1000010;
    static int[] h = new int[N];
    static int[] ph = new int[N];
    static int[] hp = new int[N];
    static int size;

    public static void main(String[] args) throws IOException {
        int n = Integer.valueOf(reader.readLine());
        size =0;
        int m= 0;
        while (n-->0){
            String[] s = reader.readLine().split(" ");
            String op = s[0];
            if(op.equals("I")){
                int x = Integer.valueOf(s[1]);
                m++;
                h[++size] = x;
                ph[m] = size;
                hp[size] = m;
                up(size);//刚进来往上调整
            }else if(op.equals("PM")){
                writer.write(h[1]+"\n");
            }else if(op.equals("DM")){
                heap_swap(1,size);
                size--;
                down(1);
            }else if(op.equals("D")){
                int k = Integer.valueOf(s[1]);
                int u =ph[k];
                heap_swap(u,size);
                size--;
                up(u);
                down(u);
            }else if(op.equals("C")){
                int k = Integer.valueOf(s[1]);
                int x = Integer.valueOf(s[2]);
                int u = ph[k];
                h[u] = x;
                down(u);
                up(u);
            }
        }
        writer.flush();
    }
    public static void heap_swap(int u,int v){
        swap(h,u,v);
        swap(hp,u,v);
        swap(ph,hp[u],hp[v]);
    }
    public static void swap(int[] a,int u ,int v){
        int tmp = a[u];
        a[u] = a[v];
        a[v] = tmp;
    }
    public static void down(int u){
        int t = u;
        if(u*2<=size && h[t] >h[u*2]) t=u*2;
        if(u*2+1 <=size && h[t] >h[u*2+1]) t= u*2+1;
        if(u !=t){
            heap_swap(u,t);
            down(t);
        }
    }
    public static void up(int u){
        if(u/2>0 && h[u]<h[u/2]){
            heap_swap(u,u/2);
            up(u/2);
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值