前言
本文对二叉树的前中后序,层序遍历的思路进行了解析,并给出了编写代码的思路和逻辑。包括前中后序的递归和非递归方法,实际上都是对栈的使用,层序遍历则是对队列的理解。
一、前中后序遍历
1.递归版本
- 前序遍历: 跟结点 -> 左子树 -> 右子树
- 中序遍历: 左子树 -> 跟结点 -> 右子树
- 后序遍历: 左子树 -> 右子树 -> 跟结点
因为递归版本比较简单,这里以前序遍历为例,其他两个类似。
以上图中前序遍历结果为 FCADBEHGM
代码也比较清晰。
/**
* 递归版
*/
public static void preOrder(Node head) {
if (head == null) {
return;
}
System.out.print(head.val);//打印当前结点
preOrder(head.left);//递归打印左子树
preOrder(head.right);//递归打印右子树
}
中后序遍历类似,不再赘述。
2.非递归版本
我们知道递归实际上是间接利用了方法栈来达到目的。
我们自己实现非递归版本就是要用栈。
我们以前序为例,无论是前中后序哪一种,不同的只是打印的时机而已。
前序遍历代码如下:
public static void preOrder1(Node head) {
System.out.println("preOrder1:");
if (head == null) {
return;
}
Stack<Node> stack = new Stack<>();
while (head != null || !stack.isEmpty()) {
//一边往左树遍历,一边全部加入stack
while (head != null) {
//打印的时机就是遍历的这个顺序
System.out.print(head.val);
stack.push(head);
head = head.left;
}
//左树走到尽头,又往右边走。同时将该元素出栈。
if (!stack.isEmpty()) {
head = stack.pop().right;
}
}
}
该方法的遍历顺序如图正好是 左->根->右,所以打印时机也比较好找。
同样的方法来中序遍历的话,如下,遍历的时机则变成了把左树全加入栈中后,右树加入栈之前。
public static void midOrder1(Node head) {
System.out.println("midOrder1:");
if (head == null) {
return;
}
Stack<Node> stack = new Stack<>();
while (head != null || !stack.isEmpty()) {
while (head != null) {
stack.push(head);
head = head.left;
}
if (!stack.isEmpty()) {
head = stack.pop();
System.out.print(head.val);
head = head.right;
}
}
}
同样的方法来后序遍历的话,还需要额外的一个变量来记录右子树
public static void postOrder1(Node head) {
System.out.println("postOrder1:");
if (head == null) {
return;
}
Node cur = head;
Stack<Node> stack = new Stack<>();
while (cur != null || !stack.isEmpty()) {
while (cur != null) {
stack.push(cur);
cur = cur.left;
}
if (!stack.isEmpty()) {
cur = stack.pop();//遍历根节点之前
Node r = cur.right;
if (r != null) {
//如果右树存在,先去遍历右树,遍历完将右树置空,
stack.push(cur);
cur.right = null;
} else {//右树不存在或者右树打印过了,已经被置空
System.out.print(cur.val);
}
cur = r;
}
}
}
可以看到,cur.right = null;
这个代码是改变了二叉树的原结构,所以并不推荐这样做。下面介绍另一种解法。
这个解法是用head来记录了前置结点,可以来表示当前路线是从左树来的还是从右树来的,从而用不同的处理方法。
public static void postOrder4(Node head) {
System.out.println("postOrder4:");
if (head == null) {
return;
}
Stack<Node> stack = new Stack<>();
stack.push(head);
Node cur;
while (!stack.isEmpty()) {
cur = stack.peek();//1.先拿出这个节点
if (cur.left != null && cur.left != head && cur.right != null && cur.right != head) {
//2.这个if表示后序遍历中左树还没走完的情况,就继续走左树
//或者说 表示左边还有,且不是从左右子树返回来的 先把左边的全部加入栈
stack.push(cur.left);
} else if (cur.right != null && cur.right != head) {
//3.这个if表示左树走完了的情况,现在去右树
//或者说 表示是从左子树过来的,此时应该加入右子树
stack.push(cur.right);
} else {
//4.这个if表示左右都走完了,现在将这个节点打印
//或者说 表示要不是叶子结点要不就是右子树过来的,总之左右子树都遍历结束
System.out.println(cur.val + " ");
//5.把当前结点记录下来,表示下一个该遍历到的结点的前置结点
head = cur;
}
}
}
二、层序遍历
层序遍历的结果为 FCEADHGBM
方法很简单,用队列先进后出的特点,
过程如下
- 队列先放入F
- 在取出F之前,把C E放进队列,
- 取出C之前将A D放入队列。
- 依次进行下去就可以了。
代码如下:
/**
* 层序遍历
*/
public static void seqOrder(Node head) {
if (head == null) return;
System.out.println("seqOrder:");
Queue<Node> queue = new LinkedList<>();
queue.add(head);
while (!queue.isEmpty()) {
head = queue.poll();
System.out.print(head.val);
if (head.left != null)
queue.add(head.left);
if (head.right != null) {
queue.add(head.right);
}
}
}
总结
主要参考的是程序员代码面试指南这本书,做完题目后的一些思考和启发。加油吧!大家。