Complete Binary Search Tree 思路分析及代码解析 v0.9
一、前导
1. 需要掌握的知识
- 完全二叉树、二叉搜索树
- 层序遍历
- 递归函数的应用
2. 题目信息
- 题目来源:PTA / 拼题A
- 题目地址:https://pintia.cn/problem-sets/16/problems/669
二、解题思路分析
1. 题意理解
- 输入数据
10 //完全二叉搜索树的结点数
1 2 3 4 5 6 7 8 9 0 //各结点的数值,根据这些结点建树
- 输出数据
6 3 8 1 5 7 9 0 2 4 //打印树的层序结构
- 题意
根据录入的信息建立一棵完全二叉搜索树(同时满足 完全二叉树 和 二叉搜索树的特点),然后打印其层序结构。
2. 思路分析(重点)
- 从完全二叉搜索树的特点入手:(1)N个结点的完全二叉树,其左子树的个数是定值(2)对于二叉搜索树,左子树比根节点小,右子树比根节点大。子树也遵循该定义。根据 (1) (2) 就可以锁定根元素
- 若用数组存储树的层序结构,首元素就是根结点,以此类推
- 根据上述特点,构建函数找到根节点并存储
- 递归调用此函数:获取其左子树的根节点、右子树的根节点
三、具体实现
1. 弯路和bug
- 使用递归时,一定要加上返回语句
- 数据结构的特点往往就是突破口
2. 代码框架(重点)
2.1 采用的数据结构
使用数组存储:空间利用率高、运算方便;便于打印,数组顺序就是树的层序结构
2.2 程序主体框架
程序伪码描述
int main()
{
0.int A[max],T[max]; // A[ ]用来存放输入的原始数值并从小到大排好序; T[ ]用来存放完全二叉搜索树的层序结构
1.实现获取根节点的函数并递归调用 //core
2.打印输出
}
2.3 各分支函数
- 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);
}
- 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.通过数组存储树的层序结构
*/