原题链接
解题思路
因为n个结点的完全二叉树的结构是固定的!又因为是二叉查找树,也就是结点间的大小关系是固定的!那么不需要去考虑如何构造这样一棵树,而是转而去考虑如何将给定的元素填在树上某个合适的结点上。
那么首先对输入元素从小到大排序,借助树结构固定这一优势,只要我们确定了左右子树的个数(结构确定了肯定能算出来),将第左子树个数+1大的元素填在根结点上,然后递归填写左右子树。
关于完成二叉树的结论:
- 第i层的结点个数为2^(i-1)
- 有i层结点的满二叉树(完美二叉树)的总结点个数为2^i-1
(写的非常混乱,直接看代码或者自己画画更好理解 )
这样的话求二叉树结点就好求了,先求放满了结点的层数h:
假设最后一层(没有满,也就是个数小于2^(i-1) )的结点个数为x,那么根据结论总结点数为n有:
2^H-1+x = n(H为树的高度),那么h = log2(n+1)向下取整,因为H>=h。有h了就可以求除了最后一层的结点个数(2 ^ h-1),所以x = n-(2 ^ h-1)。
那么左子树的个数就是上面层数的一半(2 ^ h-1)/ 2,还要加上最后一层属于左子树的结点个数,也就是可能是x,但如果x中包含了属于右子树的结点,那就加最后一层满结点个数2 ^ (h+1-1)的一半2^(h-1)。
啊好难描述,还是看代码中的注释吧。
源代码
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
const int maxn = 1010;
int data[maxn], tree[maxn];
int getLeftNum(int n){
//计算含有n个结点的完全二叉树的左子树元素
int h = log(n+1)/log(2); //装满了结点的层数
int num = pow(2, h)-1;
//下一层可能还有结点数[0,2^(h-1)]
int res = (num-1)/2+min(n-num, int(pow(2, h-1)));
return res;
}
void solve(int Dleft, int Dright, int Troot){
//Dleft和Dright为排序后的元素数组的范围下标,Troot表示当前要填写的结点(先序)
int n = Dright-Dleft+1;
if(n == 0) return ;
int L = getLeftNum(n) ; //计算n个结点的完全二叉树左子树的元素个数
tree[Troot] = data[Dleft+L];
int leftRoot = Troot*2+1; //第一个下标是0,左孩子是2倍加1
int rightRoot = leftRoot+1;//右孩子是左孩子下标加1
//递归
solve(Dleft, Dleft+L-1, leftRoot);
solve(Dleft+L+1, Dright, rightRoot);
}
int main(){
int n;
scanf("%d", &n);
for(int i=0; i<n; i++){
scanf("%d", &data[i]);
}
sort(data, data+n);
solve(0, n-1, 0);
//用数组存放的完成二叉树就是层序遍历的顺序
for(int i=0; i<n; i++){
printf("%d", tree[i]);
if(i != n-1){
printf(" ");
}
}
return 0;
}
心得体会
看到题目的时候就一直在想怎么在插入的过程中旋转保持是一棵二叉完全树,没有好的办法。后来看了陈越老师的讲解,原来不用只去想怎样去构造这样一棵树,而是在树结构确定了的情况下,如何将给定的元素填到树的结点中,一看讲解视频的封面图就明白老师的意思了,收获很多呢,不要固定了自己的思维,要灵活!