树的遍历及其应用
二叉树的遍历:
一、递归式遍历:
1.树的递归式遍历的顺序:
分析:从中我们可以看出每个节点都会被遍历到三遍
2.代码实现(包括树的结构):
1 /* 2 * 树的基本结构定义 3 */ 4 class Node { 5 int val; 6 Node left; 7 Node right; 8 } 9 10 //树的递归式遍历: 11 public static void orderTraver(Node t) { 12 if (t == null) { 13 return; 14 } 15 orderTraver(t.left); 16 orderTraver(t.right); 17 }
3.前中后序遍历(深度优先搜索):
前置知识点:前中后序遍历概念
(1).前序遍历:对于任何一个子树,总是先遍历头节点,再遍历左子树,最后遍历右子树
(2).中序遍历:对于任何一个子树,总是先遍历左子树,再遍历头节点,最后遍历右子树
(3).后序遍历:对于任何一个子树,总是先遍历左子树,再遍历右子树,最后遍历头节点
在递归序遍历中实现前中序遍历的操作:
根据上述知识点已知递归序遍历会经历同一个节点3次
(1).前序遍历:在第1次遍历到节点时,执行所要进行的操作
代码:
1 //递归式前序遍历 2 public static void preOrderTraver(Node t) { 3 if (t == null) { 4 return; 5 } 6 System.out.print(t.val + " ");//执行所对应的遍历操作 7 preOrderTraver(t.left); 8 preOrderTraver(t.right); 9 }
(2).中序遍历:在第2次遍历到节点时,执行所要进行的操作
代码:
1 //递归式中序遍历 2 public static void inOrderTraver(Node t) { 3 if (t == null) { 4 return; 5 } 6 inOrderTraver(t.left); 7 System.out.print(t.val + " ");//执行所对应的遍历操作 8 inOrderTraver(t.right); 9 } 10
(3).后序遍历:在第3次遍历到节点时,执行所要进行的操作
代码:
1 //递归式后序遍历 2 public static void posOrderTraver(Node t) { 3 if (t == null) { 4 return; 5 } 6 posOrderTraver(t.left); 7 posOrderTraver(t.right); 8 System.out.print(t.val + " ");//执行所对应的遍历操作 9 }
2.非递归遍历
(1).前序遍历(自己手动实现压栈):
* 预先准备:先把根节点压到栈中
* 1.从栈中弹出一个节点并输出
* 2.将这个节点的右儿子和左儿子先后压入栈中(如果存在的话)
* 重复以上2个步骤即可
1 public static void preOrderTraver1(Node head) { 2 if (head == null) { 3 return; 4 } 5 Stack<Node> sk = new Stack<>(); 6 sk.push(head); 7 while (sk.size() > 0) { 8 System.out.print(sk.peek().val + " "); 9 Node t = sk.pop(); 10 if (t.right != null) { 11 sk.push(t.right); 12 } 13 if (t.left != null) { 14 sk.push(t.left); 15 } 16 } 17 System.out.println(); 18 }
(2).中序遍历:
* 处理操作:
* 1.先把所在节点的左儿子(以及左儿子的左儿子(一直递推,直到找不到左儿子为止))弹入节点
* 2.之后陆续弹出这些节点,并在弹出的过程中寻找这些节点有没有右树,如果有右树,则对这颗右树继续执行1,2操作
* 不断执行以上的操作,直到栈空
1 public static void inOrderTraver1(Node head) { 2 if (head == null) { 3 return; 4 } 5 Stack<Node> sk = new Stack<>(); 6 while (sk.size() > 0 || head != null) { 7 if (head != null) {//弹入该子树的所有左儿子(一定要把弹入左儿子的情况放if条件的前面) 8 //此处的if有点相当于while的作用,在head == null之前会一直往里弹 9 sk.push(head); 10 head = head.left; 11 } 12 else {//弹入右儿子(子树)一定是在这个子树的左儿子都弹完的情况下才进行的 13 head = sk.pop(); 14 System.out.print(head.val + " "); 15 head = head.right;//下一次循环还是先去看它有没有左儿子 16 } 17 } 18 System.out.println(); 19 }
(3).后续遍历:
* 非递归实现后续遍历:
* 预先准备:设好两个栈,原栈和收集栈,并将根节点弹入到原栈中
* 1.将原栈中顶层节点取出,压入到收集栈中
* 2.把该节点的左节点和右节点依次压入到栈中
* 重复以上2个步骤,直至原栈为空后,再依次把收集栈中的元素全部取出,得到的节点顺序即为后续遍历结果
*
* 分析:(以下简称:左儿子->左,右儿子->右,头节点->头)
* 因为后续遍历是左右头的顺序,但是在非递归遍历时,不好跨过头节点去直接遍历左右儿子
* 所以我们可以将左右头看成头右左的翻转(可以用栈实现翻转,只要进栈顺序是头右左,则出栈顺序就是所要的左右头)
* 所以此时收集栈的进栈顺序是头右左,所以原栈的出栈顺序就是头右左
* 此时只要调整好原栈的进出栈操作即可(与先序遍历思想类似)
*
1 public static void posOrderTrever1(Node head) { 2 Stack<Node> sk1 = new Stack<>();//原栈 3 Stack<Node> sk2 = new Stack<>();//收集栈 4 sk1.push(head); 5 while (sk1.size() > 0) { 6 Node t = sk1.pop(); 7 sk2.push(t); 8 if (t.left != null) { 9 sk1.push(t.left); 10 } 11 if (t.right != null) { 12 sk1.push(t.right); 13 } 14 } 15 while (sk2.size() > 0) { 16 System.out.print(sk2.pop().val + " "); 17 } 18 System.out.println(); 19 }
将遍历的结果存储到线性表中:
1 //将某种顺序的遍历序列内容存到一个线性表中(使用辅助结构List) 2 //以中序遍历为例: 3 public static void inOrderResult(Node root,List<Node> list) { 4 if (root == null) { 5 return; 6 } 7 inOrderResult(root.left,list); 8 list.add(root); 9 inOrderResult(root.right,list); 10 }
深度优先搜索的应用:
剑指 Offer 55 - I. 二叉树的深度 - 力扣(LeetCode) (leetcode-cn.com)
代码:
1 public int maxDepth(TreeNode root) { 2 if (root == null) { 3 return 0; 4 } 5 return Math.max(maxDepth(root.left),maxDepth(root.right)) + 1; 6 }
3.广度(宽度)优先遍历:
* 二叉树的广度(宽度)优先遍历:
* 即按层级一层层往下遍历的方式
* 思路:用队列(先进先出的功能)进行维护,先进根节点,然后每次从队列中弹出一个节点
* 并分布弹入这个弹出节点的左节点和右节点即可.
1 public static void wideOrderTraver(Node head) { 2 if (head == null) { 3 return; 4 } 5 Queue<Node> q = new LinkedList<>();//双向链表即为队列 6 q.add(head); 7 while (q.size() > 0) { 8 Node t = q.poll(); 9 System.out.print(t.val + " "); 10 if (t.left != null) { 11 q.add(t.left); 12 } 13 if (t.right != null) { 14 q.add(t.right); 15 } 16 } 17 }
4.广度(宽度)优先遍历的应用:
* 寻找一颗树的最大宽度(即求出哪一层的节点个数最多,返回那个层数的节点个数)
思路:用广度优先搜索,按层级顺序依次往下遍历,再遍历的过程中,要注意什么时候换层并对每一层做好统计和处理即可,使用哈希表来辅助实现
代码及解析:
1 public static int GetMaxWidth(Node head) { 2 if (head == null) { 3 return 0; 4 } 5 int max = Integer.MIN_VALUE;//用来存储最终的答案 6 int curLevel = 1;//表示当前执行到哪个层了 7 int curNodes = 0;//表示当前层次已有多少个节点 8 Queue<Node> q = new LinkedList<>(); 9 HashMap<Node,Integer> hm = new HashMap<>();//记录节点所在的层数 10 q.add(head); 11 hm.put(head, 1); 12 while (q.size() > 0) { 13 Node t = q.poll(); 14 int NodeLevel = hm.get(t); 15 if (NodeLevel == curLevel) {//还在当前层 16 curNodes++; 17 } 18 else { 19 max = Math.max(max, curNodes);//对上一层的总结点个数进行处理 20 curLevel++; 21 curNodes = 1;//注意此处是1,不要把这个节点给漏了 22 } 23 if (t.left != null) { 24 q.add(t.left); 25 hm.put(t.left, NodeLevel + 1); 26 } 27 if (t.right != null) { 28 q.add(t.right); 29 hm.put(t.right, NodeLevel + 1); 30 } 31 } 32 return max; 33 }