本文不讨论二叉树层次遍历
刷题的时候看到一些二叉树遍历的解法,整理在这里作为笔记,也分享给大家
语言是 Java 的,我会采取代码+图解+说明形式来尽可能讲明白每种遍历方式
目录
一些准备
树节点类代码(TreeNode)
树节点类图解
工程文件结构
工程文件说明
递归解法(RecursiveTraversal)
递归解法复杂度
前序(递归)
中序(递归)
后序(递归)
迭代解法(IterativeTraversal)
迭代解法复杂度
前序(迭代,单层循环)
前序(迭代,双层循环)
中序(迭代,双层循环)
后序(迭代,单层循环)
后序(迭代,双层循环)
5种迭代解法图解
Morris 解法(MorrisTraversal)
Morris 解法复杂度
前序(Morris)
中序(Morris)
为了便于整理和展示,我建立了一个简单的纯 Java 工程来测试各种解法
一个简单的树节点类:
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode() {
}
TreeNode(int val) {
this.val = val;
}
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
这是一棵二叉树,它拥有四个树节点,左侧虚线框内是它的简化图
时间复杂度 | 空间复杂度 |
---|---|
O(n) | O(n) |
根左右
public static void preOrder(TreeNode root) {
if (root == null) return;
System.out.print(root.val + " ");
preOrder(root.left);
preOrder(root.right);
}
左根右
public static void inOrder(TreeNode root) {
if (root == null) return;
inOrder(root.left);
System.out.print(root.val + " ");
inOrder(root.right);
}
左右根
public static void postOrder(TreeNode root) {
if (root == null) return;
postOrder(root.left);
postOrder(root.right);
System.out.print(root.val + " ");
}
时间复杂度 | 空间复杂度 |
---|---|
O(n) | O(n) |
需要区分的一点是:
尽管子树和节点看起来都是一个 TreeNode,但它们之间存在一些区别!
另外,这里使用 LinkedList(双向链表),而非 Stack
因为 Stack 底层是基于数组实现的,对于频繁插入和删除的场景下,并不太适合
注意:选择数组或链表实现栈的优劣取决于二叉树的分布特点
对于单层循环方式,我们利用栈的后进先出特点,要先压入右子节点,再压入左子节点
public static void preOrder1(TreeNode root) { // 前序单层循环
if (root == null) return;
Deque stack = new LinkedList<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
System.out.print(node.val + " ");
if (node.right != null) stack.push(node.right);
if (node.left != null) stack.push(node.left);
}
}
内循环中压入子树的所有左子节点
public static void preOrder2(TreeNode root) { // 前序双层循环
if (root == null) return;
TreeNode node = root;
Deque stack = new LinkedList<>();
while (!stack.isEmpty() || node != null) {
while (node != null) {
System.out.print(node.val + " ");
stack.push(node);
node = node.left;
}
node = stack.pop();
node = node.right;
}
}
对于单层循环方式,我们必须先把根节点压入栈中,所以它无法实现中序遍历
内循环中压入子树的所有左子节点
public static void inOrder(TreeNode root) { // 中序双层循环
if (root == null) return;
TreeNode node = root;
Deque stack = new LinkedList<>();
while (!stack.isEmpty() || node != null) {
while (node != null) {
stack.push(node);
node = node.left;
}
node = stack.pop();
System.out.print(node.val + " ");
node = node.right;
}
}
对于单层循环方式,后序遍历可以采用一个讨巧的办法:逆序输出
这需要额外的开销(对于下面的代码,既有辅助栈的空间开销,也有逆序的时间开销)
public static void postOrder1(TreeNode root) { // 后序单层循环
if (root == null) return;
Deque stack1 = new LinkedList<>();
Deque stack2 = new LinkedList<>();
stack1.push(root);
while (!stack1.isEmpty()) {
TreeNode node = stack1.pop();
stack2.push(node);
if (node.left != null) stack1.push(node.left);
if (node.right != null) stack1.push(node.right);
}
while (!stack2.isEmpty()) {
System.out.print(stack2.pop().val + " ");
}
}
内循环中压入子树的所有左子节点
public static void postOrder2(TreeNode root) { // 后序双层循环
if (root == null) return;
TreeNode node = root;
TreeNode prev = null;
Deque stack = new LinkedList<>();
while (!stack.isEmpty() || node != null) {
while (node != null) {
stack.push(node);
node = node.left;
}
node = stack.pop();
if (node.right == null || node.right == prev) {
System.out.print(node.val + " ");
prev = node;
node = null;
} else {
stack.push(node);
node = node.right;
}
}
}
我绘制了5种解法的核心代码 N-S 图,以便于横向对比
时间复杂度 | 空间复杂度 |
---|---|
O(n) | O(1) |
public static void preOrder(TreeNode root) {
if (root == null) return;
TreeNode cur1 = root;
TreeNode cur2 = null;
while (cur1 != null) {
cur2 = cur1.left;
if (cur2 != null) {
// 找到左子树的最右叶子节点
while (cur2.right != null && cur2.right != cur1) {
cur2 = cur2.right;
}
if (cur2.right == null) {
cur2.right = cur1;
System.out.print(cur1.val + " ");
cur1 = cur1.left;
continue;
} else {
cur2.right = null;
}
} else {
System.out.print(cur1.val + " ");
}
cur1 = cur1.right;
}
}
public static void inOrder(TreeNode root) {
if (root == null) return;
TreeNode cur1 = root;
TreeNode cur2 = null;
while (cur1 != null) {
cur2 = cur1.left;
if (cur2 != null) {
// 找到左子树的最右叶子节点
while (cur2.right != null && cur2.right != cur1) {
cur2 = cur2.right;
}
if (cur2.right == null) {
cur2.right = cur1;
cur1 = cur1.left;
continue;
} else {
cur2.right = null;
}
}
System.out.print(cur1.val + " ");
cur1 = cur1.right;
}
}