ACwing:算法基础课 第二章Trie树,并查集,堆听课笔记

本文详细介绍了数据结构中的Trie树、并查集和堆的原理与应用。Trie树用于高效存储和查找字符串集合,通过路径压缩实现快速插入和查询。并查集用于快速合并集合和判断元素是否在同一集合中,通过路径压缩优化了操作效率。堆是一种特殊树形数据结构,常用于实现优先队列,文中介绍了堆的插入、删除、查找等操作及其维护方法。
摘要由CSDN通过智能技术生成

一。Trie 树

**1.基本作用:**高效地存储和查找字符串集合的数据结构。用Trie树存储字符串的时候,字符串一般都是全小写或者是全大写并且字母的个数不会很多即限制只有26个或52个。

例如:在这里插入图片描述
构造的Trie树如下:

在这里插入图片描述
注意,在每一个单词的最后一个字母后面打一个标记星号,说明到此为一个单词的结束。即要把每一个单词的最后一个字母打标记

2.代码如下:

#include<iostream>
using namespace std;
const int N=100010;

//son表示N个节点的子节点,因为字符串都是小写字母,所以一个节点最多就只有26个子节点
//cnt[N]表示以N字符结束的单词有多少个,即结尾的标记
//idx 表示给当前节点分配一个序号下标

int son[N][26],cnt[N],idx;//注意下表为0的点,既是根节点又是空节点 
char str[N];//存储输入的字符串


//存储字符串 
void insert(char str[])//str[]存储的是字符串
{
	int p=0;//指向根节点
	for(int i=0;str[i];i++)//因为字符串的最后一个字符是0,所以可以这样去判断 
	{
		int u=str[i]-'a';//提取出字符串中的每一个字符,转换到0-25
		if(!son[p][u]) son[p][u]=++idx;//如果没有就创建
		p=son[p][u];//然后就指向下一个,注意这里没有else,因为无论如何都要指向下一个
	}
	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;//如果没有的话就直接return
		p=son[p][u];//注意这个没有else,因为无论如何都要指向下一个
	}
	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 printf(%d\n”,query(str));
    }
    return 0;
}

二。并查集

思维强,短小代码,面试喜欢

1.作用:并查集可以快速的处理如下问题:
(1) 将两个集合合并
(2) 询问两个元素是否在一个集合当中

可以近乎在O(1)的时间内完成上述操作

2.基本思想:每个集合用一颗树来表示。树根的编号就是整个集合的编号。每个节点存储它的父节点,p[x]表示x的父节点

3.解决的问题:
(1) 如何判断树根:if(p[x]==x)
(2) 如何求x的集合的编号:while(p[x]!=x) x=p[x] 即x不是树根,那么就一直往上走,直到找到了树根
(3) 如何合并两个集合——加一条边就行,即把一个集合插到另外一个集合就行:假设px是x的集合编号,py是y的集合编号。p[x]=y。此时的优化操作为路径压缩:即找x的根节点的时候,一直往上走的时候,同时把父节点改成根节点。

在这里插入图片描述
4. 代码如下:

#include<iostream>
using namespace std;
const int N=100010;

int p[N];//存的为祖宗节点 ,即每一个点所属的一个集合父节点
int 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;
		scnaf("%s%d%d",op,&a,&b);//%s可以过滤掉空格和回车 
		
		if(op[0]=='M') p[find(a)]=find(b);//合并集合,即是将a的祖宗节点的父节点指向b集合的祖宗节点 
		else//判断两个集合是否是同一个集合,即判断他们的祖宗节点是否相同 
		{
			if(find(a)==find(b)) puts("Yes");
			else puts("No");
		} 
	}
	
	return 0; 
}
 

5.扩展:在更新路径的时候如何维护额外的信息之统计每一个集合内的点的数量,还可以维护节点到根节点的距离

维护一个数组size记录每个集合的节点个数,且这个数组只需要保证根节点的size有效即可
在这里插入图片描述
代码如下:

#include<iostream>
using namespace std;
const int N=100010;

int p[N];//存的为祖宗节点 ,即每一个点所属的一个集合父节点
int size[N] ;//用来记录每一个集合内点的数量,只保证根节点的size有意义即可 
int 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;//将每一个节点初始化为自己 
	    size[i]=1;//每一个集合只有自己的一个点 
	}
	
	while(m--){
		char op[5];
		int a,b;
		scnaf("%s",op);//%s可以过滤掉空格和回车 
		
		if(op[0]=='C') 
		{
			scanf("%d%d",&a,&b); 
			//如果a和b已经在一个集合里面,则忽略,直接continue 
			if(find(a)==find(b)) continue; 
		    p[find(a)]=find(b);//合并集合,即是将a的祖宗节点的父节点指向b集合的祖宗节点 
		    //合并之后要更新根节点的集合的数量
			size[find(b)]+=size[find(a)] ;
			
     	}
		else if(op[1]=='1')//判断两个集合是否是同一个集合,即判断他们的祖宗节点是否相同 
		{
			scanf("%d%d",&a,&b);
			if(find(a)==find(b)) puts("Yes");
			else puts("No");
		}
		else//询问某一个集合中点的数量 
		{
			scanf("%d",&a);
			printf("%d\n",size[find(a)]);
		}
	}
	
	return 0; 
}

三。堆:如何手写一个堆

1.堆的基本操作

(1)插入一个数
(2)求集合当中的最小值
(3)删除最小值
(4)删除任意一个元素
(5)修改任意一个元素

2.堆的基本结构

是一棵完全二叉树,即除了最后一层之外,其它层的节点都是满的
小根堆:根节点小于等于左右两个子节点
大根堆:根节点大于等于左右两个子节点

3.堆的存储

用一个一维数组来存根节点和左右节点的下标表示如下:并且下标要从1开始标
在这里插入图片描述
4. down(x)
顾名思义即往下调整,即如果在小根堆里面有一个值变大了,那么就要往下调整,此时要和子两个节点一起比较

5.up(x)
顾名思义即向上调整,即如果在大根堆里面有一个值变小了,就要往上调整,只需要和父节点进行比较就可以

6.利用up和down两个操作去完成堆的所有操作

(1)插入一个数
heap[++size]; up[size];
即在末尾加入一个数,然后往上调整,直到插入到它应该在的位置

(2)求集合当中的最小值
小根堆里面就是heap[1]

(3)删除最小值
利用一个技巧:就是让最后一个点覆盖掉第一个点,然后size–,因为删除最后一个点是比较容易的
heap[1]=heap[size];size–;
然后再维护down(1),就是让根节点往下调整,调整到它应该在的位置

(4)删除任意一个元素
heap[k]=heap[size];
size–;
然后更新维护:down(x)或者up(x)
可以一起写,但只会执行一个

(5)修改任意一个元素
heap[size]=k;down(x)

在这里插入图片描述

7。 如何把数组变成堆

 for(int i=n/2;i;i--) down(i);

8 。down操作

#include <iostream>
#include <algorithm>
using namespace std;

const int N=100010;
int n,m;
int h[N],size;

void down(int u)
{
	int t=u;//t表示根节点和其子女节点的最小值
	if(u*2<=size&&h[u*2]<h[t]) t=u*2;//左子 
	if(u*2+1<=size&&h[u*2+1]<h[t]) t=u*2+1;//右子
	if(u!=t)//说明调整了
	{
		swap(h[u],h[t]); 
		down(t);
	 } 
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&h[i]);
	size=n;
	for(int i=n/2;i;i--) down(i);//将数组变成堆
	while(m--)
	{
		printf("%d",h[1]) ;//输出最小值
		h[1]=h[size];
		size--;
		down(1);//删除最小元素 
	 } 
}

8 。up操作

只需要和父节点相比


void up(int u)
{
	while(u/2&&h[u/2]>h[u]) //与父节点比较 
	{
		swap(h[u/2],h[u]);
		u/=2;
	}
 } 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值