在这里总结一下二叉树的几种遍历各自的递归与非递归写法。
目录
如果对这几种遍历还不了解的,可以先了解一下。对于一棵如下的二叉树:
其先序遍历为:(根左右)ABDECFG
中序遍历为:(左根右)DBEAFCG
后序遍历为:(左右根)DEBFGCA
层次遍历为:ABCDEFG
0. 树节点的定义:
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
1. 先序遍历:
(助记顺序:根左右)
1.1 递归实现:
基本思路就是每到一个节点先输出其自己的值(根),然后再遍历其的左节点(左),直到没有左节点了,开始遍历右节点(右),直到叶子节点然后返回,每一层都进行类似步骤。
public static void preOrderRec(TreeNode pRoot) {
if (pRoot != null) {
System.out.print(pRoot.val + " ");
preOrderRec(pRoot.left);
preOrderRec(pRoot.right);
}
}
1.2 非递归实现:
非递归方法的基本思路就是利用栈,但可以有多种不同实现方式,这里我先输出当前节点(根),然后将所以其左节点压到栈中,方便后面出栈来处理其的右节点,然后把当前的对象指针指到其左节点(左),即代码中的 pRoot = pRoot.left ,然后 while 判断pRoot 不为空,所以继续将其输出,然后移到其左节点,直到没有左节点了,通过出栈开始处理右节点(右)。
import java.util.Stack;
public void preOreder(TreeNode pRoot) {
//通过栈来实现先序遍历的非递归实现
Stack<TreeNode> stack = new Stack<>();
while (true) {
//输出当前节点,并将当前节点压栈,然后寻找其是否还有左节点
while (pRoot != null) {
System.out.print(pRoot.val + " ");
stack.push(pRoot);
pRoot = pRoot.left;
}
//唯一出口
if (stack.empty()) break;
//弹出栈顶节点,可以理解为对其右子树再进行先序遍历
pRoot = stack.pop();
pRoot = pRoot.right;
}
}
2. 中序遍历:
(助记顺序:左根右)
2.1 递归方法:
递归的方法和先序很像,只是把输出移到了中间位置,这样就会让其先遍历完左子树,到达最左叶子节点后开始输出(左),因为是叶子节点,所以返回上一层输出(根),再遍历当层的右子树(右)。
public void inOrderRec(TreeNode pRoot) {
if (pRoot != null) {
inOrderRec(pRoot.left);
System.out.print(pRoot.val + " ");
inOrderRec(pRoot.right);
}
}
2.2 非递归方法:
这里非递归的方法也和先序很相似,都是改变输出的位置,只要理解了就很容易记忆。因为是先压所有的左节点直到叶子节点,所以保证它已经没有左节点,所以可以输出,然后再判断其是否有右子树。
import java.util.Stack;
public void inOrder(TreeNode pRoot) {
//通过栈来实现中序遍历的非递归实现
Stack<TreeNode> stack = new Stack<>();
//输出当前节点,并将当前节点压栈,然后寻找其是否还有左节点
while (true) {
while (pRoot != null) {
//只是压入,因为输入顺序是“左根右”,所以还不能输出
stack.push(pRoot);
pRoot = pRoot.left;
}
//唯一出口
if (stack.empty()) return;
pRoot = stack.pop();
//由于上面将左节点压入,所以这里输出就是先输出左节点
System.out.print(pRoot.val + " ");
pRoot = pRoot.right;
}
}
3. 后序遍历:
(助记顺序:左右根)
3.1 递归方法:
三种递归遍历方法其实都是一个样子,只是改变了输出的位置。
public void postOrderRec(TreeNode pRoot) {
if (pRoot != null) {
postOrderRec(pRoot.left);
postOrderRec(pRoot.right);
System.out.print(pRoot.val + " ");
}
}
3.2 非递归方法:
这里我的非递归方法是参考这里的博客的,我们可以对先序遍历进行修改,修改成顺序为根右左的遍历顺序,然后只要将其倒过来输出就是我们所要的后序遍历了。
import java.util.Stack;
public static void postOreder(TreeNode pRoot) {
//用来进行根右左遍历的栈
Stack<TreeNode> stack = new Stack<>();
//保存逆序输出结果的栈
Stack<TreeNode> resStack = new Stack<>();
while (true) {
//改为先压入右子树
while (pRoot != null) {
stack.push(pRoot);
resStack.push(pRoot);
pRoot = pRoot.right;
}
if (stack.empty()) break;
pRoot = stack.pop();
pRoot = pRoot.left;
}
//将结果逆序输出,即为后序遍历左右根的顺序
while (!resStack.empty()) {
System.out.print(resStack.pop().val + " ");
}
}
4. 层次遍历:
与树的前中后序遍历不同的是,层次遍历用到的是BFS的思想,而前中后序遍历是的DFS思想。一般的DFS都可用递归或者栈来实现,而对于BFS则需要使用队列来实现。
4.1 层次遍历的遍历过程:
- 对于不为空的节点,就将该节点进队
- 从队列中出列一个节点,如果它有左节点,则将左节点进队;如果有右节点,就将右节点进队。
- 如此操作直到队空为止
实现代码如下:
import java.util.Queue;
public static void levelOrder(TreeNode pRoot) {
if (pRoot == null) return;
//申请一个队列
Queue<TreeNode> queue = new LinkedList<>();
//首节点入队
queue.offer(pRoot);
while (!queue.isEmpty()) {
//出队一个节点
pRoot = queue.poll();
System.out.print(pRoot.val + " ");
//如果左右节点存在,则将其左右节点入队
if (null != pRoot.left)
queue.offer(pRoot.left);
if (null != pRoot.right) {
queue.offer(pRoot.right);
}
}
}