重建二叉树
这里介绍一下用先序-中序,后序-中序遍历结果来构建二叉树的过程。
什么是先序,后序,中序遍历?
首先看一下如下的二叉树结构。
各种遍历的规则:
- 前序遍历(根左右): 对访问到的每个结点,先访问根结点,然后是左结点,然后是右结点
- 中序遍历(左根右): 对访问到的每个结点,先访问左结点,然后是根结点,然后是右结点
- 后序遍历(左右根): 对访问到的每个结点,先访问左结点,然后是右结点,然后是根结点
由上述规则,各个遍历的遍历结果如下:
前序遍历:
1,2,4,5,3,6,7
中序遍历:
4,5,2,1,6,3,7
后序遍历:
5,4,2,6,7,3,1
下面从代码层次讲解一下过程(递归+非递归版本)
1. 遍历实现
递归版本:最好理解,按照之前的遍历规则,码好就 ok 了
非递归版本则需要借助栈的存储结构
1)前序遍历
递归版本:
public static void preOrderRecur(Node head) {
if(head!=null) {
// 根
System.out.print(head.value+" ");
// 左
preOrderRecur(head.left);
// 右
preOrderRecur(head.right);
}
}
非递归版本:
上图为一个栈,由于栈的先进先出原则,首先出的为根节点,然后为左,右。
注意:这边左右节点入栈顺序相反!
原因:由于栈的先进先出策略,左边先进栈,那么左节点晚于右节点出栈!与规则不符合
public static void preOrderUnRecur(Node head) {
System.out.print("pre-order: ");
// 使用栈结构
Stack<Node> st = new Stack<Node>();
if(head != null) {
st.push(head);
while(!st.isEmpty()) {
head = st.pop();
System.out.print(head.value+" ");
if(head.right!=null) {
st.push(head.right);
}
if(head.left!=null) {
st.push(head.left);
}
}
}
System.out.println();
}
2) 后序遍历
递归版本:
public static void posOrderRecur(Node head) {
if(head == null) {
return ;
}
// 左
posOrderRecur(head.left);
// 右
posOrderRecur(head.right);
// 根
System.out.print(head.value+" ");
}
非递归版本:
分析:
后序遍历能不能从前序遍历的非递归版本中改过来呢?
答案显然是可以的,只需要将左右入栈顺序改变;输出是进行逆序输出就行啦!!
前序遍历(根左右),后序遍历(左右根)明显看出将前序遍历做如下修改就能变成后序遍历:
根左右->根右左->左右根
代码:
public static void posOrderUnRecur1(Node head) {
System.out.print("pos-order: ");
if(head!=null) {
Stack<Node> s1 = new Stack<Node>();
// 使用stack2 进行逆序输出
Stack<Node> s2 = new Stack<Node>();
s1.push(head);
while(!s1.isEmpty()) {
head = s1.pop();
s2.push(head);
if(head.left != null) {
s1.push(head.left);
}
if(head.right!=null) {
s1.push(head.right);
}
}
while(!s2.isEmpty()) {
System.out.print(s2.pop().value+" " );
}
}
System.out.println();
}
3) 中序遍历
递归版本:
public static void inOrderRecur(Node head) {
if(head == null) {
return;
}
// 左
inOrderRecur(head.left);
// 根
System.out.print(head.value+" ");
// 右
inOrderRecur(head.right);
}
非递归版本:
分析:
先将左节点全部进栈,没有左节点时,进行出栈操作!
出栈操作:弹出最左节点打印,然后将其右节点入栈。
public static void inOrderUnRecur(Node head) {
System.out.print("in-order: ");
if(head!=null) {
Stack<Node> st = new Stack<Node>();
while(!st.isEmpty() || head!=null) {
if(head!=null) {
st.push(head);
head = head.left;
}else {
head = st.pop();
System.out.print(head.value+" " );
head = head.right;
}
}
}
System.out.println();
}
2. 重建二叉树
1)前中序重建二叉树
根据一棵树的前序遍历与中序遍历构造二叉树。
注意:
你可以假设树中没有重复的元素。
例如,给出
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:
3
/ \
9 20
/ \
15 7
分析:
首先把中序数组分为如上的部分:
index:指向根节点(前序遍历的第一个为根节点,然后找到中序遍历中根节点的位置)
li/ri : 为中序遍历的首尾索引
lp/rp:为前序遍历的首尾索引
left: 为左子树的长度
递归过程就是找到头节点和构造左右子树的过程,如上图所示。
代码:
public TreeNode buildTree(int[] preorder, int[] inorder) {
if (preorder == null || preorder.length == 0)
return null;
TreeNode root = new TreeNode(preorder[0]);
int index = 0;
for (int i = 0; i < inorder.length; i++) {
if (preorder[0] == inorder[i]){
index = i;
break;
}
}
int left = index;
int right = inorder.length-index-1;
root.left = process(preorder,inorder, 1,left,0,index-1);
root.right = process(preorder,inorder,left+1,preorder.length-1, index+1,inorder.length-1);
return root;
}
public TreeNode process(int[] preorder, int[] inorder,int lp,int rp,int li, int ri){
// base case
if (lp > rp || li>ri)
return null;
if (lp == rp){
return new TreeNode(preorder[lp]);
}
if (ri == li){
return new TreeNode(preorder[li]);
}
TreeNode root = new TreeNode(preorder[lp]);
// find the root value's index in inorder array
int index = 0;
for (int i = li; i <= ri; i++) {
if (preorder[lp] == inorder[i]){
index = i;
break;
}
}
// calculate the length of left tree
int left = index-li;
// build left tree
root.left = process(preorder,inorder,lp+1,lp+left,li,index-1);
// build right tree
root.right = process(preorder,inorder,lp+left+1,rp,index+1,ri);
return root;
}
2)中后序遍历重建二叉树
根据一棵树的中序遍历与后序遍历构造二叉树。
注意:
你可以假设树中没有重复的元素。
例如,给出
中序遍历 inorder = [9,3,15,20,7]
后序遍历 postorder = [9,15,7,20,3]
返回如下的二叉树:
3
/ \
9 20
/ \
15 7
代码:
public TreeNode buildTree(int[] inorder, int[] postorder) {
if (inorder == null || inorder.length == 0)
return null;
TreeNode root = new TreeNode(postorder[postorder.length-1]);
int index = 0;
for (int i = 0; i < inorder.length; i++) {
if (postorder[postorder.length-1] == inorder[i]){
index = i;
break;
}
}
int left = index - 0;
root.left = buildTree(inorder,postorder, 0, index-1, 0, left-1);
root.right = buildTree(inorder,postorder,index+1,inorder.length-1, left, postorder.length-2);
return root;
}
public TreeNode buildTree(int[] inorder, int[] postorder, int li, int ri, int lp, int rp){
// base case
if (li>ri || lp>rp){
return null;
}
if (li == ri){
return new TreeNode(inorder[li]);
}
if (lp == rp){
return new TreeNode(inorder[lp]);
}
TreeNode node = new TreeNode(postorder[rp]);
// find the index of root index in inorder array
int index = 0;
for (int i = li; i <= ri; i++) {
if (postorder[rp] == inorder[i]){
index = i;
break;
}
}
int left = index-li;
// build left tree
node.left = buildTree(inorder,postorder,li,index-1,lp,lp+left-1);
// build right tree
node.right = buildTree(inorder,postorder,index+1,ri,lp+left,rp-1);
return node;
}
分析跟1)相似:不同在于根节点在后序数组中的最后位置。