二叉樹周遊也是面試中經常碰到的題目,這篇文章主要總結一下有關二叉樹周遊的相關問題。
二叉樹結構
public static class Node{
//左子節點
public Node left;
//右子節點
public Node right;
public int value;
public Node(int v){
value = v;
}
}
二叉樹的周遊
- 層次周遊 :主要借助隊列先進先出來實作
-
深度周遊 :三種順序
先序(頭,左,右),
中序(左、頭、右),
後序(左、右、頭)
三種深度周遊皆可以通過遞歸,非遞歸(借助棧來實作),morris方式來實作。其中三種方式的時間複雜度均為O(N), 遞歸和非遞歸的空間複雜度為O(H),而morris方法的空間複雜度為O(1)
層次周遊
主要借助隊列先進先出來實作
public void level(Node head){
if(head == null){
return;
}
Queue<Node> queue = new LinkedList<>();
//将頭節點放入隊列中
queue.add(head);
//當隊列不為空時,每次彈出一個節點,彈出就列印
while (!queue.isEmpty()){
Node cur = queue.poll();
System.out.print(cur.value);
//依次判斷彈出的做孩子和右孩子是否為空,
//不為空加入隊列中
if(cur.left != null){
queue.add(cur.left);
}
if(cur.right != null){
queue.add(cur.right);
}
}
}
深度周遊
遞歸方式
遞歸序:
二叉樹遞歸周遊存在遞歸序,二叉樹中的每一個節點在遞歸序中
都會通過該節點三次,第一次通過就列印該節點稱為先序周遊
第二次經過該節點就列印稱為中序周遊,第三次經過該節點就
列印稱為後序周遊
//遞歸序
public void f(Node head){
if(head == null){
return;
}
// 先序 列印head.value
f(head.left);
//中序 列印head.value
f(head.right);
//後序 列印head.value
}
非遞歸方式
非遞歸方式的深度周遊都是借助棧來實作的,都遵循一個原則:
彈出就列印 (後序利用兩個棧的方式是彈出就入棧)
先序
主要借助棧來實作頭,左,右的順序列印
public void pre(Node head){
if(head == null){
return;
}
Stack<Node> stack = new Stack<>();
//将頭節點壓入棧中
stack.push(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);
}
}
}
中序
借助棧來實作 左,頭,右的列印順序。
本質是依次将二叉樹的左子樹加入棧中。
public void in(Node head){
if(head == null){
return;
}
Stack<Node> stack = new Stack<>();
//初始條件,head不為null或者站不為空,則繼續
while (head != null || !stack.isEmpty()){
//如果head不為空,則加入棧中,并來到左子樹
if(head != null){
stack.push(head);
head = head.left;
}else{
//直到head為null,就一直彈出就列印,
//之後來到右子樹
head = stack.pop();
System.out.print(head.value);
head = head.right;
}
}
}
後序
方式一: 借助兩個棧來實作,基于先序排序,将先序排序稍作修改在入棧時,先左孩子,再右孩子,同時彈出節點時,不列印,而是直接放入另一個棧中,直到第一個棧為空時,依次列印第二個棧的值即可。
public void pos1(Node head){
if(head == null){
return;
}
Stack<Node> s1 = new Stack<>();
Stack<Node> s2 = new Stack<>();
s1.push(head);
while (!s1.isEmpty()){
head = s1.pop();
s2.push(head);
System.out.print(head.value);
if(head.left != null){
s1.push(head.left);
}
if(head.right != null){
s1.push(head.right);
}
}
while (!s2.isEmpty()){
System.out.print(s2.pop() + " ");
}
}
方式二:
借助棧和兩個變量來實作左,右,頭的順序列印
//整個流程都是先處理左孩子,再處理右孩子,再處理頭節點
//那麼在棧中為什麼順序沒有反過來,因為這裡用的是peek,并沒有poll
public void pos2(Node h){
if( h == null){
return;
}
Stack<Node> stack = new Stack<>();
stack.push(h);
//c為目前周遊的節點
Node c = null;
//這裡的h代表上次列印的節點,
//開始預設為head(其實随便指定一個不相幹的值即可)
while (!stack.isEmpty()){
//取頭頂的節點作為目前節點,這裡并不彈出
c = stack.peek();
//如果c有左孩子,
//并且c節點的左右孩子都沒列印,則直接壓入左孩子
if(c.left != null
&& h != c.left
&& h != c.right){
stack.push(c.left);
//如果c有右孩子,且右孩子沒有列印,則直接壓入右孩子
}else if( c.right != null
&& h != c.right){
stack.push(c.right);
}else {
//兩個孩子都處理完了,該處理頭節點了,彈出并列印
System.out.print(stack.pop()+" ");
h = c;
}
}
}
morris方式
利用有限幾個變量實作二叉樹的深度周遊。
在morris周遊中,如果一個節點有左孩子,那麼morris周遊就會經過該節點兩次(一次,是左孩子的最右節點指向null,第二次是左孩子的最右節點指向cur)。其餘節點隻會經過一次。
morris周遊的流程:
1、cur節點來到head節點
2、如果cur節點無左樹,則cur = cur.left
3、如果cur節點有左樹:
a、找到cur左樹上的最右節點mostRight,如果
mostRight節點為null,那麼 讓
mostRight.right = cur, cur = cur.left;
b、如果mostRight.right == cur,那麼讓
mostRight.right = null, cur = cur.right;
public static void morris(Node head){
if(head == null){
return;
}
Node cur = head;
Node mostRight = null;
while (cur != null){
mostRight = cur.left;
//左子樹不為空
if(mostRight != null){
while (mostRight.right != null && mostRight.right != cur){
mostRight = mostRight.right;
}
if(mostRight.right == null){
mostRight.right = cur;
cur = cur.left;
continue; //直接跳過下一個節點去判斷
}else{
//來到這裡說明節點 mostRight.right == cur,是第二次來到目前節點了
mostRight.right = null;
}
}
cur = cur.right;
}
}