GPLT-天梯赛 二叉树&&二叉搜索树&&树的部分

1、7-12 这是二叉搜索树吗? (25 分)

在这里插入图片描述
思路:该题给出了二叉搜索树的前序遍历,然后题目要求我们判断他是否是一个二叉树,是的话就输出后序遍历,否则输出NO。对于前序二叉搜索树,我们可以知道先遍历左子树,再遍历右子树,那么从根节点开始往后找到第一个大于等于根节点的节点就是右子树根节点,同理从右往左找到第一个小于根节点的节点就是左子树的根节点,镜面则直接相反,开一个vector在搜完左树和右树后存下根节点,这个相当于模拟后序遍历过程了,然后判断是否是二叉搜索树,就直接让看vector的容量是否达到了n即可,总的来说是一道模拟题。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e3+10;
int n;
int t[maxn];
vector<int> v;
int flag;
void dfs(int l, int r){
	if(l>r) return;
	int i=l+1,j=r;
	if(!flag){
		while(i<=r&&t[l]>t[i]) i++;
		while(j>l&&t[l]<=t[j]) j--;
	}
	else{
		while(i<=r&&t[l]<=t[i]) i++;
		while(j>l&&t[l]>t[j]) j--;
	}
	dfs(l+1,j);
	dfs(i,r);
	v.push_back(t[l]);
}
int main(){
	cin >> n;
	for(int i=1; i<=n; i++) scanf("%d",&t[i]);
	dfs(1,n);
	if(v.size()==n){
		puts("YES");
		for(int i=0; i<v.size(); i++){
			if(i!=0) printf(" ");
			printf("%d",v[i]);
		}
	}
	else{
		flag=1;
		v.clear();
		dfs(1,n);
		if(v.size()==n){
			puts("YES");
			for(int i=0; i<v.size(); i++){
				if(i!=0) printf(" ");
				printf("%d",v[i]);
			}
		}
		else puts("NO");
	}
	return 0;
}

2、7-13 树的遍历 (25 分)
在这里插入图片描述
思路:根据后序和中序恢复树并求他的前序遍历,简单题

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+10;
int n;
int mid[maxn],beh[maxn];
struct node{
	int l,r;
}t[maxn];
int build(int lm, int rm, int lb, int rb){
	if(lm>rm) return 0;
	int u=beh[rb],i=lm;//注意一下这里的u树根节点的值
	while(i<=rm&&mid[i]!=u) i++;
	t[u].l=build(lm,i-1,lb,rb-(rm-i+1));
	t[u].r=build(i+1,rm,rb-(rm-i),rb-1);
	return u;
}
void bfs(int u){
	queue<int> q;
	q.push(u);
	int flag=0;
	while(!q.empty()){
		int tt=q.front();
		q.pop();
		if(flag) printf(" ");
		printf("%d",tt);
		flag=1;
		if(t[tt].l) q.push(t[tt].l);
		if(t[tt].r) q.push(t[tt].r);
	}
}
int main(){
	cin >> n;
	for(int i=1; i<=n; i++) cin >> beh[i];
	for(int i=1; i<=n; i++) cin >> mid[i];
	int root=build(1,n,1,n);
	bfs(root);
	return 0;
}

3、7-16 二叉搜索树的最近公共祖先 (25 分)在这里插入图片描述

思路:二叉树搜索树的一个经典问题,对于这道题而言,首先我们注意到键值是整数,也就是说会出现负数的情况,那么我们就不能恢复树,我们可以利用二叉搜索树的性质做题

首先,我们排除三种键值不存在的情况并输出答案,这个可以利用map完成
然后,就是我们利用二叉搜索树进行递归判断:
1、如果x和y其中有一个能够相等于当前最左端节点,那么x或y就是对方的祖先
2、如果x和y分别小于和大于当前根节点,那么就可以说明当前根节点是其祖先节点
3、以上情况没成立,那就找到左子树和右子树的分界点,进行下一步的递归判断

#include <bits/stdc++.h>
#define MAX 0x3f3f3f3f
using  namespace std;
typedef long long ll;
const int maxn = 1e5+10;
int n,m,k,x,y;
int a[maxn];
map<int,int> mp;
void judge(int l, int r){
	if(a[l]==x){
		printf("%d is an ancestor of %d.\n",x,y);
	}
	else if(a[l]==y){
		printf("%d is an ancestor of %d.\n",y,x);
	}
	else if((a[l]>x&&a[l]<y)||(a[l]>y&&a[l]<x)){
		printf("LCA of %d and %d is %d.\n",x,y,a[l]);
	}
	else{
		int i=r;
		while(i>l&&a[i]>a[l]) i--;//找到右子树和左子树的分界点 
		if(x<a[l]&&y<a[l]) judge(l+1,i);//全部属于左树,就到左树寻找 
		else judge(i+1,r);//否则全部去右树寻找 
	}
}
int main(){
	scanf("%d%d",&m,&n);
	for(int i=1; i<=n; i++){
		scanf("%d",&a[i]);
		mp[a[i]]++;	
	}
//	scanf("%d",&k);
	while(m--){
		scanf("%d%d",&x,&y);
		if(!mp[x]&&!mp[y]){
			printf("ERROR: %d and %d are not found.\n",x,y);
		}
		else if(!mp[x]){
			printf("ERROR: %d is not found.\n",x);
		}
		else if(!mp[y]){
			printf("ERROR: %d is not found.\n",y);
		}
		else{
			judge(1,n);
		}
	}
	return 0;
}

4、7-11 玩转二叉树 (25 分)
在这里插入图片描述
思路:经典建树,看代码,不解释

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+10;
struct node{
	int l,r;
}t[maxn];
int n;
int mid[maxn],pre[maxn];
int build(int lm, int rm, int lp, int rp){
	if(lm>rm) return 0;
	int i=lm,u=pre[lp];
	while(i<=rm&&mid[i]!=u) i++;
	t[u].l=build(lm,i-1,lp+1,rp-(rm-i));//注意一下这里的下标后前序有所不同
	t[u].r=build(i+1,rm,rp-(rm-i-1),rp);
	return u;
}
void bfs(int u){
	queue<int> q;
	q.push(u);
	int flag=0;
	while(!q.empty()){
		int tt=q.front();
		q.pop();
		if(flag) printf(" ");
		flag=1;
		printf("%d",tt);
		if(t[tt].r) q.push(t[tt].r);
		if(t[tt].l) q.push(t[tt].l);
	}
}
int main(){
	scanf("%d",&n);
	for(int i=1; i<=n; i++) cin >> mid[i];
	for(int i=1; i<=n; i++) cin >> pre[i];
	int root=build(1,n,1,n);
	bfs(root);
	return 0;
}

5、7-15 完全二叉树的层序遍历 (25 分)
在这里插入图片描述
思路:这题的思路比较奇怪,我也是参考了别人的代码写出来的。先乘2往下找,再乘2+1往下找,最后把他加到数组里,递归查找所生成的数组就是二叉树的层序遍历了。
这应该是一个规律题。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+10;
int t[maxn], res[maxn], n,cnt=1;
void dfs(int p){
	if(p>n) return;
	dfs(p<<1);
	dfs(p<<1|1);
	res[p]=t[cnt++];
}
int main(){
	scanf("%d",&n);
	for(int i=1; i<=n; i++) scanf("%d",&t[i]); 
	dfs(1);
	for(int i=1; i<=n; i++){
		if(i!=1) printf(" ");
		printf("%d",res[i]);
	}
	return 0;
}

6、7-8 完全二叉搜索树 (25 分)
在这里插入图片描述
思路:这题和上一题一样,找二叉搜索树的层序遍历,不过给的是一个序列,然后我们将他排成中序,恢复即可。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+10;
int t[maxn],a[maxn];
int n;
int cnt;
void dfs(int u){
	if(u>n) return;
	dfs(u<<1);
	t[u]=a[++cnt]; 
	dfs(u<<1|1);
}
int main(){
	cin >> n;
	for(int i=1; i<=n; i++) cin >> a[i];
	sort(a+1,a+1+n);
	dfs(1);
	for(int i=1; i<=cnt; i++){
		if(i!=1) printf(" ");
		printf("%d",t[i]);
	}
	return 0;
}

一个总结:
通过上两题和我们的规律寻找可以发现,我们可以通过完全二叉树的前序、中序、后序遍历恢复他的层序遍历,这个完全二叉树是包括了完全二叉搜索树的。下面是代码总结

一般完全二叉树的先序和后序和中序是会给你的,或者说是完全二叉树的话就考你快排可以得到一个
完全二叉搜素树的中序

//t是答案数组,a是信息数组(就是先序等序列)
void dfs(int u){
	if(u>n) return;
	//先序
	t[u]=a[++cnt]; 
	dfs(u<<1);
	dfs(u<<1|1);
	
	//中序
	dfs(u<<1);
	t[u]=a[++cnt]; 
	dfs(u<<1|1);

	//后序
	dfs(u<<1);
	dfs(u<<1|1);
	t[u]=a[++cnt]; 
}
这样我们的t数组就是层序遍历的结果

7、7-11 是否完全二叉搜索树 (25 分)
在这里插入图片描述
思路:首先要知道什么是完全二叉树。完全二叉树是从1~n都用满了的树,即中间没有空节点。所以我们先递归建好搜索树,然后再利用这一个性质直接从1到maxn遍历n个节点并看他是否是连续的即可,同时这个输出的也是层序遍历的顺序。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+10;
int t[maxn];
int a[maxn];
int n;
void build(int u, int val){
	if(t[u]==0){
		t[u]=val;
		return;
	}
	else if(val>t[u]) build(u<<1,val);
	else if(val<=t[u]) build(u<<1|1,val);
}
int main(){
	cin >> n;
	for(int i=1; i<=n; i++){
		int x;
		cin >> x;
		build(1,x);
	}
	int cnt=1;
	int flag=0;
	for(int i=1; i<=1e5+10; i++){
		if(t[i]!=0){
			if(cnt!=1) printf(" ");
			printf("%d",t[i]);
			if(cnt==n) break;
			cnt++;
		}
		else flag=1;
	}
	puts("");
	if(flag) puts("NO");
	else puts("YES");
	return 0;
}

8、7-9 二叉搜索树的2层结点统计 (25 分)
在这里插入图片描述

思路:直接建立二叉树,同时记录最大的节点,找到他对应的2次幂的范围就可以找到最下面两层节点的编号范围了,这样统计不为一个特殊数的节点个数就是最下面两层节点的个数了,当然会t,但是,我们合理剪枝,如果编号太大了,那就直接默认两个即可。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e7+10;
int n;
int t[maxn];
int a[maxn];
int maxx;
int flag;
void build(int u, int val){
	if(flag) return;
    if(u>=1000001){
        flag=1;
        return;
    }
	if(t[u]==-1001){
		t[u]=val;
		maxx=max(maxx,u);
		return;
	}
	else if(t[u]>=val) build(u<<1,val);
	else build(u<<1|1,val);
}
int main(){
	cin >> n;
	for(int i=1; i<=n; i++) scanf("%d",&a[i]);
	for(int i=0; i<=10000009; i++) t[i]=-1001;
//	cout << maxn  << endl;
	for(int i=1; i<=n; i++){
		build(1,a[i]);
	}
	int cnt=0;
	while(maxx>pow(2,cnt)-1){
		cnt++;
	}
//	cout << cnt << endl;
	int sum=0;
	for(int i=pow(2,cnt)-1; i>pow(2,cnt-2)-1; i--){
		if(t[i]!=-1001){
			sum++;
//			cout << i << endl;
		}
	}
	if(flag){
		printf("2");
		return 0;
	}
	printf("%d",sum);
	return 0;
}
### 完全二叉树的概念及其在天梯赛中的应用 #### 定义与性质 完全二叉树是一种特殊的二叉树结构,其定义为:对于深度为 \(D\) 的二叉树,如果有 \(N\) 个节点,并且这些节点的位置与相同深度的完美二叉树(即每一层节点数均达到最大值)的层序遍历序列中前 \(N\) 个位置一致,则该二叉树被称为完全二叉树[^1]。 这种数据结构具有以下重要特性: - 所有叶子节点仅可能出现在最深的两层上。 - 对于任意节点,如果它的右子存在,则左子也一定存在。 - 如果按照层次顺序编号,那么父节点与其子节点之间的关系可以通过简单的数学计算得出。例如,假设根节点编号为 1,则第 \(i\) 号节点的左孩子编号为 \(2i\),右孩子编号为 \(2i+1\),而其父节点编号为 \(\lfloor i/2 \rfloor\)。 #### 层次遍历算法实现 为了验证给定的一组数据是否构成一棵完全二叉树,通常采用队列辅助完成层次遍历操作。以下是基于 Python 实现的一个简单例子: ```python from collections import deque def is_complete_binary_tree(nodes): queue = deque() null_found = False for node in nodes: if node is None: null_found = True else: if null_found: return False, "Not a complete binary tree" queue.append(node.left) queue.append(node.right) while queue and not any(queue): queue.popleft() return len(queue) == 0, "Is a complete binary tree" class TreeNode: def __init__(self, val=0, left=None, right=None): self.val = val self.left = left self.right = right # Example usage with test case setup omitted here. ``` 上述代码片段展示了如何通过广度优先搜索来判断输入列表 `nodes` 是否表示一颗完整的二叉树。 #### 天梯赛相关题目解析 根据已知资料,在近几年的团体程序设计天梯赛 (GPLT) 中确实涉及到了一些关于完全二叉树的知识点。比如 L2 级别的某些试题可能会考察选手们对这类特殊形式二叉树的理解程度以及实际编码能力[^2]。 具体来说,像堆结构的操作往往依赖于完全二叉树模型之上;因此熟悉这一理论基础有助于解决诸如构建最小(大)堆等问题。另外还有可能出现要求模拟整个过程或者优化存储方式之类的挑战性较大的任务。 #### 总结 掌握好完全二叉树的基础概念和基本运算方法是非常重要的,这不仅能够帮助我们更好地理解其他高级的数据结构如堆、哈夫曼等,而且也是应对各类竞赛编程题目的必备技能之一。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值