0. 介绍
递归方法实现树的遍历很容易,但如果限定使用非递归方式来实现树的递归会相对麻烦一些,这篇blog旨在使用相对容易理解的方式来实现树的非递归遍历。需要理解的是,虽然没有使用递归,但是实现的本质还是与递归一致,即需要使用栈FILO或者队列FIFO来实现结点的延迟访问,这是由于顺序执行的机制所决定的,比如说后序遍历虽然是要求以“左-右-根”的顺序进行遍历,但是实际上最先访问的还是根节点。
1. 前序遍历(Pre-Order Traversal)
1.1 思路
前序遍历要求我们以“根-左-右”的顺序来遍历结点,这里我们可以使用栈来实现。
- 先把root结点push入栈
- 栈顶元素pop出栈
- 把pop元素的右、左结点push入栈
- 循环2、3步骤直到栈为空栈
需要注意的点就是第3步中要先压入右结点再压入左结点才能够满足前序遍历“根-左-右”的遍历要求。
1.2 代码实现
public void preOrder(Node root) {
//栈
Stack<Node> stack = new Stack<>();
//curr表示正在操作的结点
Node curr = root;
//把curr push进栈
stack.push(curr);
//当栈不为空的时循环
while (!stack.empty()) {
//pop出栈顶的结点,同时把该结点的孩子结点入栈
Node pop = stack.pop();
//访问操作
//do something with pop...
//先压入右结点
if (pop.right != null) stack.push(pop.right);
//压入左结点
if (pop.left != null) stack.push(pop.left);
}
}
运行结果:
注:运行结果,均以前文图中的树为示例。
2. 后序遍历(Post-Order Traversal)
2.1 思路
后序遍历要求的是以“左-右-根”的顺序来遍历树结点。这里把后序遍历放在中序遍历之前,是因为后序遍历本质上和前序遍历的代码逻辑差不多,只需要在前序遍历代码基础上添加几行代码就可以实现后序遍历。需要说明的是,正如前面介绍中所说的,由于顺序执行的机制,此处的后序遍历并非真正按照“左-右-根”的顺序来执行。
在前序遍历中,我们是以“根-左-右”的顺序实现了树的遍历,而后序遍历唯一的不同就是根结点的访问从前面移到了后面,所以我们大可以把前序遍历的访问顺序倒转一下就可以实现后续遍历了。具体的做法可以是再声明一个栈,只需要按照前序遍历的访问顺序压入栈中,就可以实现后序遍历了。即“根-右-左”经过倒转就成了“左-右-根”。
-
先把root结点push入栈
-
栈顶元素pop出栈
a.把pop出的元素压入另一个栈中
-
把pop元素的左、右结点push入栈
-
循环2、3步骤直到栈为空栈
b.访问栈中元素即为后序遍历的顺序
2.2 代码实现
public void postOrder(Node root) {
//栈1
Stack<Node> stack = new Stack<>();
//栈2
Stack<Node> res = new Stack<>();
//curr表示正在操作的结点
Node curr = root;
//把curr push进栈
stack.push(curr);
//当栈不为空的时循环
while (!stack.empty()) {
//pop出栈顶的结点,同时把该结点的孩子结点入栈
Node pop = stack.pop();
//pop元素压入res栈中
res.push(pop);
//压入左结点
if (pop.left != null) stack.push(pop.left);
//先压入右结点
if (pop.right != null) stack.push(pop.right);
}
//后续遍历
while(!res.empty()) {
//访问操作
Node pop = res.pop();
//do some with pop元素...
}
}
运行结果:
3. 中序遍历(In-Order Traversal)
3.1 思路
中序遍历的访问顺序要求是“左-根-右”。如之前所说,由于顺序访问机制,和后序遍历一样,不能直接从最左结点开始访问,但是中序遍历并不需要一个额外的容器来存储结点以实现延迟访问,这是因为结点本身就持有右结点。所以可以有如下的实现:
- 把curr以及curr的左结点循环入栈,直到curr为叶子结点
- 栈中元素pop出栈
以上图为例,1步骤后,栈中元素为:F -> B -> A,2步骤将A元素出栈,循环一次后将B元素出栈,此时我们想访问D元素该怎么做呢?很简单,直接把D元素赋给curr即可,现在就构成了一个循环能够按照中序遍历的顺序访问树的所有结点了。剩下的实现为:
- 把pop结点的右结点赋给curr
- 循环1、2、3步骤
3.2 代码实现
public static void inOrder(Node root) {
//栈
Stack<Node> stack = new Stack<>();
//curr表示当前结点
Node curr = root;
//栈不为空 or curr不为null时循环
while (!stack.empty() || curr != null) {
//curr的左子树全部入栈
while (curr != null) {
stack.push(curr);
curr = curr.left;
}
//栈顶元素pop出栈
Node pop = stack.pop();
//do something with pop...
System.out.print(pop.value + "->");
//curr = pop的右结点,无论是否为null
curr = pop.right;
}
System.out.println("\b\b");
}
运行结果:
附:详细测试代码
package com.structure.demo;
import java.util.Stack;
/**
* @Classname Demo
* @Description TODO
* @Date 2022-7-11 22:18
* @Created by Yang Yi-zhou
*/
public class Demo {
public static void main(String[] args) {
Node root = INIT();
// postOrder(root);
// preOrder(root);
inOrder(root);
}
public static void inOrder(Node root) {
//栈
Stack<Node> stack = new Stack<>();
//curr表示当前结点
Node curr = root;
//栈不为空 or curr不为null 时循环
while (!stack.empty() || curr != null) {
//curr的左子树全部入栈
while (curr != null) {
stack.push(curr);
curr = curr.left;
}
//栈顶元素pop出栈
Node pop = stack.pop();
//do something with pop...
System.out.print(pop.value + "->");
//curr = pop的右结点,无论是否为null
curr = pop.right;
}
System.out.println("\b\b");
}
public static void preOrder(Node root) {
//栈
Stack<Node> stack = new Stack<>();
//curr表示正在操作的结点
Node curr = root;
//把curr push进栈
stack.push(curr);
//当栈不为空的时循环
while (!stack.empty()) {
//pop出栈顶的结点,同时把该结点的孩子结点入栈
Node pop = stack.pop();
//访问操作
//do something with pop...
System.out.print(pop.value + "->");
//先压入右结点
if (pop.right != null) stack.push(pop.right);
//压入左结点
if (pop.left != null) stack.push(pop.left);
}
System.out.println("\b\b");
}
public static void postOrder(Node root) {
//栈1
Stack<Node> stack = new Stack<>();
//栈2
Stack<Node> res = new Stack<>();
//curr表示正在操作的结点
Node curr = root;
//把curr push进栈
stack.push(curr);
//当栈不为空的时循环
while (!stack.empty()) {
//pop出栈顶的结点,同时把该结点的孩子结点入栈
Node pop = stack.pop();
//pop元素压入res栈中
res.push(pop);
//压入左结点
if (pop.left != null) stack.push(pop.left);
//先压入右结点
if (pop.right != null) stack.push(pop.right);
}
//后续遍历
while (!res.empty()) {
//访问操作
Node pop = res.pop();
//do some with pop元素...
System.out.print(pop.value + "->");
}
System.out.println("\b\b");
}
private static Node INIT() {
Node<String> A = new Node<>("A");
Node<String> B = new Node<>("B");
Node<String> C = new Node<>("C");
Node<String> D = new Node<>("D");
Node<String> E = new Node<>("E");
Node<String> F = new Node<>("F");
Node<String> G = new Node<>("G");
Node<String> H = new Node<>("H");
Node<String> I = new Node<>("I");
F.left = B;
F.right = G;
G.right = I;
I.left = H;
B.left = A;
B.right = D;
D.left = C;
D.right = E;
return F;
}
static class Node<E> {
E value;
Node left;
Node right;
public Node(E value) {
this.value = value;
}
}
}