二叉树遍历方式
创建二叉树实体类:
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}
1.前序遍历
前序遍历打印结点顺序:中 左 右
递归实现:
/**
* 递归前序遍历
* @param root
*/
public void preOrder(TreeNode root) {
if (root != null) {
System.out.print(root.val + "\t");
preOrder(root.left);
preOrder(root.right);
}
}
非递归实现:
/**
* 非递归前序遍历,虽然是采用栈的方式,但是并未完全应用栈的思想,采取循环过程中输出的方式来遍历
* @param root
*/
public void preOrder(TreeNode root) {
Stack<TreeNode> stack = new Stack<>();
while (root != null || !stack.isEmpty()) {
while (root != null) {
System.out.print(root.val + "\t");//在while循环中打印结点
stack.push(root);
root = root.left;
}
if (!stack.isEmpty()) {
root = stack.pop();
root = root.right;
}
}
}
2.中序遍历
中序遍历打印节点顺序:左 中 右
递归实现:
/**
* 递归实现中序遍历
* @param root
*/
public void inOrder(TreeNode root) {
if (root != null) {
inOrder(root.left);
System.out.print(root.val + "\t");
inOrder(root.right);
}
}
非递归实现:
/**
* 非递归实现中序遍历
* @param root
*/
public void inOrder(TreeNode root) {
Stack<TreeNode> stack = new Stack<>();
while (root!=null || !stack.isEmpty()) {
while (root != null) {
stack.push(root);
root = root.left;
}
if (!stack.isEmpty()) {
root = stack.pop();
System.out.print(root.val + "\t");//在循环外打印,左中右
root = root.right;
}
}
}
3.后序遍历
后序遍历打印结点顺序:左 右 中
递归实现:
/**
* 递归实现后序遍历
* @param root
*/
public void postOrder(TreeNode root) {
if (root != null) {
postOrder(root.left);
postOrder(root.right);
System.out.print(root.val+ "\t");
}
}
非递归实现:
方法一:用一个栈实现
1)用一个指针cur标记当前退出的节点是什么。
2)后序遍历的过程中在遍历完左子树跟右子树cur都会回到根结点。所以当前不管是从左子树还是右子树回到根结点都不应该再操作了,应该退回上层。
3)如果是从右边再返回根结点,应该回到上层。
public static void postOrder(TreeNode head) {
if (head == null) {
return;
}
TreeNode cur = head;
Stack<TreeNode> stack = new Stack<>();
stack.push(head);
while (!stack.isEmpty()) {
TreeNode peek = stack.peek();
if (peek.left != null && peek.left != cur && peek.right != cur) {
stack.push(peek.left);
} else if (peek.right != null && peek.right != cur) {
stack.push(peek.right);
} else {
System.out.print(stack.pop().val + " ");
cur = peek;
}
}
}
方法二:用两个栈实现
- 参考前序遍历的过程是 中左右。
- 将其转化成 中右左。也就是压栈的过程中优先压入左子树,在压入右子树。
- 然后将这个结果返回来,这里是利用栈的先进后出倒序打印。
public static void postOrderIteration(TreeNode head) {
if (head == null) {
return;
}
Stack<TreeNode> stack1 = new Stack<>();
Stack<TreeNode> stack2 = new Stack<>();
stack1.push(head);
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().value + " ");
}
}
4.层序遍历
1)每一层都按从左到右的顺序打印结点。
返回一个二维数组:给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。
示例:
二叉树:[3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回其层次遍历结果:
[
[3],
[9,20],
[15,7]
]
代码实现:
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
if(root==null){
return new ArrayList<List<Integer>>();
}
List<List<Integer>> list = new ArrayList<List<Integer>>();
LinkedList<TreeNode> queue = new LinkedList<TreeNode>();//先进先出队列
queue.add(root);
while(queue.size()!=0){
ArrayList<Integer> temp = new ArrayList<Integer>();
int size = queue.size();
for(int i=0;i<size;i++){
TreeNode node = queue.remove();
temp.add(node.val);
if(node.left!=null){
queue.add(node.left);
}
if(node.right!=null){
queue.add(node.right);
}
}
list.add(temp);
}
return list;
}
}
返回一个一维数组:从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。
例如:
给定二叉树: [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回:
[3,9,20,15,7]
class Solution {
public int[] levelOrder(TreeNode root) {
if(root == null) return new int[0];
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
ArrayList<Integer> ans = new ArrayList<>();
while(!queue.isEmpty()) {
TreeNode node = queue.poll();
ans.add(node.val);
if(node.left != null) queue.add(node.left);
if(node.right != null) queue.add(node.right);
}
int[] res = new int[ans.size()];
for(int i = 0; i < ans.size(); i++)
res[i] = ans.get(i);
return res;
}
}
2)之字形从上到下顺序打印二叉树结点
请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。
例如:
给定二叉树: [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回其层次遍历结果:
[
[3],
[20,9],
[15,7]
]
代码实现:(也可以在层序遍历的基础上外加一个栈和奇偶层的判断进行结点顺序的反转)
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
List<List<Integer>> res = new ArrayList<>();
if(root != null) queue.add(root);
while(!queue.isEmpty()) {
LinkedList<Integer> tmp = new LinkedList<>();//定义一个双向队列实现
for(int i = queue.size(); i > 0; i--) {
TreeNode node = queue.poll();
if(res.size() % 2 == 0) tmp.addLast(node.val); // 偶数层 -> 队列头部
else tmp.addFirst(node.val); // 奇数层 -> 队列尾部
if(node.left != null) queue.add(node.left);
if(node.right != null) queue.add(node.right);
}
res.add(tmp);
}
return res;
}
}
5.进阶:Morris解法
Morris遍历使用二叉树节点中大量指向null的指针,由Joseph Morris 于1979年发明。
时间复杂度:O(n)
额外空间复杂度:O(1)
在你阅读以下代码之前,在这边先讲解一下Morris的通用解法过程。
Morris的整体思路就是将 <u>以某个根结点开始,找到它左子树的最右侧节点之后与这个根结点进行连接</u>
我们可以从 图2 看到,如果这么连接之后,cur 这个指针是可以完整的从一个节点顺着下一个节点遍历,将整棵树遍历完毕,直到 7 这个节点右侧没有指向。
模板代码:
public static void preOrderMorris(TreeNode head) {
if (head == null) {
return;
}
TreeNode cur1 = head;//当前开始遍历的节点
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;
}
}
cur1 = cur1.right;//一直往右边走,参考图
}
}
前序遍历
在某个根结点创建连线的时候打印。因为我们是顺着左边的根节点来创建连线,且创建的过程只有一次。
打印某些自身无法创建连线的节点,也就是叶子节点。
代码实现:
public static void preOrderMorris(TreeNode head) {
if (head == null) {
return;
}
TreeNode cur1 = head;
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.value + " ");
cur1 = cur1.left;
continue;
} else {
cur2.right = null;
}
} else {
System.out.print(cur1.value + " ");
}
cur1 = cur1.right;
}
}
中序遍历
从最左侧开始顺着右节点打印。也就是在将cu1切换到上层节点的时候。
代码实现:
public static void inOrderMorris(TreeNode head) {
if (head == null) {
return;
}
TreeNode cur1 = head;
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.value + " ");
cur1 = cur1.right;
}
}
后序遍历
后序遍历就比较复杂了,先看一下图
当我们到达最左侧,也就是左边连线已经创建完毕了。
打印 4
打印 5 2
打印 6
打印 7 3 1
我们将一个节点的连续右节点当成一个单链表来看待。
当我们返回上层之后,也就是将连线断开的时候,打印下层的单链表。
比如返回到 2,此时打印 4
比如返回到 1,此时打印 5 2
比如返回到 3,此时打印 6
那么我们只需要将这个单链表逆序打印就行了,下文也给出了 单链表逆序代码
这里不应该打印当前层,而是下一层,否则根结点会先与右边打印。
代码实现:
//后序Morris
public static void postOrderMorris(TreeNode head) {
if (head == null) {
return;
}
TreeNode cur1 = head;//遍历树的指针变量
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;
postMorrisPrint(cur1.left);
}
}
cur1 = cur1.right;
}
postMorrisPrint(head);
}
//打印函数
public static void postMorrisPrint(TreeNode head) {
TreeNode reverseList = postMorrisReverseList(head);
TreeNode cur = reverseList;
while (cur != null) {
System.out.print(cur.value + " ");
cur = cur.right;
}
postMorrisReverseList(reverseList);
}
//翻转单链表
public static TreeNode postMorrisReverseList(TreeNode head) {
TreeNode cur = head;
TreeNode pre = null;
while (cur != null) {
TreeNode next = cur.right;
cur.right = pre;
pre = cur;
cur = next;
}
return pre;
}