二叉树类型的题目为常考题型
1、能够结合队列、栈、链表、字符串等很多数据结构。
2、需要掌握图的基本遍历方式,比如BFS和DFS。
3、需要掌握递归函数的使用,并自己设计出递归过程。
4、与实际工作结合紧密。
面试中,二叉树节点类型仅包括:数据项、左孩子、右孩子。
工程上的二叉树节点类型,往往多一条指向父节点的指针。
一般默认面试中的二叉树节点结构不包含指向父节点的指针,除非特别说明。
实现二叉树的先序、中序、后序遍历,包括递归方式和非递归方式
要清楚前序、中序、后序传统的递归遍历其实都是一次遍历访问节点三次,当在哪一次访问做动作就出现了三种遍历方式。而morris遍历(进阶)只访问每个节点两次,且利用了每个叶子节点的空指针。
(依次访问节点)
打印时机放在
第一次来到节点,先序
第二次来到节点,中序
第三次来到节点,后序
import java.util.Stack;public class Code_01_PreInPosTraversal { public static class Node { public int value; public Node left; public Node right; public Node(int data) { this.value = data; } } //递归版先中后序遍历 public static void preOrderRecur(Node head) { if (head == null) { return; } System.out.print(head.value + " ");// 头节点 preOrderRecur(head.left); preOrderRecur(head.right); } public static void inOrderRecur(Node head) { if (head == null) { return; } inOrderRecur(head.left); System.out.print(head.value + " "); inOrderRecur(head.right); } public static void posOrderRecur(Node head) { if (head == null) { return; } posOrderRecur(head.left); posOrderRecur(head.right); System.out.print(head.value + " "); }
用非递归的方式实现二叉树的先序遍历,(回到节点两次):
1. 申请一个新的栈, 记为 stack。然后将头节点 head 压入 stack 中。
2. 从 stack 中弹出栈顶节点, 记为 head, 然后打印head 节点的值, 再将节点 head的右孩子节点(不为空的话) 先压入 stack 中, 最后将head 的左孩子节点(不为空的话) 压入 stack 中。
3. 不断重复步骤 2, 直到 stack 为空, 全部过程结束。
举例:
中序遍历具体过程:(自己压栈只能压入node类型,需要精细设计结构,比起递归丢失很多信息)
1、申请一个新的栈,记为stack,申请一个变量cur ,初始时令cur等于头节点。
2、当前结点不为空时,先把head节点压入栈中,对head节点为头的整棵子树来说,只要依次把整棵树的左边界压入栈中,即不断令head=cur.left,然后重复步骤2。当前结点指针向它左结点移动,一压压一条边。
3、不断重复步骤2,当前结点为空、栈不为空时,此时从stack中弹出一个节点,记为node。打印node的值,并让head=node.right当前结点指针向右移动,然后继续重复步骤2。
4、当stack为空并且head为空时,整个过程结束。
后序遍历用两个栈。由前面的先序遍历,中左右(先压右再压左),改为中右左, 不打印,然后放入辅助栈中逆序,得到左右中,即后序遍历
具体体过程如下∶
1、申请一个栈,记为s1,然后将头节点压入s1中。
2、从s1中弹出的节点记为cur,然后先把cur的左孩子压入s1中,然后把cur1的右孩子压入s1中。
3、在整个过程中,每一个从s1中弹出的节点都放进第二个栈s2中。
4、不断重复步骤2和步骤3,直到s1为空,过程停止。
5、从s2中依次弹出节点并打印,打印的顺序就是后序遍历的顺序了。
//非递归版先序遍历 // 往栈中压入根结点 // 弹出栈中一个结点并打印 // 压入刚弹出结点的右结点和左结点 // 弹出栈中一个结点并打印 public static void preOrderUnRecur(Node head) { System.out.print("pre-order: "); if (head != null) { Stack stack = new Stack(); stack.add(head); while (!stack.isEmpty()) { head = stack.pop(); System.out.print(head.value + " "); if (head.right != null) { stack.push(head.right); } if (head.left != null) { stack.push(head.left); } } } System.out.println(); }// 中序遍历具体过程: public static void inOrderUnRecur(Node head) { System.out.print("in-order: "); if (head != null) { Stack stack = new Stack(); while (!stack.isEmpty() || head != null) { if (head != null) { stack.push(head); head = head.left; } else { head = stack.pop(); System.out.print(head.value + " "); head = head.right; } } } System.out.println(); } // 后序遍历用了两个栈 public static void posOrderUnRecur(Node head) { System.out.print("pos-order: "); if (head != null) { Stack s1 = new Stack(); Stack s2 = new Stack(); s1.push(head); while (!s1.isEmpty()) { head = s1.pop(); s2.push(head);// 先序是打印,后序是先把他入辅助栈压 if (head.left != null) { s1.push(head.left); } if (head.right != null) { s1.push(head.right); } } while (!s2.isEmpty()) { System.out.print(s2.pop().value + " "); } } System.out.println(); }
用一个栈进行后序遍历
1、申请一个栈,记为stack,将头节点压入stack ,同时设置两个变量h和c。在整个流程中,h代表最近一次弹出并打印的节点,c代表当前stack的栈顶节点,初始时令h为头节点,c为null.
2、每次令c等于当前stack的栈顶节点,但是不从stack中弹出节点,此时分以下三种情况。
(1)如果c的左孩子不为空,并且h不等于c的左孩子,也不等于c的右孩子,则把c的左孩子压入stack中。等于的时候就说明左孩子或者右孩子打印完毕
(2)如果情况1不成立,并且c的右孩子不为空并且h不等于c的右孩子,则把c的右孩子压入stack中。
(3)如果情况1和情况2都不成立,左右都打印完毕,那么从stack中弹出c并打印,然后令h等于c.
3、一直重复步骤2,直到stack为空,过程停止。
举例说明:
【步骤】节点1压入stack中,准备变量h=头节点1和c=null,命中步骤2情况1,压入节点2,栈顶结点为2,命中步骤2的情况1,压入结点4,此时栈顶结点为4,命中步骤2的情况3,将结点4从栈中弹出并打印,h变为结点4,此时栈顶结点为2,命中情况2,压入结点5,栈顶节点变为5,命中步骤2的情况3,弹出结点5并打印,h变成结点5,栈顶节点变为2,命中步骤2的情况3,弹出结点2打印,h变为结点2,栈顶结点为结点1,命中步骤2的情况2,压入结点3,栈顶结点为结点3,命中步骤2的情况1,压入结点6;栈顶结点为结点6,命中步骤2的情况3,弹出结点6打印;h变成结点6,栈顶节点变为3,命中步骤2的情况2,压入结点7;栈顶结点为结点7,命中步骤2的情况3,弹出结点7打印,栈顶结点变为3,命中步骤2的情况3,弹出结点3打印,h变为3栈顶变为1,命中步骤2的情况3,弹出结点1打印。
public static void posorderUnRecur2(Node h) { System.out.print(pos-order: "); if(h != null) { stack stack = new stack(); stack.push(h); Node c= null; while ( !stack.isEmpty(){ c = stack.peek(); if(c.ieft != null &&_h != c.left && h != c.right){ stack.push(c.left); }else if (c.right != nu11 && h != c.right) { stack.push(c.right); }else { system.out.print(stack.pop().value + " " ); h= c; system.out.println();}
不管是递归方法还是非递归方法,遍历整棵树的时间复杂度都是O(N),N为二叉树的节点数,额外空间复杂度为O(L),L为二叉树的层数。
测试主函数:
public static void main(String[] args) { Node head = new Node(5); head.left = new Node(3); head.right = new Node(8); head.left.left = new Node(2); head.left.right = new Node(4); head.left.left.left = new Node(1); head.right.left = new Node(7); head.right.left.left = new Node(6); head.right.right = new Node(10); head.right.right.left = new Node(9); head.right.right.right = new Node(11); // recursive System.out.println("==============recursive=============="); System.out.print("pre-order: "); preOrderRecur(head); System.out.println(); System.out.print("in-order: "); inOrderRecur(head); System.out.println(); System.out.print("pos-order: "); posOrderRecur(head); System.out.println(); // unrecursive System.out.println("============unrecursive============="); preOrderUnRecur(head); inOrderUnRecur(head); posOrderUnRecur(head); }}