二叉树+并查集作业

目录

作业

1.P1827 [USACO3.4] 美国血统 American Heritage​​​​​​

1.1题目

1.2思路

1.3核心代码

1.4完整代码

2.P1305 新二叉树

2.1题目

2.2思路

2.3完整代码

3.P1030 [NOIP2001 普及组] 求先序排列

3.1题目

3.2思路

3.3完整代码

4.P4913 【深基16.例3】二叉树深度

4.1 题目

4.2 思路

4.3核心代码

4.4完整代码

5.P1229 遍历问题

5.1 题目

5.2思路

5.3完整代码

6.P3367 【模板】并查集

6.1题目

6.2思路

6.3核心代码

6.4完整代码

7.P1551 亲戚

7.1题目

7.2思路

7.3完整代码

8.P2078 朋友

8.1题目

8.2思路

8.3核心代码

8.4完整代码

9.P1111 修复公路

9.1题目

9.2思路

9.3完整代码


作业

1.P1827 [USACO3.4] 美国血统 American Heritage​​​​​​

1.1题目

题目描述

农夫约翰非常认真地对待他的奶牛们的血统。然而他不是一个真正优秀的记帐员。他把他的奶牛 们的家谱作成二叉树,并且把二叉树以更线性的“树的中序遍历”和“树的前序遍历”的符号加以记录而 不是用图形的方法。

你的任务是在被给予奶牛家谱的“树中序遍历”和“树前序遍历”的符号后,创建奶牛家谱的“树的 后序遍历”的符号。每一头奶牛的姓名被译为一个唯一的字母。(你可能已经知道你可以在知道树的两 种遍历以后可以经常地重建这棵树。)显然,这里的树不会有多于 2626 个的顶点。

这是在样例输入和样例输出中的树的图形表达方式:

         C
         /  \
        /  \
       B    G
      / \  /
       A   D  H
        / \
       E   F

附注:

  • 树的中序遍历是按照左子树,根,右子树的顺序访问节点;
  • 树的前序遍历是按照根,左子树,右子树的顺序访问节点;
  • 树的后序遍历是按照左子树,右子树,根的顺序访问节点。

输入格式

第一行一个字符串,表示该树的中序遍历。

第二行一个字符串,表示该树的前序遍历。

输出格式

单独的一行表示该树的后序遍历。

输入输出样例

输入 #1复制

ABEDFCHG
CBADEFGH 

输出 #1复制

AEFDBHGC

说明/提示

题目翻译来自NOCOW。

USACO Training Section 3.4

1.2思路

根据中序遍历和前序遍历,来递归划分左子树和右子树,再打印根结点以此来模拟后序遍历

1.3核心代码

递归:前序的第一个为根结点,找到根来划分中序遍历的左、右子树。

遍历左子树:前序遍历的左子树为去掉前一个数(为根结点),前序遍历左边界为l1+1;右边界为 左边界( l1 + 1)加上左子树的个数( i - l2+1)。 右子树的左边界l2,右边界结点 i -1。 

遍历右子树:前序遍历左边界为左子树的右边界( l1 + i - l2 )+1;右边界为r1, 右子树的左边界 i + 1,右边界结点 r2 。 

void PostOrder(int l1, int r1, int l2, int r2)//l1,r1为前序的左右边界,l2,r2为后序的左右边界
{
	if (l1 > r1 || l2 > r2)
	{
		return;
	}
	for (int i = l2; i <= r2; i++)
	{
		if (in[i] == pre[l1])
		{
			PostOrder(l1 + 1, l1 + i - l2, l2, i - 1);//根据左右子树的个数计算,遍历左子树,左右根
			PostOrder(l1 + i - l2 + 1, r1, i + 1, r2);//遍历右子树
			printf("%c", pre[l1]);//打印根结点
		}
	}
}

1.4完整代码

#include <stdio.h>
#include <string.h>
char pre[30];
char in[30];
void PostOrder(int l1, int r1, int l2, int r2)//l1,r1为前序的左右边界,l2,r2为后序的左右边界
{
	if (l1 > r1 || l2 > r2)
	{
		return;
	}
	for (int i = l2; i <= r2; i++)
	{
		if (in[i] == pre[l1])
		{
			PostOrder(l1 + 1, l1 + i - l2, l2, i - 1);//根据左右子树的个数计算,遍历左子树,左右根
			PostOrder(l1 + i - l2 + 1, r1, i + 1, r2);//遍历右子树
			printf("%c", pre[l1]);//打印根结点
		}
	}
}
int main()
{
	scanf("%s", in);
	scanf("%s", pre);
	int n = strlen(in) - 1;
	PostOrder(0, n, 0, n);
	return 0;
}

2.P1305 新二叉树

2.1题目

2.2思路

构造树结构,再根据前序遍历树来打印

2.3完整代码

#include <stdio.h>
struct BiTNode
{
	char data;
	char lchild;
	char rchild;
}tree[300];
void ProOrder(char ch)
{
	if (ch != '*')
	{
		printf("%c", ch);
		ProOrder(tree[ch].lchild);
		ProOrder(tree[ch].rchild);
	}
}
int main()
{
	int n;
	scanf("%d", &n);
	char ch;
	for (int i = 0; i < n; i++)//也可以只记录根结点,之后只记录左右,只需要根据根结点遍历左右结点
	{
		char a, b, c;
		scanf(" %c%c%c", &a, &b, &c);
		if (i == 0)
		{
			ch = a;//存根结点字母
		}
		tree[a].data = a;
		tree[a].lchild = b;
		tree[a].rchild = c;
	}
	ProOrder(ch);
	return 0;
}

3.P1030 [NOIP2001 普及组] 求先序排列

3.1题目

题目描述
给出一棵二叉树的中序与后序排列。求出它的先序排列。(约定树结点用不同的大写字母表示,且二叉树的节点个数 

8
≤8)。

输入格式
共两行,均为大写字母组成的字符串,表示一棵二叉树的中序与后序排列。

输出格式
共一行一个字符串,表示一棵二叉树的先序。

输入输出样例
输入 #1复制
BADC
BDCA
输出 #1复制
ABCD
说明/提示
【题目来源】

NOIP 2001 普及组第三题

3.2思路

与第一道,思路类似,根据中序遍历和后序遍历,打印根结点,来递归划分左子树和右子树,以此来模拟前序遍历

3.3完整代码

#include <stdio.h>//跟第一题同样的方法,
#include <string.h>
char post[30];
char in[30];
void PostOrder(int l1, int r1, int l2, int r2)//l1,r1为后序序列边界,l2,r2为中序序列边界
{
	if (l1 > r1 || l2 > r2)
	{
		return;
	}
	for (int i = l2; i <= r2; i++)
	{
		if (in[i] == post[r1])
		{
			printf("%c", post[r1]);
			PostOrder(l1, l1 + i - l2 - 1, l2, i - 1);
			PostOrder(l1 + i - l2, r1 - 1, i + 1, r2);
		}
	}
}
int main()
{
	scanf("%s", in);
	scanf("%s", post);
	int n = strlen(in) - 1;
	PostOrder(0, n, 0, n);
	return 0;
}

4.P4913 【深基16.例3】二叉树深度

4.1 题目

4.2 思路

先根据输入的来构造树,然后再用后序遍历的变种,来探寻树的深度。

4.3核心代码

遍历左右子树,一直递归到叶子结点,直到为空结点,输出0,再迭代回去,返回左右子树的深度的上一层+1

int depth(int t)
{
	if (t == 0)
	{
		return 0;
	}
	else
	{
		int l = depth(tree[t].lchild);
		int r = depth(tree[t].rchild);
		return l > r ? l + 1 : r + 1;
	}
}

4.4完整代码

#include <stdio.h>
struct BitNode
{
	int lchild;
	int rchild;
}tree[1000010];
int depth(int t)
{
	if (t == 0)
	{
		return 0;
	}
	else
	{
		int l = depth(tree[t].lchild);
		int r = depth(tree[t].rchild);
		return l > r ? l + 1 : r + 1;
	}
}
int main()
{
	int n;
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
	{
		scanf("%d %d", &tree[i].lchild, &tree[i].rchild);
	}
	int m = depth(1);
	printf("%d\n", m);
	return 0;
}

5.P1229 遍历问题

5.1 题目

5.2思路

由前序遍历和后序遍历不能确定树的结构,树的只有单个孩子的结点n会影响中序遍历的顺序,单个孩子在左还是在右,有两种情况,所以中序遍历的总数为 2的n次方

5.3完整代码

#include <stdio.h>//找单个孩子的结点数sum,因为孩子可以在左,也可以在右,总个数为2的sum次方,规律题
#include <string.h>
int main()
{
	char arr1[300];
	char arr2[300];
	scanf("%s", arr1);
	scanf("%s", arr2);
	int sum = 0;
	int n = strlen(arr1);
	for (int i = 0; i < n; i++)
	{
		for (int j = 1; j < n; j++)//注意j从1开始防止越界了
		{
			if (arr1[i] == arr2[j] && arr1[i + 1] == arr2[j - 1])//规律 AB与BA 则B为单个孩子,前序为左孩子,后序中为右孩子,则为单个。
			{
				sum++;
			}
		}
	}
	int ans = 1;
	for (int i = 0; i < sum; i++)
	{
		ans *= 2;
	}
	printf("%d\n", ans);
	return 0;
}

6.P3367 【模板】并查集

6.1题目

6.2思路

利用数组来模拟树结构实现并查集,当z等于1时执行将集合合并的操作,当z等于2时执行查根的操作,看是否属于集合

6.3核心代码

初始化并查集

	int n, m;
	int arr[20010] = { 0 };
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= n; i++)//每个独立的集合,本身就只有一个,所以存入-1;
	{
		arr[i] = -1;
	}

实现查找操作Find,并且需要路径压缩来降低时间复杂度

int Find(int s[], int x)
{
	int root = x;
	while (s[root] >= 0)
	{
		root = s[root];
	}
	while (x != root)
	{
		int t = s[x];
		s[x] = root;
		x = t;
	}
	return root;
}

实现合并操作Union,需要先分别找到根结点,将小树挂到大树上,并且加上小树的结点数。

void Union(int s[], int root1, int root2)
{
	if (root1 == root2)
	{
		return;
	}
	if (s[root1] < s[root2])
	{
		s[root1] += s[root2];
		s[root2] = root1;
	}
	else
	{
		s[root2] += s[root1];
		s[root1] = root2;
	}
}

6.4完整代码

#include <stdio.h>
int Find(int s[], int x)
{
	int root = x;
	while (s[root] >= 0)
	{
		root = s[root];
	}
	while (x != root)
	{
		int t = s[x];
		s[x] = root;
		x = t;
	}
	return root;
}

void Union(int s[], int root1, int root2)
{
	if (root1 == root2)
	{
		return;
	}
	if (s[root1] < s[root2])
	{
		s[root1] += s[root2];
		s[root2] = root1;
	}
	else
	{
		s[root2] += s[root1];
		s[root1] = root2;
	}
}
int main()
{
	int n, m;
	int arr[20010] = { 0 };
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= n; i++)//每个独立的集合,本身就只有一个,所以存入-1;
	{
		arr[i] = -1;
	}
	while (m--)
	{
		int z, x, y;
		scanf("%d %d %d", &z, &x, &y);
		int a = Find(arr, x);
		int b = Find(arr, y);
		if (z == 2)
		{
			if (a == b)
			{
				printf("Y\n");
			}
			else
			{
				printf("N\n");
			}
		}
		if (z == 1)
		{
			Union(arr, a, b);
		}
	}
	return 0;
}

7.P1551 亲戚

7.1题目

7.2思路

与上道题目的思路类似,也是一道典型的并查集,实现查找和合并两个操作就可以

7.3完整代码

#include <stdio.h>
int s[5100] = { 0 };
int Find(int x)
{
	int root = x;
	while (s[root] >= 0)
	{
		root = s[root];
	}
	while (x != root)
	{
		int t = s[x];
		s[x] = root;
		x = t;
	}
	return root;
}
void Union(int root1, int root2)
{
	if (root1 == root2)
	{
		return;
	}
 s[root1]+=s[root2];
	s[root2] = root1;
}
int main()
{
	int n, m, p;
	scanf("%d %d %d", &n, &m, &p);
	for (int i = 1; i <= n; i++)
	{
		s[i] = -1;
	}
	while (m--)
	{
		int x, y;
		scanf("%d %d", &x, &y);
		int a, b;
		a = Find(x);
		b = Find(y);
		Union(a, b);
	}
	while (p--)
	{
		int x, y;
		scanf("%d %d", &x, &y);
		int a, b;
		a = Find(x);
		b = Find(y);
		if (a == b)
		{
			printf("Yes\n");
		}
		else
		{
			printf("No\n");
		}
	}
	return 0;
}

8.P2078 朋友

8.1题目

8.2思路

利用两个数组来记录男生和女生的关系并查集,将女的输入的数变为正数(负数 * -1),最后查找两个数组的根结点1所记录的总结点数,取两者之间的较小值(一夫一妻)。

8.3核心代码

必须确保1为根结点,挂到1上,这样才能确定1所含有的总结点数。

void Union(int s[], int root1, int root2)
{
	if (root1 == root2)
	{
		return;
	}
	if (root1 == 1 && root2 != 1)//确保1为根结点,才能计算
	{
		s[root1] += s[root2];
		s[root2] = root1;
		return;
	}
	if (root2 == 1 && root1 != 1)
	{
		s[root2] += s[root1];
		s[root1] = root2;
		return;
	}
	s[root1] += s[root2];
	s[root2] = root1;
}

8.4完整代码

#include <stdio.h>
int min(int a, int b)
{
	return a < b ? a : b;
}
int Find(int s[], int x)
{
	int root = x;
	while (s[root] >= 0)
	{
		root = s[root];
	}
	while (x != root)
	{
		int t = s[x];
		s[x] = root;
		x = t;
	}
	return root;
}
void Union(int s[], int root1, int root2)
{
	if (root1 == root2)
	{
		return;
	}
	if (root1 == 1 && root2 != 1)//确保1为根结点,才能计算
	{
		s[root1] += s[root2];
		s[root2] = root1;
		return;
	}
	if (root2 == 1 && root1 != 1)
	{
		s[root2] += s[root1];
		s[root1] = root2;
		return;
	}
	s[root1] += s[root2];
	s[root2] = root1;
}
int main()
{
	int s1[11000] = { 0 };
	int s2[11000] = { 0 };
	int n, m, p, q;
	scanf("%d %d %d %d", &n, &m, &p, &q);
	for (int i = 1; i <= n; i++)
	{
		s1[i] = -1;
	}
	for (int i = 1; i <= m; i++)
	{
		s2[i] = -1;
	}
	while (p--)
	{
		int x, y;
		scanf("%d %d", &x, &y);
		int a, b;
		a = Find(s1, x);
		b = Find(s1, y);
		Union(s1, a, b);
	}
	while (q--)
	{
		int x, y;
		scanf("%d %d", &x, &y);
		x = -x;
		y = -y;
		int a, b;
		a = Find(s2, x);
		b = Find(s2, y);
		Union(s2, a, b);
	}
	int ans = min(-s1[1], -s2[1]);
	printf("%d\n", ans);
	return 0;
}

9.P1111 修复公路

9.1题目

9.2思路

将按照时间顺序将这些操作数排序,按照时间来依次修路,根结点所记录的总根结点数为 -n 时(数组下标记录的总根结点数为负数),就说明全部通车 ,此时的时间也是最早的时间。如果不等于-n,则没有通车,打印-1。特别注意,排序那里必须用快速排序,不然时间复杂度太高,题目过不去

9.3完整代码

#include <stdio.h>
struct gonglu
{
	int a;
	int b;
	int t;
}arr[100010];
int s[11000] = { 0 };
int Find(int x)
{
	int root = x;
	while (s[root] >= 0)
	{
		root = s[root];
	}
	while (x != root)
	{
		int t = s[x];
		s[x] = root;
		x = t;
	}
	return root;
}
void Union(int root1, int root2)
{
	if (root1 == root2)
	{
		return;
	}
	if (s[root1] <= s[root2])
	{
		s[root1] += s[root2];
		s[root2] = root1;
	}
	else
	{
		s[root2] += s[root1];
		s[root1] = root2;
	}
}
void quick(int l, int r)
{
	if (l > r)
	{
		return;
	}
	int i, j;
	int temp;
	temp = arr[l].t;
	struct gonglu t = arr[l];
	i = l;
	j = r;
	while (i != j)
	{
		while (arr[j].t >= temp && i < j)
		{
			j--;
		}
		while (arr[i].t <= temp && i < j)
		{
			i++;
		}
		if (i < j)
		{
			struct gonglu tem = arr[i];
			arr[i] = arr[j];
			arr[j] = tem;
		}
	}
	arr[l] = arr[i];
	arr[i] = t;
	quick(l, i - 1);
	quick(i + 1, r);
	return;
}
int main()
{
	int n, m;
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= m; i++)
	{
		scanf("%d %d %d", &arr[i].a, &arr[i].b, &arr[i].t);
	}
	quick(1, m);//必须得快排,不然时间复杂度太高了
	for (int i = 1; i <= n; i++)
	{
		s[i] = -1;
	}
	for (int i = 1; i <= m; i++)
	{
		int x = Find(arr[i].a);
		int y = Find(arr[i].b);
		Union(x, y);
		if (s[Find(1)] == -n)
		{
			printf("%d\n", arr[i].t);
			return 0;
		}
	}
	printf("-1\n");
	return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

エース和

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值