二叉树的遍历
二叉树遍历包括前序遍历、中序遍历、后续遍历和层序遍历
- 前序遍历:根节点 -> 左子树 -> 右子树
- 中序遍历:左子树 -> 根节点 -> 右子树
- 后序遍历:左子树 -> 右子树 -> 根节点
- 层序遍历:按照二叉树的层级,从上到下,从左到右遍历
前序遍历、中序遍历和后序遍历都有递归和非递归的方法
递归
递归的形式很简单,先序遍历是每次访问到了节点就输出,然后依次递归的遍历左子树和右子树;中序遍历是访问完了左子树,在输出根节点;后序遍历则是访问完了左子树和右子树后再回到根节点输出。三种遍历递归的形式是一致的,只是访问的顺序不一样,代码如下
public static void preOrder(TreeNode root) {
if (root != null) {
System.out.print(root.val + " ");
preOrder(root.left);
preOrder(root.right);
}
}
public static void inOrder(TreeNode root) {
if (root != null) {
inOrder(root.left);
System.out.print(root.val + " ");
inOrder(root.right);
}
}
public static void postOrder(TreeNode root) {
if (root != null) {
postOrder(root.left);
postOrder(root.right);
System.out.print(root.val + " ");
}
}
非递归
在树的遍历过程中,我们都是遵循着一样的遍历路线,但是访问的时机不一样。我们的遍历路线都是顺着左子树深入,当深入左子树到底的时候再深入右子树,只不过先序遍历是每次遍历到节点的时候先访问节点,中序遍历是左子树遍历完了再访问节点,后序遍历是左右子树都遍历完了再访问节点。从中序遍历着手,我们发现遍历的时候依次深入遍历左子树直到停止左子树的遍历,然后输出最后一个遍历的左子树节点,这种结构有点像是栈的后进先出,所以可以考虑使用一个栈来保存数据;那么先序遍历就是入栈之前就访问节点,中序遍历是入栈后再出栈的时候访问节点;后序遍历复杂一点,因为节点出栈后,还要先访问其右子树,之后才能访问该节点,那么可以将节点再次入栈,然后访问其右子树,这样的话,节点就有两次入栈,所以需要额外的变量记录节点是不是第一次入栈。我们可以更改树的结构,新增一个变量来实现。
非递归的代码如下:
public static void preOrder(TreeNode root) {
LinkedList<TreeNode> stack = new LinkedList<>();
while ( root != null || !stack.isEmpty()) {
// 左子树压栈直到左子树为空
while (root != null) {
// 在入栈前访问
System.out.print(root.val + " ");
stack.push(root);
root = root.left;
}
root = stack.pop();
// 转向右子树
root = root.right;
}
}
public static void inOrder(TreeNode root) {
LinkedList<TreeNode> stack = new LinkedList<>();
while ( root != null || !stack.isEmpty()) {
// 左子树压栈直到左子树为空
while (root != null) {
stack.push(root);
root = root.left;
}
root = stack.pop();
// 出栈时访问
System.out.print(root.val + " ");
// 转向右子树
root = root.right;
}
}
private static void postOrder(TreeNode root) {
LinkedList<TreeNode> stack = new LinkedList<>();
while (root != null || !stack.isEmpty()) {
// 左子树压栈直到左子树为空
while (root != null) {
stack.push(root);
root.isFirst = true; // 表明是第一次入栈
root = root.left;
}
root = stack.pop();
if (root.isFirst) {
stack.push(root);
root.isFirst = false;
root = root.right;
} else {
System.out.print(root.val + " ");
root = null;
}
}
}
还有一种先序遍历和后序遍历的方法
对于先序遍历,我们先把根节点入栈,在出栈的时候访问,然后把根节点的左右子节点入栈,根据栈的特点,这个时候应该先入栈右节点,再入栈左节点,这样出栈的时候就是先访问的左节点,然后把该左节点的右节点和左节点分别入栈,依次进行下去,就能得到先序遍历的结果
private static void preOrder2(TreeNode root) {
LinkedList<TreeNode> stack = new LinkedList<>();
if (root != null)
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.poll();
System.out.print(node.val + " ");
if (node.right != null)
stack.push(node.right);
if (node.left != null)
stack.push(node.left);
}
}
用同样的方式考虑后序遍历的时候,发现用一个栈没有办法一步到位,发现还需要一个辅助栈来存储出栈的结果。
与上述先序遍历类似,先入栈根节点,然后入栈左节点,在入栈右节点,出栈的时候,将出栈的节点再存入一个新的栈中。
private static void postOrder2(TreeNode root) {
LinkedList<TreeNode> stack = new LinkedList<>();
LinkedList<TreeNode> result = new LinkedList<>();
if (root != null)
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
result.push(node); // 出栈的结果存入新的栈中
if (node.left != null)
stack.push(node.left);
if (node.right != null)
stack.push(node.right);
}
for (TreeNode node : result) {
System.out.print(node.val + " ");
}
}
层序遍历
层序遍历可以用一个队列来辅助,比较简单,具体如下:
private static void levelOrder(TreeNode root) {
LinkedList<TreeNode> queue = new LinkedList<>();
if (root != null)
queue.offer(root);
while (!queue.isEmpty()) {
TreeNode node = queue.poll();
System.out.print(node.val + " ");
if (node.left != null)
queue.offer(node.left);
if (node.right != null)
queue.offer(node.right);
}
}
层序遍历和上述的先序遍历和后序遍历的方法时候有相似性的,只不过前者是用队列后者是用栈来实现的。