二叉树的非递归遍历
先序遍历
首先先将头节点入栈。
之后不断判断栈中是否为空,不为空就将栈顶元素弹出,打印节点的值。
然后将弹出节点的右节点先入栈,左节点后入栈。(注意不为空时才入栈)
代码
import java.util.Stack;
public class BinaryTreeTraversal {
public static class TreeNode {
public int val;
public TreeNode left;
public TreeNode right;
public TreeNode(int v) {
val = v;
}
}
/**
* 先序遍历
* @param head
*/
public static void preOrder(TreeNode head){
if (head != null){
Stack<TreeNode> stack = new Stack<>();
stack.push(head);
while (!stack.isEmpty()){
TreeNode pop = stack.pop();
System.out.println(pop.val);
if (pop.right != null){
stack.push(pop.right);
}
if (pop.left != null) {
stack.push(pop.left);
}
}
}
}
public static void main(String[] args) {
/**
* 1
* / \
* 2 3
* / \ / \
* 4 5 6 7
*/
TreeNode root = new TreeNode(1);
root.left = new TreeNode(2);
root.right = new TreeNode(3);
root.left.left = new TreeNode(4);
root.left.right = new TreeNode(5);
root.right.left = new TreeNode(6);
root.right.right = new TreeNode(7);
preOrder(root);
}
}
中序遍历
- 当前来到某一条子树的头节点,将子树的整条左边界进栈,直到左边界遍历完。
- 从栈里弹出节点打印,把弹出节点的右树重复步骤1(打印之后,就把节点的右树看作新的子树头,看看有没有左边界,有的话继续进栈,一直到左边界遍历完)。
- 直到没有子树,并且栈空了。
图解
首先来到 1节点,将其整个左边界进栈。
之后将 4节点弹出,把4节点的右树的整个左边界进栈。
弹出6节点,之后应继续将6节点的右子树的左边界进栈,但6节点的右子树为空,所以继续弹出5节点。
5节点弹出后,其右树依然为空。栈中再弹出2节点。
2节点弹出之后,栈中压入2节点的右子树的左边界,7节点入栈。
栈弹出7节点,由于7节点没有右树,之后再弹出1节点。
将1节点的右树的整个左边界入栈,只有3节点入栈。
将栈顶元素3节点弹出后,让3节点的右树头节点的左边界入栈。
由于8、9、10均没有右树,后续的操作我们也只是将其弹出。全部弹出后,栈为空,且没有子树,中序遍历结束。
代码
import java.util.Stack;
public class BinaryTreeTraversal {
public static class TreeNode {
public int val;
public TreeNode left;
public TreeNode right;
public TreeNode(int v) {
val = v;
}
}
/**
* 中序遍历
* @param head
*/
public static void inOrder(TreeNode head){
if (head != null){
Stack<TreeNode> stack = new Stack<>();
while (!stack.isEmpty() || head != null){
if (head != null){ // 来到当前子树的头节点,头节点不为null,将其整个左边界压入栈中
stack.push(head);
head = head.left;
}else { // 头节点为null,从head = 栈中弹出元素,之后让弹出元素的右树的左边界入栈
head = stack.pop();
System.out.print(head.val + " ");
head = head.right; // 先让head指向右树头节点,下一次循环判断head不为空,继续将其左边界入栈
}
}
System.out.println();
}
}
public static void main(String[] args) {
/**
* 1
* / \
* 2 3
* / \ / \
* 4 5 6 7
*/
TreeNode root = new TreeNode(1);
root.left = new TreeNode(2);
root.right = new TreeNode(3);
root.left.left = new TreeNode(4);
root.left.right = new TreeNode(5);
root.right.left = new TreeNode(6);
root.right.right = new TreeNode(7);
inOrder(root);
}
}
后序遍历
先序遍历的顺序是 中左右。代码上我们的实现方式是先压入右节点,再压入左节点。
如果先压入左节点,再压入右节点,得到的顺序是中右左。
在此基础上,将这个顺序反转,就是左右中,也就是后序遍历。
两个栈实现
package study.tree;
import java.util.Stack;
public class BinaryTreeTraversal {
public static class TreeNode {
public int val;
public TreeNode left;
public TreeNode right;
public TreeNode(int v) {
val = v;
}
}
/**
* 后序遍历(两个栈实现)
* @param head
*/
public static void posOrder(TreeNode head) {
if (head != null){
Stack<TreeNode> stack = new Stack<>();
Stack<TreeNode> collect = new Stack<>();
stack.push(head);
while (!stack.isEmpty()){
/**
* 注意,整段代码中节点的入栈顺序是先压左,再压右
* 如果这里我们将弹出的元素打印,得到的顺序是中右左
* 现在我们不打印,而是将 pop 元素 压入collect栈中
* 这样原来先打印的元素,最后出栈,得到的顺序就是左右中了
*/
TreeNode pop = stack.pop();
collect.push(pop);
if (pop.left != null){
stack.push(pop.left);
}
if (pop.right != null){
stack.push(pop.right);
}
}
while (!collect.isEmpty()){
System.out.print(collect.pop().val + " ");
}
}
}
public static void main(String[] args) {
/**
* 1
* / \
* 2 3
* / \ / \
* 4 5 6 7
*/
TreeNode root = new TreeNode(1);
root.left = new TreeNode(2);
root.right = new TreeNode(3);
root.left.left = new TreeNode(4);
root.left.right = new TreeNode(5);
root.right.left = new TreeNode(6);
root.right.right = new TreeNode(7);
posOrder(root);
}
}
一个栈实现
一个栈实现版本引入了一个变量记录上一次打印的节点。h 就是这个变量。
如果始终没有打印过节点,h就一直是头节点。
一旦打印过节点,h就变成打印节点。
之后h的含义:上一次打印的节点。
图解
我们先将1节点(h)入栈。然后将栈顶元素peek,并没有弹出。(此时的h等于peek)
条件判断分支的第一条,peek.left != null && h != peek.left && h != peek.right,成立,将peek的左孩子2节点压入栈。
之后进入下一次循环判断。
将栈顶元素peek,即2节点。
条件判断分支的第一条,peek.left != null && h != peek.left && h != peek.right,成立,将peek的左孩子4节点压入栈。
栈顶元素peek,即4节点。
4节点 既没有左树也没有右树,条件判断分支的第三条,直接打印4节点,并且将4节点弹出,h指向弹出的4节点。
栈顶元素peek,即2节点
条件判断分支的第二条,cur.right != null && h != cur.right 成立,将2节点的右节点5压入栈。
栈顶元素peek,即5节点。
条件判断分支的第三条成立,打印5,把5弹出,h指向5。
栈顶元素peek,即2节点。
此时h指向5,peek.left != h,但是peek.right == h。
h来到peek的右节点,说明peek的左右子树都处理过了,现在就该处理peek了。
条件判断分支的第三条成立,打印2,把2弹出,h指向2。
2弹出之后,peek栈顶元素为1。
条件判断分支的第二条成立,然后3入栈。(之后的流程与之前类似,不多赘述了)
代码
import java.util.Stack;
public class BinaryTreeTraversal {
public static class TreeNode {
public int val;
public TreeNode left;
public TreeNode right;
public TreeNode(int v) {
val = v;
}
}
/**
* 后序遍历(一个栈实现)
* @param head
*/
public static void posOrder2(TreeNode head){
if (head != null){
Stack<TreeNode> stack = new Stack<>();
stack.push(head);
while (!stack.isEmpty()){
TreeNode peek = stack.peek();
if (peek.left != null && peek.left != head && peek.right != head){
// 有左树先去处理左树
// peek.left != head 说明左树还没有被处理过
stack.push(peek.left);
} else if (peek.right != null && peek.right != head) {
// 左树处理完再处理右树
// peek.right != head 说明右树还没有被处理过
stack.push(peek.right);
}else {
//左右子树都处理完了,处理当前节点
System.out.print(peek.val + " ");
head = stack.pop();
}
}
}
}
public static void main(String[] args) {
/**
* 1
* / \
* 2 3
* / \ / \
* 4 5 6 7
*/
TreeNode root = new TreeNode(1);
root.left = new TreeNode(2);
root.right = new TreeNode(3);
root.left.left = new TreeNode(4);
root.left.right = new TreeNode(5);
root.right.left = new TreeNode(6);
root.right.right = new TreeNode(7);
posOrder2(root);
}
}