一,什么样的序列组合才能恢复一棵二叉树 ?
1,前序+中序 2,后序+中序 3,特殊的层序遍历(需要记录空节点)
前序遍历的结果 [根节点, [左子树], [右子树]]
中序遍历的结果 [[左子树], 根节点, [右子树]]
后序遍历的结果 [[左子树], [右子树], 根节点]
所以若要恢复一棵二叉树,必须要包含 "中序遍历" 的结果,因为只有中序遍历中的左子树和右子树是分开的。
同理,要想将中序遍历结果中左右子树分开,必须要找到根节点,所以还需要另外一个遍历结果。
二,需要注意的细节
因为我们是根据 "根节点的值" 来将中序遍历结果进行划分,所以要确保该值是唯一的,即在左右子树中,不能出现与根节点值有相同值的节点。
简而言之,被恢复的二叉树所有节点中,每个节点所包含的值都是唯一的。(也就是遍历的结果中,每个值都是不同的)
三,恢复的方法
1,递归法 (该递归本质也算是一个前序遍历算法)
2,迭代法 [还没明白,后续明白了再更新]
四,头文件
#ifndef __TREE_NODE_H__
#define __TREE_NODE_H__
#include <time.h>
#include <stdlib.h>
#include <vector>
#define NUM_SIZE 10
#define PRINT(vec) \
do { \
for (auto it = (vec).begin(); it != (vec).end(); it++) { \
std::cout << *it << " "; \
} \
std::cout << std::endl << std::endl; \
} while(0)
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode() :val(0), left(nullptr), right(nullptr) {}
TreeNode(int v) :val(v), left(nullptr), right(nullptr) {}
TreeNode(int v, TreeNode* l, TreeNode* r) : val(v), left(l), right(r) {}
};
#define PRE_IN_BINARY_TREE 0
#define POST_IN_BINARY_TREE !PRE_IN_BINARY_TREE
static void preOrder(TreeNode* root, std::vector<int>& vec); // 前序遍历
static void inOrder(TreeNode* root, std::vector<int>& vec); // 中序遍历
static void postOrder(TreeNode* root, std::vector<int>& vec); // 后序遍历
static TreeNode* getRandomTree(); // 获取一棵随机二叉树
// 验证结果,若 flag == 0, 则验证 pre + in; 若 flag == 1, 则验证 post + in。
static bool verifyResult(TreeNode* root, std::vector<int> order, std::vector<int> inorder, int flag);
void preOrder(TreeNode* root, std::vector<int>& vec) {
if (root) {
vec.emplace_back(root->val);
preOrder(root->left, vec);
preOrder(root->right, vec);
}
}
void inOrder(TreeNode* root, std::vector<int>& vec) {
if (root) {
inOrder(root->left, vec);
vec.emplace_back(root->val);
inOrder(root->right, vec);
}
}
void postOrder(TreeNode* root, std::vector<int>& vec) {
if (root) {
postOrder(root->left, vec);
postOrder(root->right, vec);
vec.emplace_back(root->val);
}
}
bool verifyResult(TreeNode* root, std::vector<int> order, std::vector<int> inorder, int flag) {
std::vector<int> ans;
inOrder(root, ans);
if (ans.size() != inorder.size()) {
return false;
}
for (int i = 0; i < ans.size(); i++) {
if (ans[i] != inorder[i]) {
return false;
}
}
ans.clear();
if (flag == 0) {
preOrder(root, ans);
}
else {
postOrder(root, ans);
}
if (ans.size() != order.size()) {
return false;
}
for (int i = 0; i < ans.size(); i++) {
if (ans[i] != order[i]) {
return false;
}
}
return true;
}
TreeNode* getRandomTree() {
srand((unsigned)time(NULL));
TreeNode* root = new TreeNode(0);
TreeNode* tmp = root;
int val = 1;
for (int i = 0; i < NUM_SIZE; i++) {
TreeNode* node = new TreeNode(val++);// 每次都要使用不同的 val,故让其递增即可
int seed = rand() % 2;
if (seed == 0) {
tmp->left = node;
}
else {
tmp->right = node;
}
tmp = node;
}
return root;
}
#endif // __TREE_NODE_H__
五,前序+中序 恢复一棵二叉树
#include "TreeNode.h"
#if PRE_IN_BINARY_TREE
#include <iostream>
using namespace std;
/*
该递归函数的定义 : 通过前序遍历和中序遍历的指定区间,恢复并返回二叉树的根节点
该递归的本质也是一个前序遍历算法
*/
TreeNode* build(vector<int>& preorder, vector<int>& inorder, int preStart, int preEnd, int inStart, int inEnd) {
if (preStart > preEnd || inStart > inEnd) { // 若越界,则直接返回空节点
return nullptr;
}
TreeNode* root = new TreeNode(preorder[preStart]);// 前序遍历的起始节点就是根节点
int offset = 0; // offset 为偏移量,为了在 中序遍历结果 中找到根节点,从而将左右子树分开
while (inorder[inStart + offset] != preorder[preStart]) { // 这一步可以优化,因为每个值都是唯一的,可以使用 map 将值与 index 相绑定
offset++;
}
/*
对于 前序遍历 [根,左,右] ,左子树的起点就是 preStart+1, 终点就是 preStart+左子树节点个数
对于 中序遍历 [左, 根, 右] ,我们通过 offset 找到了 根节点,故左子树的节点个数就是 offset。
preStart preEnd
| |
v v
pre [根, [左1, ...... , 左n], [右1, ......, 右n]]
inStart inEnd
| |
v v
in [[左1, ...... , 左n], 根, [右1, ......, 右n]]
^ ^
| |
offset = 0 offset
*/
root->left = build(preorder, inorder, preStart + 1, preStart + offset, inStart, inStart + offset - 1);
root->right = build(preorder, inorder, preStart + offset + 1, preEnd, inStart + offset + 1, inEnd);
return root;
}
/*
采用递归法恢复二叉树
*/
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
return build(preorder, inorder, 0, preorder.size() - 1, 0, inorder.size() - 1);
}
int main() {
TreeNode* root = getRandomTree();
vector<int> preorder;
vector<int> inorder;
preOrder(root, preorder);
cout << "before preorder : " << endl;
PRINT(preorder);
inOrder(root, inorder);
cout << "before inorder : " << endl;
PRINT(inorder);
root = buildTree(preorder, inorder);
bool ret = verifyResult(root, preorder, inorder, 0);
cout << "ret : " << ret << endl;
return 0;
}
#endif // PRE_IN_BINARY_TREE
六,后序+中序 恢复一棵二叉树
#include "TreeNode.h"
#if POST_IN_BINARY_TREE
#include <iostream>
using namespace std;
TreeNode* build(vector<int>& postorder, vector<int>& inorder, int postStart, int postEnd, int inStart, int inEnd) {
if (postStart > postEnd || inStart > inEnd) {
return nullptr;
}
TreeNode* root = new TreeNode(postorder[postEnd]);
int offset = 0;
while (inorder[inStart + offset] != postorder[postEnd]) {
offset++;
}
root->left = build(postorder, inorder, postStart, postStart + offset - 1, inStart, inStart + offset - 1);
root->right = build(postorder, inorder, postStart + offset, postEnd - 1, inStart + offset + 1, inEnd);
return root;
}
TreeNode* buildTree(vector<int>& postorder, vector<int>& inorder) {
return build(postorder, inorder, 0, postorder.size() - 1, 0, inorder.size() - 1);
}
int main() {
TreeNode* root = getRandomTree();
vector<int> postorder;
vector<int> inorder;
postOrder(root, postorder);
cout << "before postorder : " << endl;
PRINT(postorder);
inOrder(root, inorder);
cout << "before inorder : " << endl;
PRINT(inorder);
root = buildTree(postorder, inorder);
bool ret = verifyResult(root, postorder, inorder, 1);
cout << "ret : " << ret << endl;
return 0;
}
#endif // POST_IN_BINARY_TREE
七,特殊的层序遍历 恢复一棵二叉树
/*
特殊的层序遍历,需要记录非空节点的两个孩子,即使非空节点的孩子是空节点,也要占位。
1
/ \
2 3
/ \ / \
# 4 # #
/ \
# #
层序遍历需要记录的结果是 [ 1, 2, 3, #, 4, #, #, #, #]
则我们可以通过上述的遍历结果,将该二叉树恢复。(该方法也可用于二叉树的序列化与反序列化)
*/
/* NULL_NUM 表示 #,就是一个与二叉树中所有节点值都不相同的一个数字 */
void levelOrder(TreeNode* root, vector<int>& vec, int NULL_NUM) {
if (root) {
queue<TreeNode*> queue;
queue.emplace(root);
while (!queue.empty()) {
TreeNode* front = (TreeNode*)queue.front();
queue.pop(); // 弹出该节点
if (front == nullptr) { // 因为有可能将空节点加入,所以需要判空
vec.emplace_back(NULL_NUM); // 若是空节点,则加入特殊标志的数字
}
else {
vec.emplace_back(front->val);
queue.emplace(front->left); // 注意,此处与正常的层序遍历不同,因为需要将非空节点的两个孩子都记录,所以即使是空孩子,也要加入
queue.emplace(front->right);
}
}
}
}
/*
如何通过上述的序列恢复二叉树呢 ?
恢复的方法与遍历的方法一致,也是需要通过队列。
*/
TreeNode* buildTree(vector<int> vec, int NULL_NUM) {
if (vec.size() == 0) return nullptr;
TreeNode* root = new TreeNode(vec[0]);
queue<TreeNode*> queue;
queue.emplace(root);
for (int i=1; i<vec.size(); i++) {
TreeNode* front = (TreeNode*)queue.front();
queue.pop();
if (vec[i] != NULL_NUM) {
TreeNode* left = new TreeNode(vec[i]);
front->left = left;
queue.emplace(left); // 需要将非空节点加入队列
}
if (vec[++i] != NULL_NUM) { // 需要 ++i
TreeNode* right = new TreeNode(vec[i]);
front->right = right;
queue.emplace(right);
}
}
return root;
}