2023.1.4(总结)

一,并查集

题目描述

如题,现在有一个并查集,你需要完成合并和查询操作。

输入格式

第一行包含两个整数 N,MN,M ,表示共有 NN 个元素和 MM 个操作。

接下来 MM 行,每行包含三个整数 Z_i,X_i,Y_iZi​,Xi​,Yi​ 。

当 Z_i=1Zi​=1 时,将 X_iXi​ 与 Y_iYi​ 所在的集合合并。

当 Z_i=2Zi​=2 时,输出 X_iXi​ 与 Y_iYi​ 是否在同一集合内,是的输出 Y ;否则输出 N 。

输出格式

对于每一个 Z_i=2Zi​=2 的操作,都有一行输出,每行包含一个大写字母,为 Y 或者 N 。

输入输出样例

输入 #1复制

4 7
2 1 2
1 1 2
2 1 2
1 3 4
2 1 4
1 2 3
2 1 4

输出 #1复制

N
Y
N
Y

说明/提示

对于 30\%30% 的数据,N \le 10N≤10,M \le 20M≤20。

对于 70\%70% 的数据,N \le 100N≤100,M \le 10^3M≤103。

对于 100\%100% 的数据,1\le N \le 10^41≤N≤104,1\le M \le 2\times 10^51≤M≤2×105,1 \le X_i, Y_i \le N1≤Xi​,Yi​≤N,Z_i \in \{ 1, 2 \}Zi​∈{1,2}。

分析:

1,并查集主要就是两个操作,合并和查询。

2,合并的话,主要就是找根节点,如果没有根节点,那自己就作为根节点。

3,查询,就是根据需要查询的两个数去找根结点,如果根节点相同,那就在同一集合内,如果不相同,那就不在同一集合内。

4,无论是合并还是查询都有最基础的模板,跟着模板套进去就行。

合并模板如下:

void join(int x, int y) {
	int fx = find(x);
	int fy = find(y);
	if (fx!=fy){
		pre[fx] = fy;
		//合并集合,计算个数
		//size[fy] += size[fx];
	}
}

查询模板如下(查询又称作压缩路径,就是把得到的数最终让他们拥有同一个根节点

int find(int x)
{
	if(pre[x]==0)return x;
	return pre[x]=find(pre[x]);
}

代码如下:

c++

#include<iostream>
#include<cstdio>
using namespace std;
int n,m,z,x,y,pre[10005];
int find(int x)//找爸爸+路径压缩
{
	if(pre[x]==0)return x;
	return pre[x]=find(pre[x]);
}
void bcj(int x,int y)//并查集
{
	int x1=find(x),y1=find(y);//找爸爸
	if(x1!=y1)pre[y1]=x1;//合并
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		cin>>z>>x>>y;
		if(z==1)bcj(x,y);//如果z==1就合并集合,否则就判断集合是否一样
		else if(find(x)==find(y))cout<<"Y"<<endl;
			  else cout<<"N"<<endl;
	}
	return 0;
}

c

#include<stdio.h>
#include<math.h>
#include<string.h>
int find[200005];
int f(int f1)
{
    if(find[f1]!=0)
    return find[f1]=f(find[f1]);
    return f1;
}


int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=0;i<m;i++)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        if(a==1)
        {
            if(f(c)!=f(b))
            find[f(c)]=f(b);
        }
        else
        {
            if(f(c)==f(b))
            printf("Y\n");
            else 
            printf("N\n");
        }
    }
}

二,遍历问题

题目描述

我们都很熟悉二叉树的前序、中序、后序遍历,在数据结构中常提出这样的问题:已知一棵二叉树的前序和中序遍历,求它的后序遍历,相应的,已知一棵二叉树的后序遍历和中序遍历序列你也能求出它的前序遍历。然而给定一棵二叉树的前序和后序遍历,你却不能确定其中序遍历序列,考虑如下图中的几棵二叉树:

所有这些二叉树都有着相同的前序遍历和后序遍历,但中序遍历却不相同。

输入格式

输A数据共两行,第一行表示该二叉树的前序遍历结果s1,第二行表示该二叉树的后序遍历结果s2。

输出格式

输出可能的中序遍历序列的总数,结果不超过长整型数。

输入输出样例

输入 #1复制

abc                           
cba

输出 #1复制

4

分析:
1, 前序的遍历是根左右,后序的遍历是左右根,而中序的是左根右。

2,能根据前序和中序求出后序,或者能根据中序和后序求出前序,但是不能根据前序和后序求出中序,其原因是确定不了左右子树,在前序和后序中我们只能确定根,而确定不了根下的数是在左子树还是右子树。

3,根据二叉树的特性,每个根下面都有两个子树,在确定不了是左子树还是右子树的情况下,中序的可能是根节点下每层的子树的两倍。

4,这里有一个很明显的特征是,当我们不论左右节点,而只把前序和后序分为根和结点的时候,我们会发现,前序的前面是根,那么后面一个就一定是节点,而后序的根的前一个也一定是节点。

5,由此,我们可以推论出只要我找到一个根并且根下面有节点,那么根据二叉树的特性,我们就乘以2。

代码如下;

c

#include<stdio.h>
#include<math.h>
#include<string.h>
char a[1000],b[1000];
long long n=1;
void sort()
{
    int sa=strlen(a);
    for(int i=0;i<sa-1;i++)
    {
        for(int j=0;j<sa;j++)
        {
            if(a[i]==b[j]&&a[i+1]==b[j-1])
            n*=2;
        }
    }

}

int main()
{
    gets(a);
    gets(b);
    sort();
    printf("%lld",n);
}

三,【深基16.例3】二叉树深度

题目描述

有一个 n(n \le 10^6)n(n≤106) 个结点的二叉树。给出每个结点的两个子结点编号(均不超过 nn),建立一棵二叉树(根节点的编号为 11),如果是叶子结点,则输入 0 0

建好这棵二叉树之后,请求出它的深度。二叉树的深度是指从根节点到叶子结点时,最多经过了几层。

输入格式

第一行一个整数 nn,表示结点数。

之后 nn 行,第 ii 行两个整数 ll、rr,分别表示结点 ii 的左右子结点编号。若 l=0l=0 则表示无左子结点,r=0r=0 同理。

输出格式

一个整数,表示最大结点深度。

输入输出样例

输入 #1复制

7
2 7
3 6
4 5
0 0
0 0
0 0
0 0

输出 #1复制

4

分析:

1,因为它是要找最大值,那就说明它其实是在多个值里面找最大值,所以一开始就想到了深搜。

2,深搜的话,要找几个要素,分别是深搜的形参,结束递归的条件,以及如果找到最大值后保存下来。

3,首先先找深搜的形参,我们确定的是要有一个子树的值,还要有一个第几层的值,所以形参就是两个值,在最开始的时候,我们的第一个子树的值是1,因为我的数组从1开始,作为根节点,当然就是第一层。

4,然后找递归结束的条件,如果我找到的节点没有数据,也就是为0的时候,那就应该结束此次递归。

5,在什么时候比较呢?应该在判断的后面,如果在判断的前面,递归的层数肯定就会多1。

6,我们递归是分的两部分,左子树和右子树。

代码如下:

c

#include<stdio.h>
#include<math.h>
#include<string.h>
int n,m=0;
struct tt
{
    int l;
    int r;
}a[1000005];

void ss(int jd,int s)
{
    if(jd==0)
    return;
    m=m>s?m:s;
    ss(a[jd].l,s+1);
    ss(a[jd].r,s+1);
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&a[i].l,&a[i].r);
    }
    ss(1,1);
    printf("%d",m);
}

c++

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
struct node{
	int l,r;
}a[1001000];//记录每个节点的左右节点
int Max=-1,n;
void dfs(int root,int step){
	if(root==0) return;//如果该节点为0(即上它的爸爸没有这个儿子),返回
	Max=max(Max,step);//记录最大值
	dfs(a[root].l,step+1);//搜索它的左儿子
	dfs(a[root].r,step+1);//搜索它的右儿子
}
int main(){
	cin>>n;//输入n
	for(int i=1;i<=n;i++){
		cin>>a[i].l>>a[i].r;//输入该节点的左节点和右节点
	}
	dfs(1,1);//从1号节点,深度为1开始搜索 
	cout<<Max;//输出最大值
	return 0;
}

总结:

1,今天先是学习了并查集,并查集的话,主要就是合并查询,记住模板就行。

2,这三个题目里面有一个很重要的词就是压缩路径,压缩路径主要就是将二叉树使用数组表达出来,这不难,但是得好好理解理解。

3,只要知道中序,通过后序可以求出前序,通过前序可以求出后序,但是只要前序和后序,不难求出中序。

4,一般在使用二叉树的时候,会搭配递归一起使用,这是由于二叉树的特性,都是一层一层的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值