我的PAT-ADVANCED代码仓:https://github.com/617076674/PAT-ADVANCED
我的Data Structures and Algorithms代码仓:https://github.com/617076674/Data-Structures-and-Algorithms
原题链接:
PAT-ADVANCED1064:https://pintia.cn/problem-sets/994805342720868352/problems/994805407749357568
Data Structures and Algorithms7-7:https://pintia.cn/problem-sets/16/problems/669
题目描述:
PAT-ADVANCED1064、Data Structures and Algorithms7-7:
题目翻译:
1064 完全二分搜索树
一棵二分搜索树是一棵二叉树,递归地定义如下:
左子树的节点都小于当前节点的值。
右子树的节点都大于或等于当前节点的值。
左右子树都是一棵二分搜索树。
完全二叉树(CBT)是一个除了最底层,其他层全被填满的树,最底层的元素集中在左侧。
给定一系列不同的非负整数,如果要求构建的BST是一棵CBT,那么构建的树一定是唯一的。你需要输出这棵BST的层序遍历序列。
输入格式:
每个输入文件包含一个测试用例。在每个测试用例中,第一行有一个正整数N(<= 1000)。下一行包含N个非负整数。一行中的所有数字以一个空格分隔且数字大小均不超过2000。
输出格式:
对每个测试用例,你需要输出相应二分搜索树的层序遍历结果。一行中的所有数字都由一个空格隔开,行末不得有多余空格。
输入样例:
10
1 2 3 4 5 6 7 8 9 0
输出样例:
6 3 8 1 5 7 9 0 2 4
知识点:二分搜索树的中序遍历、层序遍历、根据中序遍历重建完全二分搜索树
思路一:根据中序遍历重建完全二分搜索树
重建一棵二叉树的关键是需要知道有关根节点的信息和如何区分左右子树的信息。对于二分搜索树,如何区分左右子树的信息已经包含在了二分搜索树的定义中,我们的关键是获取根节点的信息。
对于一般的二分搜索树,根据中序遍历,我们是无法重建出一棵唯一的二分搜索树的,因为我们无法获取根结点的信息。但是对于一棵完全二分搜索树,我们却可以。因为对于一棵完全二分搜索树来说,假设根结点记作第1层,并假设这棵树有h层。那么对于1 ~ h层各层的节点数我们都是可以确定的。
第1层的节点数是2 ^ 0,只有一个根节点。
第2层的节点数是2 ^ 1,根节点的左边1个,右边1个。
第3层的节点数是2 ^ 2,根节点的左边2个,右边2个。
第4层的节点数是2 ^ 3,根节点的左边2 ^ 2个,右边2 ^ 2个。
……
第h - 1层的节点数是2 ^ (h - 2),根节点的左边2 ^ (h - 3)个,右边2 ^ (h - 3)个。
第h层的节点个数就是总节点数减去前面各层的节点数,且第h层的节点数均集中在左侧。我们很容易知道根节点的左边有多少个节点,右边有多少个节点。
以上分析可以看出,对于一棵完全二分搜索树,我们很容易能知道根节点的左侧有多少个节点,右侧有多少个节点。再结合非降序的中序遍历结果,我们就能够确定其根节点信息。
分析清楚了上面这些,我们只需递归地交给计算机去解决问题就行了。重建完树后再层序遍历即得结果。
时间复杂度是O(NlogN)。空间复杂度是O(N)。
C++代码:
#include<iostream>
#include<vector>
#include<queue>
#include<algorithm>
#include<cmath>
using namespace std;
struct node {
int data;
int lchild;
int rchild;
};
int N;
node Node[1000];
int index = 0;
vector<int> inOrder;
vector<int> levelOrder;
int create(int left, int right);
void levelOrderTraversal(int root);
void print();
int main() {
cin >> N;
int num;
for(int i = 0; i < N; i++) {
cin >> num;
inOrder.push_back(num);
}
sort(inOrder.begin(), inOrder.end());
int root = create(0, inOrder.size() - 1);
levelOrderTraversal(root);
print();
return 0;
}
int create(int left, int right) {
if(left > right) {
return -1;
}
int root = index++;
int count = right - left + 1; //总共有count个节点
int level; //层数
for(int i = 1;; i++) {
if((int)pow(2, i) - 1 >= count) {
level = i;
break;
}
}
int countLastLevel = count - (int)pow(2, level - 1) + 1; //最后一层的节点数
int numLeft; //根节点左边的节点数
if(countLastLevel <= (int)pow(2, level - 2)) { //(int)pow(2, level - 2)是最后一层节点数的一半
numLeft = countLastLevel + (count - 1 - countLastLevel) / 2;
} else {
numLeft = (int)pow(2, level - 2) + (count - 1 - countLastLevel) / 2;
}
Node[root].data = inOrder[left + numLeft];
Node[root].lchild = create(left, left + numLeft - 1);
Node[root].rchild = create(left + numLeft + 1, right);
return root;
}
void levelOrderTraversal(int root) {
queue<int> q;
q.push(root);
while(!q.empty()) {
int now = q.front();
levelOrder.push_back(Node[now].data);
q.pop();
if(Node[now].lchild != -1) {
q.push(Node[now].lchild);
}
if(Node[now].rchild != -1) {
q.push(Node[now].rchild);
}
}
}
void print() {
for(int i = 0; i < levelOrder.size(); i++) {
cout << levelOrder[i];
if(i != levelOrder.size() - 1) {
cout << " ";
}
}
cout << endl;
}
C++解题报告:
思路二:用中序遍历去填充数组
如果使用数组来存放一棵完全二叉树,那么对完全二叉树当中的任意一个节点(设编号为x,其中根结点编号为1),其左孩子节点的编号就一定是2x,而右孩子节点的编号就一定是2x + 1。于是我们可以开一个数组levelOrder[1001],其中levelOrder[1] ~ levelOrder[N]按层序存放完全二叉树的n个节点,这个数组就存放了一棵完全二叉树,只不过暂时还没有为其元素进行赋值。
而根据题给的数据,我们很容易能得到非降序的中序遍历序列inOrder。然后我们对levelOrder数组表示的二叉树进行中序遍历,并在中序遍历的过程中将inOrder中的数组从小到大地填入数组,最后就能得到一棵完全二叉搜索树。由于levelOrder数组就是按照二叉树的层序来存放节点的,因此只需要将数组元素按顺序输出即为层序遍历序列。
以往我们中序遍历都是打印其节点元素,而这次只不过是换成了往节点中填入值。
时间复杂度是O(NlogN)。空间复杂度是O(N)。
C++代码:
#include<iostream>
#include<vector>
#include<algorithm>
#include<cmath>
using namespace std;
int N;
vector<int> inOrder;
int index = 0;
int levelOrder[1001];
void inOrderTraversal(int root);
void print();
int main(){
cin >> N;
int num;
for(int i = 0; i < N; i++) {
cin >> num;
inOrder.push_back(num);
}
sort(inOrder.begin(), inOrder.end());
inOrderTraversal(1);
print();
return 0;
}
void inOrderTraversal(int root){
if(root > N){
return;
}
inOrderTraversal(root * 2);
levelOrder[root] = inOrder[index++];
inOrderTraversal(root * 2 + 1);
}
void print() {
for(int i = 1; i <= N; i++) {
cout << levelOrder[i];
if(i != N) {
cout << " ";
}
}
cout << endl;
}
C++解题报告: