前言
二叉树的遍历有三种方式,前序,中序和后序遍历都可以完成对二叉树的遍历。遍历一颗树很容易,那么建树呢?是否可以选择任意 2 种遍历序列来建立一颗二叉树。
分析
这三种遍历方式最为特殊就是中序遍历,中序遍历先遍历左子树后输出当前节点再遍历右子树,这种遍历方式可以保证当前位置的左边元素都是左子树的,右边的元素都是右子树的。前序序列的第一个元素为根节点,后序遍历的最后一个元素为根节点,配合中序遍历我们可以不断确定根节点从而建立二叉树,所以中序遍历配合后序或者前序就可以确认一颗二叉树,
那么前序和后序遍历配合能否建立二叉树呢?当然可以建立,不过这个二叉树无法唯一确定,因为前序和后序本质上都是将父节点和子节点进行分离,并没有指明左子树和右子树的能力。如果没有中序遍历来确定下一个根节点的方向,就可能出现下面这种情况:
上面两个二叉树的前序遍历都是2-1-3,后序遍历都是3-1-2,但是它们显然是不同的二叉树,所以根据先序序列和后序序列并不能唯一确定二叉树。(可以建树,不过结果可能不唯一)
实现
1、前序遍历和中序遍历建树
- 前序遍历数组的第一个元素就是当前二叉树的根节点
- 在中序序列找到该根节点,该根节点把中序序列分为左右子树两部分,确定左右子树长度
- 通过左右子树长度来确定左右子树的前序序列范围,前部分是左子树的前序序列,后部分是右子树的前序序列
- 同样的规则递归左右两个子树不断确定根节点
例如:
先序序列:1 2 3 4 5 6
中序序列:3 2 4 1 6 5 7
根据先序序列可以知道该根节点为1,那么找到中序序列中该根节点的位置 k,当前 k 为4 (1 2 3 4 5 6 7)
根据中序序列的特点,可知该位置的左边就是根节点的左子树,右边为根节点的右子树。(3 2 4 1 6 5 7)
接下来就要确定左右子树的前序和中序序列,不断缩小树的规模。
左子树的中序序列:il
到k-1
左子树的先序序列:pl+1
到k-il+pl
右子树的中序序列:k+1
到ir
右子树的先序序列:k-il+pl+1
到pr
通过这些序列后左子树和右子树就是一颗我们原来的树了,以同样的方法递归左右子树不断确定根节点就建立了一颗树
Code~
#include<bits/stdc++.h>
using namespace std;
int a[1005],b[1005];
struct Tree{
Tree * l, *r;
int val;
};
// [pl,pr] 前序序列开始和结束
// [il,ir] 中序序列开始和结束
// n 树的结点个数
Tree * build(int pl,int pr,int il,int ir,int n){
if(n == 0) return NULL;
Tree * root = new Tree();
root->val = a[pl];
int k = il;
while(a[pl] != b[k]) k++;
root->l = build(pl + 1, k - il + pl, il, k - 1, k - il);
root->r = build(k - il + pl + 1, pr, k + 1, ir, ir - k);
return root;
}
int main()
{
int n;
cin >> n;
for(int i = 1; i <= n; i++){
cin >> a[i];
}
for(int i = 1; i <= n; i++){
cin >> b[i];
}
Tree * tree;
tree = build(1,n,1,n,n);
}
2、后序遍历和后序遍历建树
-
后序遍历数组的最后一个元素就是当前二叉树的根节点
-
在中序序列找到该根节点,该根节点把中序序列分为左右子树两部分,确定左右子树长度
-
通过左右子树长度来确定左右子树的后序序列范围,前部分是左子树的后序序列,后部分是右子树的后序序列
-
同样的规则递归左右两个子树不断确定根节点
左子树的中序序列:il
到k-1
左子树的后序序列:pl
到k-il+pl-1
右子树的中序序列:k+1
到ir
右子树的后序序列:k-il+pl
到pr-1
同理递归左右子树不断确定根节点就建立了一颗树
#include<bits/stdc++.h>
using namespace std;
int a[1005],b[1005];
struct Tree{
Tree * l,*r;
int val;
};
// [pl,pr] 后序序列开始和结束
// [il,ir] 中序序列开始和结束
// n 树的结点个数
Tree * build(int pl,int pr,int il,int ir,int n){
if(n == 0) return NULL;
Tree * root = new Tree();
root->val = a[pr];
int k = il;
while(a[pr] != b[k]) k++;
root->l = build(pl, k - il + pl - 1, il, k - 1, k - il);
root->r = build(k - il + pl, pr - 1, k + 1, ir, ir - k);
return root;
}
int main()
{
int n;
cin >> n;
for(int i = 1; i <= n; i++){
cin >> a[i];
}
for(int i = 1; i <= n; i++){
cin >> b[i];
}
Tree * tree;
tree = build(1,n,1,n,n);
}
3、前序遍历和后序遍历建树
-
前序遍历,根节点在左右子树的前前面。而在后序遍历中,根结点在左右子树后面。
-
后序序列确定好根结点,该结点前面的序列按左子树右子树的顺序进行排列。
-
在前序序列以一个顶点作为根节点,提供其随后的节点A作为左子树根节点
-
在后序序列中找到该根节点A的位置,即可获取左子树的长度
-
确定好左右子树的前序和后序序列,即可以同样的规则递归左右两个子树不断确定根节点。
左子树的前序序列:pl+1
到pl+k-il+1
左子树的后序序列:pl
到k
右子树的前序序列:pl+k-il+1+1
到pr
右子树的后序序列:k+1
到ir-1
#include<bits/stdc++.h>
using namespace std;
struct node {
int value;
node *lchild, *rchild;
};
int n;
int pre[105], post[105];
map<int, int> mp;
node *create(int pl, int pr, int il, int ir) {
if (pl > pr) return NULL;
if (pl == pr) {
node* root = new node;
root->value = pre[pl];
root->lchild = root->rchild = NULL;
return root;
}
node *root = new node;
root->value = pre[pl];
int k = mp[pre[pl + 1]];
int numleft = k - il + 1;
root->lchild = create(pl + 1, pl + numleft, il, k);
root->rchild = create(pl + numleft + 1, pr, k + 1, ir - 1);
return root;
}
int main()
{
cin >> n;
for (int i = 0; i < n; i++){
cin >> pre[i];
}
for (int i = 0; i < n; i++) {
cin >> post[i]; mp[post[i]] = i;
}
node *root = create(0, n - 1, 0, n - 1);
return 0;
}
前序和后序建树练习题:https://pintia.cn/problem-sets/994805342720868352/problems/99480535347086
参考资料:
https://blog.csdn.net/Mr_dimple/article/details/117046959 先序(后序)配合中序建树
https://blog.csdn.net/JasonRaySHD/article/details/104223642 前序序列与后序序列建树