作用:快速的【存储】和【查找】字符串集合的数据结构。
辅助称呼:前缀树,多叉树
结构:树
有一个根节点。
在每个串的结尾单词处进行标记。查找会更方便。
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);
}
}
}