参考连接:一篇文看透二叉树,先序遍历、中序遍历、后序遍历、广度优先、深度优先,java实现
1 递归实现
1.1 前序遍历
前序遍历的顺序是:root
->left
->right
。
使用递归的形式很简单,但是因为使用了递归,所以多做了很多重复的工作,使得代码复杂度很高,但是代码简单易读。
public void preOrderTrversal(TreeNode root){
if (root!=null){
System.out.print(root.getVal()+" ");
preOrderTrversal(root.getLeft());
preOrderTrversal(root.getRight());
}
}
1.2 中序遍历
中序遍历的顺序是:left
->root
->right
。
public List<Integer> midOrderTrversal(TreeNode root){
List<Integer> res = new LinkedList<>();
if (root!=null){
midOrderTrversal(root.getLeft());
System.out.print(root.getVal()+" ");
midOrderTrversal(root.getRight());
}
return res;
}
1.3 后序遍历
后序遍历的顺序是:left
->right
->root
。
public void afterOrderTrversal(TreeNode root){
if (root!=null){
afterOrderTrversal(root.getLeft());
afterOrderTrversal(root.getRight());
System.out.print(root.getVal()+" ");
}
}
1.4 案例
以上面这颗树为例,下面是三种遍历结果的序列:
2 非递归实现
上面有讲过,递归实现的算法,虽然代码简单清晰,但是过程中,多做了很多无畏的计算,增加了代码的复杂度。下面介绍一下非递归的实现方式。
2.1 前序遍历
前序遍历的顺序是:root
->left
->right
。
- 思路1
基本思路:使用栈替代递归,因为递归在电脑系统中也是栈的一种应用。前序遍历相对较简单,任何节点都可以看成是一个根节点,所以没遇到一个节点先直接输出,在将当前节点的右子树先放入栈中,再将左子树放入栈中(因为栈式先入后出),直到最后一个节点被输出完毕即可。
public void preOrder(TreeNode root) {
if (null == root)
return;
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode t = stack.pop();
System.out.print(t.val + " ");
if (t.right != null) {
stack.push(t.right);
}
if (t.left != null) {
stack.push(t.left);
}
}
System.out.println();
}
- 思路2
对于前序遍历,还有第二种思路,就是先把当前二叉树的根节点和左子树添加到栈中,如果当前节点没有左子树了,那么久退出当前的循环,并将当前节点退回到上一步,级当前节点的父节点,然后访问其父节点的右子树。
public void preOrder2(TreeNode root) {
if (null == root)
return;
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while (null != cur || !stack.empty()) {
while (null != cur) { //先把根和左子树遍历并添加到栈
System.out.print(cur.val + " ");
stack.push(cur);
cur = cur.left;
}
if (!stack.empty()) { //当cur没有左子树时,此时需要出栈,进入到右子树的循环中
cur = stack.pop();
cur = cur.right;
}
}
}
2.2 中序遍历
中序遍历的顺序是:left
->root
->right
。
中序遍历和前序遍历有些类似,只需要将输出位置做一个简单的调整即可。因为中序遍历是左子树先被访问,然后才是根节点被访问。
public void inOrder(TreeNode root) {
if (null == root) {
return;
}
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while (cur != null || !stack.empty()) {
while (cur != null) {
stack.push(cur);
cur = cur.left;
}
if (!stack.empty()) {
cur = stack.pop();//此处被弹出的是左子树
System.out.print(cur.val + " ");//输出位置放到这里了
cur = cur.right;
}
}
System.out.println();
}
2.3 后序遍历
后序遍历的顺序是:left
->right
->root
。
(1)方法一
后序遍历相对前序和中序有点复杂,因为根节点是在左右子树都被访问过之后才能被访问,所以需要对上一次被访问的节点进行标记,如果上一次访问的是左子树,则继续遍历右子树,如果上一次访问的是右子树,则输出根节点的值;还有一种情况就是当前节点没有右子树,也是直接输出根节点的值。
public void postOrder(TreeNode root) {
if (null == root)
return;
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
TreeNode lastVisited = null;
while (null != cur) { //从根节点出发,将该二叉树的所有左子树都放入栈中
stack.push(cur);
cur = cur.left;
}
while (!stack.empty()) {
cur = stack.pop();
if (cur.right == null || lastVisited == cur.right) { //如果当前根节点右子树为空或者右子树被访问过 则输出当前根节点的值
System.out.print(cur.val + " ");
lastVisited = cur;
} else { //否则,需要遍历右子树
stack.push(cur); //将当前节点放入栈中,因为还没有被访问
cur = cur.right; //节点移动到当前节点的右子树上
while (null!=cur){ // 对右子树做一次相同的事情
stack.push(cur);
cur =cur.left;
}
}
}
System.out.println();
}
(2)方法二
后序遍历和前序遍历可以看成是两个相反的遍历过程,只不过后序遍历将左右子树的顺序换了一下,后序遍历的顺序为:left
->right
->root
,而前序遍历的顺序为:root
->left
->right
。可以看出他们其实是两个相反的过程,所以可以使用前序遍历的方法进行后序遍历,只不过遍历后的结果需要进行逆序输出。
public List<Integer> postOrder(TreeNode root) {
if (null == root)
return;
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
List<Integer> res = new ArrayList<>();
while (!stack.isEmpty()) {
TreeNode t = stack.pop();
if (t.left != null) {
stack.push(t.left);
}
if (t.right != null) {
stack.push(t.right);
}
res.add(0, t.val); //逆序输出
}
return res;
}
2.4 非递归完整代码
package middle;
import kotlin.reflect.jvm.internal.impl.util.collectionUtils.ScopeUtilsKt;
import utils.tree.TreeNode;
import java.util.Stack;
/**
* 用非递归的方式实现树的三种深度优先遍历
*/
public class DfsStack {
public void preOrder2(TreeNode root) {
if (null == root)
return;
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while (null != cur || !stack.empty()) {
while (null != cur) { //先把根和左子树遍历并添加到栈
System.out.print(cur.val + " ");
stack.push(cur);
cur = cur.left;
}
if (!stack.empty()) { //当cur没有左子树时,此时需要出栈,进入到右子树的循环中
cur = stack.pop();
cur = cur.right;
}
}
}
public void preOrder(TreeNode root) {
if (null == root)
return;
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode t = stack.pop();
System.out.print(t.val + " ");
if (t.right != null) {
stack.push(t.right);
}
if (t.left != null) {
stack.push(t.left);
}
}
System.out.println();
}
public void inOrder(TreeNode root) {
if (null == root) {
return;
}
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while (cur != null || !stack.empty()) {
while (cur != null) {
stack.push(cur);
cur = cur.left;
}
if (!stack.empty()) {
cur = stack.pop();
System.out.print(cur.val + " ");
cur = cur.right;
}
}
System.out.println();
}
/**
* 此种方法输出的是后序遍历的相反的序列,将其反向即是正确的遍历顺序
* @param root
*/
public void afterOrder(TreeNode root) {
if (null == root)
return;
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
stack.push(cur);
while (cur!=null||!stack.isEmpty()) {
while (cur!=null){
stack.push(cur);
cur = cur.left;
}
if (!stack.empty()){
TreeNode t = stack.pop();
System.out.print(t.val+" ");
cur = cur.right;
}
}
System.out.println();
}
public void afterOrder2(TreeNode root) {
if (null == root)
return;
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
TreeNode lastVisited = null;
while (null != cur) { //从根节点出发,将该二叉树的所有左子树都放入栈中
stack.push(cur);
cur = cur.left;
}
while (!stack.empty()) {
cur = stack.pop();
if (cur.right == null || lastVisited == cur.right) { //如果当前根节点右子树为空或者右子树被访问过 则输出当前根节点的值
System.out.print(cur.val + " ");
lastVisited = cur;
} else { //否则,需要遍历右子树
stack.push(cur); //将当前节点放入栈中,因为还没有被访问
cur = cur.right; //节点移动到当前节点的右子树上
while (null!=cur){ // 对右子树做一次相同的事情
stack.push(cur);
cur =cur.left;
}
}
}
System.out.println();
}
public static void main(String[] args) {
DFS dfs = new DFS();
DfsStack dfsStack = new DfsStack();
System.out.println("前序遍历:");
dfs.preOrderTrversal(root);
System.out.println();
dfsStack.preOrder(root);
System.out.println("中序遍历");
dfs.midOrderTrversal(root);
System.out.println();
dfsStack.inOrder(root);
System.out.println("后序遍历:");
dfs.afterOrderTrversal(root);
System.out.println();
dfsStack.afterOrder2(root);
}
}