前言
我们知道,树是一种非常重要的数据结构,在各种竞赛和各大企业笔试题中,关于树,特别是二叉树的题目出现频率都很高。
今天我们来掌握二叉树的前序、中序遍历,并且给出递归和非递归两种实现。
用节点表示树
package org.lanqiao.algo;
public class TreeNode<T> {
public T val ;
public TreeNode<T> left = null;
public TreeNode<T> right = null;
public TreeNode(T val) {
this.val = val;
}
}
中序遍历
所谓中序遍历,是根节点居中出现的一种遍历方式,遵循左-根-右的原则。
递归形式:
public static void inorderIter(TreeNode node, Consumer consumer) {
if (null != node.left)
inorderIter(node.left, consumer);
consumer.accept(node.val);
if (null != node.right)
inorderIter(node.right, consumer);
}
比较复杂的是非递归形式,非递归形式要借用栈这种结构:
- 先顺着根节点把所有左支路的节点压入栈中,这时,栈顶是最左的叶子节点
- 接下来开始弹弹弹
- 首先弹出的肯定是最左叶子
- 弹出来的对象供消费
- 重点来了,如果弹出的节点有右孩子,就暂停弹出,应该从这个右孩子开始回到步骤起点,把右孩子作为起点重新来一轮
public static void inorderIterNoRecursion(TreeNode node, Consumer consumer) {
Stack<TreeNode> stack = new Stack<>();
TreeNode curr = node;
while (curr != null) {
// 顺着当前节点将左支路的节点从上往下加入栈中,最后:最左叶子节点在栈顶
while (curr != null) {
stack.add(curr);
curr = curr.left;
}
//接下来开始弹弹弹,首先弹出的肯定是最左叶子
while (!stack.isEmpty()) {
TreeNode pop = stack.pop();
consumer.accept(pop.val);// 弹出来的对象供消费
//重点来了,如果弹出的节点有右孩子,就暂停弹出,应该从右孩子开始回到外层循环的起点,把右孩子作为起点重新来一轮
if (pop.right != null) {
curr = pop.right;
break;
}
}
}
}
前序遍历
前序遍历,是根节点靠前出现的一种形式,遵循根-左-右的原则。
递归形式:
public static void preIter(TreeNode<Integer> node) {
System.out.print(node.val + "\t");
if (null != node.left)
preIter(node.left);
if (null != node.right)
preIter(node.right);
}
非递归形式:
- 根节点入栈
- 循环开始
- 弹出栈顶
- 栈顶的右孩子入栈
- 栈顶的左孩子入栈
- 栈空结束循环
public static void preIterNoRecursion(TreeNode<Integer> node) {
TreeNode curr = node;
Stack<TreeNode> stack = new Stack<>();
stack.add(curr);
while (!stack.isEmpty()) {
//弹出栈顶
TreeNode pop = stack.pop();
System.out.print(pop.val + "\t");
if (pop.right != null) {
stack.add(pop.right);
}
if (pop.left != null) {
stack.add(pop.left);
}
}
}
牢记这些代码,一些关于树遍历的变种题,会比较好解