一、深度优先-广度优先统一的方式
由于常见的深度优先遍历是用递归的方式,而常见的广度优先遍历是用迭代的方式,所以无法体现出二者的内在联系,也就无法将二者统一。
事实上,只要用非递归(迭代)的方式实现深度优先遍历,即可将深度优先遍历与广度优先遍历统一起来,进而也就可以将几种深度优先遍历的方式(前序、中序、后序)也一起统一起来。
非递归的广度优先遍历
首先,广度优先遍历二叉树需要一个辅助队列,遍历方式如下:
// 二叉树的广度优先遍历
public List<Integer> breadthFirstSearch(TreeNode root) {
List<Integer> resultList = new ArrayList<>(); // 结果列表
Deque<TreeNode> queue = new LinkedList<>(); // 辅助队列,其中存储的是(本层和下一层中)尚未遍历的节点
if (root != null) queue.offer(root); // 根节点如果存在,就先入队
while (!queue.isEmpty()) { // 队列不为空就说明还有节点未遍历到
// 取出队列中下一个要遍历的节点(也就是队头的节点)
TreeNode node = queue.poll();
// 保存结果
resultList.add(node.val);
// 如果该节点有左、右节点,就入队
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
return resultList;
}
非递归的深度优先遍历
最简单的深度优先遍历是递归的方式,但是如何实现非递归的深度优先遍历呢?
很有趣的是,只需要把广度优先遍历中的队列换成栈,然后把左、右子节点的处理顺序对调,就变成了深度优先遍历!
除此之外,一字不改!
修改之后的遍历如下:
// 二叉树的深度优先遍历(前序遍历)
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> resultList = new ArrayList<>(); // 结果列表
Deque<TreeNode> stack = new LinkedList<>(); // 辅助栈
if (root != null) stack.offer(root); // 根节点如果存在,就先入栈
while (!stack.isEmpty()) { // 栈不为空就说明还有节点未遍历到
// 取出栈中下一个要遍历的节点(即按前序的下一个节点)
TreeNode node = stack.pop();
// 保存结果
resultList.add(node.val);
// 如果该节点有左、右节点,就入栈(注意,要先右后左)
if (node.right != null) stack.push(node.right); // 先右!
if (node.left != null) stack.push(node.left); // 后左!
}
return resultList;
}
由此,广度优先遍历与深度优先遍历完成了大一统!很有数学之美!
非递归的前序遍历
上面的深度优先遍历就是前序遍历!
非递归的后序遍历
那么前序遍历和后序遍历如何进行统一呢?
其实也很简单,只需要将左、右子节点的处理顺序换回到正常顺序,再把最终结果序列反转,就直接 OK 了!
// 二叉树的后序遍历
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> resultList = new ArrayList<>(); // 结果列表
Deque<TreeNode> stack = new LinkedList<>(); // 辅助栈
if (root != null) stack.offer(root); // 根节点如果存在,就先入栈
while (!stack.isEmpty()) { // 栈不为空就说明还有节点未遍历到
// 取出栈中下一个要遍历的节点(即按前序的下一个节点)
TreeNode node = stack.pop();
// 暂时保存结果(此时结果是倒序的)
resultList.add(node.val);
// 如果该节点有左、右节点,就入队(先左后右的正常顺序)
if (node.left != null) stack.push(node.left);
if (node.right != null) stack.push(node.right);
}
// 将倒序的结果反转为正序的结果
int size = resultList.size(), temp;
for (int i = 0; i < size >> 1; i++) {
temp = resultList.get(i);
resultList.set(i, resultList.get(size - i - 1));
resultList.set(size - i - 1, temp);
}
return resultList;
}
二、辅助栈存储深度优先遍历路径的方式
这种方式是利用一个辅助栈存储按深度优先遍历走过的路径上的、但是按要求的顺序还不应该被输出的节点,也可以很好的将前序、中序、后序三种遍历方式进行统一。
三种方式的注意区别就在于保存结果的时机不同。
非递归的前序遍历
// 二叉树的前序遍历
public List<Integer> preorderTraversal1(TreeNode root) {
List<Integer> resultList = new ArrayList<>(); // 结果列表
Deque<TreeNode> stack = new LinkedList<>(); // 辅助栈
while (!stack.isEmpty() || root != null) { // 栈不为空或者当前节点存在
// 先冲到左下角,并一路入栈 + 保存结果
while (root != null) {
stack.push(root);
resultList.add(root.val);
root = root.left;
}
// 弹出栈顶节点
root = stack.pop();
// 指向当前节点的右孩子
root = root.right;
}
return resultList;
}
非递归的中序遍历
// 二叉树的中序遍历
public List<Integer> inorderTraversal1(TreeNode root) {
List<Integer> resultList = new ArrayList<>(); // 结果列表
Deque<TreeNode> stack = new LinkedList<>(); // 辅助栈
while (!stack.isEmpty() || root != null) { // 栈不为空或者当前节点存在
// 先冲到左下角,并一路入栈
while (root != null) {
stack.push(root);
root = root.left;
}
// 弹出栈顶节点
root = stack.pop();
// 保存结果
resultList.add(root.val);
// 指向当前节点的右孩子
root = root.right;
}
return resultList;
}
非递归的后序遍历
// 二叉树的后序遍历
public List<Integer> postorderTraversal1(TreeNode root) {
List<Integer> resultList = new ArrayList<>(); // 结果列表
Deque<TreeNode> stack = new LinkedList<>(); // 辅助栈
TreeNode preNode = null; // 用于防止遍历时重复进入节点的右子节点,如果右子节点刚去过,就视为没有
while (!stack.isEmpty() || root != null) { // 栈不为空或者当前节点存在
// 先冲到左下角,并一路入栈
while (root != null) {
stack.push(root);
root = root.left;
}
// 分情况处理栈顶节点
assert stack.peek() != null;
if (stack.peek().right == null || stack.peek().right == preNode) { // 第二个条件是为了防止重复进入右子节点,即如果右子节点刚去过,就视为没有
root = stack.pop(); // 弹出栈顶节点
resultList.add(root.val); // 保存结果
preNode = root; // 更新 preNode
root = null; // 指向 null
} else {
root = stack.peek(); // 指向栈顶节点
root = root.right; // 指向当前节点的右孩子
}
}
return resultList;
}