PTA Complete Binary Search Tree 思路分析及代码解析

一、前导

1. 需要掌握的知识

  1. 完全二叉树、二叉搜索树
  2. 层序遍历
  3. 递归函数的应用

2. 题目信息

  1. 题目来源:PTA / 拼题A
  2. 题目地址:https://pintia.cn/problem-sets/16/problems/669

二、解题思路分析

1. 题意理解

  1. 输入数据
10		//完全二叉搜索树的结点数
1 2 3 4 5 6 7 8 9 0		//各结点的数值,根据这些结点建树
  1. 输出数据
6 3 8 1 5 7 9 0 2 4		//打印树的层序结构
  1. 题意
    根据录入的信息建立一棵完全二叉搜索树(同时满足 完全二叉树 和 二叉搜索树的特点),然后打印其层序结构。

2. 思路分析(重点)

  1. 从完全二叉搜索树的特点入手:(1)N个结点的完全二叉树,其左子树的个数是定值(2)对于二叉搜索树,左子树比根节点小,右子树比根节点大。子树也遵循该定义。根据 (1) (2) 就可以锁定根元素
  2. 若用数组存储树的层序结构,首元素就是根结点,以此类推
  3. 根据上述特点,构建函数找到根节点并存储
  4. 递归调用此函数:获取其左子树的根节点、右子树的根节点

三、具体实现

1. 弯路和bug

  1. 使用递归时,一定要加上返回语句
  2. 数据结构的特点往往就是突破口

2. 代码框架(重点)

2.1 采用的数据结构

使用数组存储:空间利用率高、运算方便;便于打印,数组顺序就是树的层序结构

2.2 程序主体框架

               程序伪码描述
int main()
{	
	0.int A[max],T[max]; 	// A[ ]用来存放输入的原始数值并从小到大排好序; T[ ]用来存放完全二叉搜索树的层序结构
	1.实现获取根节点的函数并递归调用 //core
	2.打印输出
}

2.3 各分支函数

  1. getRootValue( ) 本题解题的关键,核心函数
    1.1 A[ ]用来存放输入的原始数值并从小到大排序;T[ ]用来存放完全二叉搜索树的层序结构
    1.2 初始调用为 getRootValue(0,N-1,0),需要先弄明白初始调用中各参数的含义和原因
    第一个0,表示数组A的第一个元素 ALeft,对应树的最小值;N-1表示数组A的最后一个元素 ARight,对应树的最大值;第二个0,对应数组T的首元素 TRoot,表示树的树根
    1.3 根据 ‘N个结点的完全二叉树,左子树结点数是定值’ + ‘二叉搜索树,左小右大’,求得左子树的个数 L,进而确定根结点:A[ALeft+L],然后存储到T[0]
    1.4 递归调用 getRootValue,参数中分别录入 树最左结点(最小值)对应的数组序号、树最右结点(最大值)对应的数组序号、子树根的序号
int A[max],T[max]; 其中A是排序后的输入序列 ,T数组用于存放树的层序遍历
sort(A,A+N); //使用标准库函数从小到大排序

void getRootValue(int ALeft, int ARight, int TRoot)
{
	int n,L,LeftTRoot,RightTRoot; //初始调用为 getRootValue(0,N-1,0); n表示树结点总数,L表示左子树的结点个数
	n=ARight-ALeft+1;  
	if(n==0) return;  //**使用递归,有去有回** 
	L=GetLeftLength(n); //从完全二叉树的特点入手,计算出n个结点的树其左子树有多少个结点
	T[TRoot]=A[ALeft+L]; //知道了左子树有多少结点,再根据'二叉搜索树,左小右大'的特点,就可以锁定根元素了
	
	LeftTRoot = TRoot*2+1;
	RightTRoot= LeftTRoot+1;
	
	getRootValue(ALeft,ALeft+L-1,LeftTRoot);
	getRootValue(ALeft+L+1,ARight,RightTRoot);	
}
  1. GetLeftLength(n) 该函数用于获取左子树的结点个数
    2.1 设叶结点的个数为X,树的高度为H(不考虑叶结点那一层),树的结点总数为N,则 N = 2H - 1 + X;树的高度近似 H=log2(N+1),从而得到叶节点的近似值 X= N + 1 - 2H
    2.2 对于完全二叉树 左子树的叶结点,最小值为0,最大值为2H-1,若求得的近似值X>2H-1,则叶结点应该取最大值2H-1
#define base 2
int GetLeftLength(int N)
{
	int H; //H=树的高度, 不考虑叶节点那一层 
	H=log(N+1)/log(base); //以2为底求 N+1 的对数 
	int X; //X表示最底层的节点个数(叶结点个数)
	X= N+1-pow(base,H);  
	
	int tmp; //tmp表示左子树叶结点的最大值 
	tmp=pow(base,H-1);
	if(tmp<X) X=tmp;
	
	int L;
	L=pow(base,H-1)-1+X;
	return L;
}

3. 完整编码

如果本文帮到了您,请点赞鼓励,您的鼓励是作者持续原创的动力,谢谢 😃

#include<cmath>
#include<algorithm>
#include <iostream>
using namespace std;
#define base 2
#define max 1000
int A[max],T[max];
int GetLeftLength(int n);
void getRootValue(int ALeft, int ARight, int TRoot);

int main()
{
	int N;
	cin>>N;
	for(int i=0;i<N;i++) cin>>A[i];
	sort(A,A+N);
	getRootValue(0,N-1,0);
	cout<<T[0];
	for(int i=1;i<N;i++) cout<<' '<<T[i];
	return 0;
}
void getRootValue(int ALeft, int ARight, int TRoot)
{
	int n,L,LeftTRoot,RightTRoot; //初始调用为 getRootValue(0,N-1,0) 
	n=ARight-ALeft+1;  //n表示树结点总数 
	if(n==0) return;  //**使用递归,有去有回** 
	L=GetLeftLength(n); //从完全二叉树的特点入手,计算出n个结点的树其左子树有多少个结点
	T[TRoot]=A[ALeft+L]; //知道了左子树有多少结点,再根据'二叉搜索树,左小右大'的特点,就可以锁定根元素了
	LeftTRoot = TRoot*2+1;
	RightTRoot= LeftTRoot+1;
	getRootValue(ALeft,ALeft+L-1,LeftTRoot);
	getRootValue(ALeft+L+1,ARight,RightTRoot);	
}

int GetLeftLength(int N)
{
	int H; //H=树的高度, 不考虑叶节点那一层 
	H=log(N+1)/log(base); //以2为底求 N+1 的对数 
	int X; //X表示最底层的节点个数近似值(叶结点个数)
	X= N+1-pow(base,H);  
	int tmp; //tmp表示左子树叶结点的最大值 
	tmp=pow(base,H-1);
	if(tmp<X) X=tmp;
	int L;
	L=pow(base,H-1)-1+X;
	return L;
}

2021.10.9 AC代码:没有之前的总结根本做不出来系列。求左子树结点个数的方法并没有完全理解,自己目前认为按这种方式计算出的结果存在异常

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

void GetTreeRoot(int Left, int Right, int RootIndex);
int GetLeft(int N);

int* a, * Tree; //数组a存放原始输入数据并从小到大排列。数组Tree存放树的层序结构
int main()
{
	int N; cin >> N;
	a = new int[N]; Tree = new int[N];

	int Value;
	for (int i = 0; i < N; i++)
	{
		cin >> Value;
		a[i] = Value;
	}
	sort(a, a + N);//从小到大排列

	GetTreeRoot(0, N - 1, 0);//依次代表数组a的第一个元素、数组a最后一个元素、数组Tree的第一个元素(树根)

	cout << Tree[0];
	for (int i = 1; i < N; i++)
		cout << " " << Tree[i];

	return 0;
}

void GetTreeRoot(int Left, int Right, int RootIndex)
{
	int N = Right - Left + 1;
	if (N <= 0) return;

	int LeftNum = GetLeft(N); //左子树的结点个数已确定,从而确定根结点
	Tree[RootIndex] = a[Left+ LeftNum];

	//递归左右子树
	GetTreeRoot(Left, Left + LeftNum - 1, RootIndex*2 + 1);
	GetTreeRoot(Left + LeftNum + 1, Right, RootIndex*2 + 2);

	return;
}

int GetLeft(int N)
{
	int High = log(N + 1) / log(2); //树的高度(不包含叶结点那一层)

	int LeafNum = N + 1 - pow(2, High);//始终感觉按这种计算方式,叶结点的数量一定是0;
	//cout<<"Leaf: " << LeafNum << endl; //但是事实并非如此啊:实际情况并非一直是0,老师的计算方法为什么是正确的呢

	int LeafNumMax = pow(2, High - 1);
	if (LeafNum > LeafNumMax) LeafNum = LeafNumMax;

	return pow(2, High - 1) - 1 + LeafNum;
}

/*
* 1.完全二叉搜索树的数据结构是本题的突破口:
(1)已知树结点总数,那么完全二叉树左子树的结点个数X可以近似求出来   
 -- N=2^High - 1 + X ---> 近似 N=2^High  - 1 , 2^High = N+1, High=log2(N+1)  计算出的High偏大:int High=log(N + 1)/log(2);
 -- 叶结点X近似为 X = N + 1 - 2^High  ?  这结果不就是0吗?? 这样就没有意义了啊   但事实是这样是正确的
(2)已知树结点总数 + 左子树结点个数,可以找到根结点 //对于二叉搜索树 根结点的数值大于左子树的各结点、小于右子树的各结点
(3)使用(2)中找到根结点的函数,递归左右子树
* 2.通过数组存储树的层序结构

*/

四、参考资料

浙江大学 陈越、何钦铭老师主讲的数据结构

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值