第六章_二叉樹_2019-03-24
介紹
- 二叉樹的結構
-
二叉樹常考的原因有如下幾點
1、它可以結合連結串列、棧、佇列和字串等資料結構出題
2、需要熟練掌握圖的BFS,DFS遍歷
3、需掌握遞迴函式的使用,並根據題義自己設定遞迴過程
4、工程上二叉樹比較常用 - 面試中,預設二叉樹只有資料項、左孩子指標、右孩子指標。
- 工程上常常會用到節點包含指向其父節點的二叉樹。
相關概念
- 二叉樹的子樹:以二叉樹中任意節點為根的子樹
- 平衡二叉樹(AVL):二叉樹中任意一顆子樹的左右子樹的深度差小於等於1(平衡二叉樹是在構造二叉排序樹的過程中,每當插入一個新結點時,首先檢查是否因插入新結點而破壞了二叉排序樹的平衡性,若是,則找出其中的最小不平衡子樹,在保持二叉排序樹特性的前提下,調整最小不平衡子樹中各結點之間的連結關係,進行相應的旋轉,使之成為新的平衡子樹,空樹是平衡二叉樹,平衡二叉樹首先是二叉排序樹)
- 搜尋二叉樹:二叉樹中,每顆子樹的左子樹的所有節點值都小於根節點,右子樹的所有節點值都大於根節點
- 紅黑樹、平衡搜尋二叉樹:這兩種樹都是搜尋二叉樹的不同實現,它們都提高了搜尋二叉樹的搜尋效率,同時減少了調整為搜尋二叉樹的代價
- 滿二叉樹:除了葉結點外每一個結點都有左右子葉且葉結點都處在最底層的二叉樹,設二叉樹的高度為L,節點樹為N,則滿足等式N = 2L - 1的二叉樹為滿二叉樹,滿二叉樹的高度可以根據L = log(N + 1)計算
- 完全二叉樹(堆結構就是一種完全二叉樹):非滿二叉樹的完全二叉樹的最後一層都集中在樹的左邊
- 後繼節點:二叉樹中序遍歷中,該節點的下一個節點
- 前驅節點:二叉樹中序遍歷中,該節點的上一個節點
- 度:指的是一個節點擁有子節點的個數。如二叉樹的節點的最大度為2。
- 深度:數的層數,根節點為第一層,依次類推。
- 葉子節點:度為0的節點,即沒有子節點的節點。
- 哈夫曼樹:帶權路徑長度達到最小的二叉樹,也叫做最優二叉樹。不關心樹的結構,只要求帶權值的路徑達到最小值,哈夫曼樹可能是完全二叉樹也可能是滿二叉樹。
二叉樹的性質
- 在二叉樹中,第i層的結點總數不超過2^(i-1);
- 深度為h的二叉樹最多有2^h-1個結點(h>=1),最少有h個結點;
- 對於任意一棵二叉樹,如果其葉結點數為N0,而度數為2的結點總數為N2,則N0=N2+1;
- 具有n個結點的完全二叉樹的深度為int(log2n)+1
- 有N個結點的完全二叉樹各結點如果用順序方式儲存,則結點之間有如下關係:若I為結點編號則 如果I<>1,則其父結點的編號為I/2;如果2I <= N,則其左兒子(即左子樹的根結點)的編號為2 I;若2I>N,則無左兒子;如果2 I+1<=N,則其右兒子的結點編號為2I+1;若2 I+1>N,則無右兒子(根節點的編號為1)。
- (6)給定N個節點,能構成h(N)種不同的二叉樹。
- h(N)為卡特蘭數的第N項。h(n)=C(n,2*n)/(n+1)。
經典題
-
用遞迴和非遞迴的方式實現二叉樹的先序、中序、後序遍歷
-
①非遞迴方式實現先序遍歷
具體過程:
1、首先申請一個新的棧,記為stack。
2、然後將頭節點head壓入stack中。
3、每次從stack中彈出棧頂節點,記為cur,然後列印cur節點的值。如果cur右孩子不為空的話,將cur的右孩子先壓入stack中。最後如果cur的左孩子不為空的話,將cur的左孩子壓入stack中。
4、不斷重複步驟3,直到stack為空,全部過程結束。 -
②非遞迴方法實現中序遍歷
具體過程:
1、申請一個新的棧,記為stack,申請一個變數cur,初始時令cur等於頭節點。
2、先把cur節點壓入棧中,對以cur節點為頭的整棵子樹來說,依次把整棵樹的左邊界壓入棧中,即不斷令cur=cur.left,然後重複步驟2。
3、不斷重複步驟2,直到發現cur為空,此時從stack中彈出一個節點,記為node。列印node的值,並讓cur=node.right,然後繼續重複步驟2。
4、當stack為空並且cur為空時,整個過程結束。 -
③非遞迴方法實現後序遍歷方法一:使用兩個棧實現
具體過程如下:
1、申請一個棧,記為s1,然後將頭節點壓入s1中。
2、從s1中彈出的節點記為cur,然後先把cur的左孩子壓入s1中,然後把cur1的右孩子壓入s1中。
3、在整個過程中,每一個從s1中彈出的節點都放進第二個棧s2中。
4、不斷重複步驟2和步驟3,直到51為空,過程停止。
5、從s2中依次彈出節點並列印,列印的順序就是後序遍歷的順序了。 -
④非遞迴方法實現後序遍歷方法二:使用一個棧實現
具體過程如下:
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為空,過程停止。 - 先序,中序和後序的遞迴演算法如下:
-
①非遞迴方式實現先序遍歷
# -*- coding:utf-8 -*- # class TreeNode: #def __init__(self, x): #self.val = x #self.left = None #self.right = None class TreeToSequence: def fore_seq(self, root, result): if root != None: result[0].extend([root.val]) self.fore_seq(root.left, result) self.fore_seq(root.right, result) def inter_seq(self, root, result): if root != None: self.inter_seq(root.left, result) result[1].extend([root.val]) self.inter_seq(root.right, result) def back_seq(self, root, result): if root != None: self.back_seq(root.left, result) self.back_seq(root.right, result) result[2].extend([root.val]) def convert(self, root): # write code here result = [[], [], []] self.fore_seq(root, result) self.inter_seq(root, result) self.back_seq(root, result) #以二維陣列的形式返回先序、中序和後序遍歷序列 return result
不管是遞迴方法還是非遞迴方法,遍歷整棵樹的時間複雜度都是O(N),N為二叉樹的節點數,額外空間複雜度為O(L),L為二叉樹的層數。
-
判斷以head為根的樹是否為平衡二叉樹
採用後序遍歷的思想,每次遍歷某節點的左(右)子樹時記錄其是否為平衡二叉樹,如果不是平衡二叉樹則返回False,如果是平衡二叉樹,則返回其樹高,之後比較左右子樹樹高差距是否小於等於1,如果是,則以該節點為根的子樹為平衡二叉樹,否則不是平衡二叉樹。
-
判斷以head為根的子樹書否為搜尋二叉樹
中序遍歷二叉樹的同時判斷當前節點是否小於中序後繼節點,為了方便,採用非遞迴方法實現中序遍歷
-
判斷以head為根的二叉樹是否為完全二叉樹
從左到右按層次遍歷二叉樹的同時用排除法判斷,具體如下:
1、如果當前節點只有右孩子,返回False
2、如果當前節點只有左孩子,則如果後序節點有非葉子節點,則返回False
3、如果遍歷完整,沒有返回False,則返回True
-
給定存在指向父節點的指標parent的二叉樹,請返回給定節點node的後繼節點。
1、一般解法為先根據parent找到根節點,再中序遍歷,這種解法時間和空間複雜度都是O(N)
2、最優解可以做到時間複雜度為O(L),L為node到其後繼節點的實際距離,時間複雜度為O(1):
- 如果node有右子樹,則其後繼為其右子樹的最左節點
- 如果node無右子樹,但它是其父節點的左子樹,則其父節點為其後繼
- 如果node無右子樹,且是其父節點的右孩子,則需要向上查詢,目標為其父節點(包括隔代父節點)為其父節點的左子樹,則其父節點的父節點就是node的後繼(這時,node實際上是其後繼節點左子樹的最右節點)
- 一直向上查詢,直到空,如果沒找到,則無後繼
-
從下往上對摺紙條,會在紙條上留下摺痕,對摺一次留下向下摺痕,對摺兩次從上往下,依次為向下、向下、向上,給定對摺次數N,請列印紙條展開後從上到下的摺痕方向的序列
第一次對摺會留下一條向下的摺痕,後序每對摺一次,會在原來對摺留下摺痕的上下分別留下向下和向上的摺痕,句子可知,摺痕情況可以形式化為滿二叉樹的節點,節點值為摺痕的方向,按右-中-左順序遍歷即可得到紙條從上到下的摺痕序列,N為樹高
-
給定搜尋二叉樹,頭節點為head,其中有兩個節點位置發生了調換,請列印被調換位置的節點
中序遍歷二叉樹,則被調換節點出現在序列中出現降序的位置,且為第一次降序位置的較大節點和最後一次(可能只有一次降序)降序位置的較小節點。
-
給定二叉樹,頭節點為head,請返回所有節點間的最大距離
某子樹頭節點為A,出現距離最大的三種情況:
- 記左子樹中所有節點和節點A的距離最大值為Lmax,記右子樹中所有節點和節點A的距離最大值為Rmax,max1 = Lmax + 1 + Rmax。
- 左子樹中所有非頭結點之間的最大距離:max2
- 右子樹中所有非頭結點之間的最大距離:max3
子樹A的最大節點間距離為max1,max2,max3中的最大值
根據以上分析,採用後序遍歷的方式,具體如下:
- 後序遍歷二叉樹,對當前節點進行2中的資訊收集
-
對左子樹產生Lmax1(非根節點間的最大距離)和Lmax2(節點和左子樹根節點間的最大距離)兩個資訊
對右子樹產生Rmax1(非根節點間的最大距離)和Rmax2(節點和右子樹根節點間的最大距離)兩個資訊 - 以當前節點為根的子樹節點間的最大距離為max(Lmax1, Rmax1, Lmax2 + 1 + Rmax2)
- Lmax2 + 1和Rmax2 + 1分別為當前節點左子樹中節點到當前節點的最大值和當前節點右子樹中節點到當前節點的最大值,需要返回
程式設計中返回兩個資訊可以通過返回長度為2的陣列實現
在遞迴中維護當前層的處理結果並返回給上一層可以通過更新全域性變數實現
-
已知二叉樹頭結點為head,其中所有節點都不一樣,請返回含有節點數最多的搜尋二叉樹的頭節點
某子樹頭節點為A,出現搜尋二叉樹的兩種情況:
- 來自左子樹中的搜尋二叉樹的頭結點為左孩子且左子樹上所有節點值小於A的節點值,來自右子樹中的搜尋二叉樹的頭結點為右孩子且右子樹上所有節點值大於A的節點值,則以A為根的子樹為搜尋二叉樹
- 不滿足以上情況則返回左、右子樹中節點多的搜尋二叉樹
根據以上分析,採用後序遍歷的方式,具體如下:
- 後序遍歷二叉樹,對每個節點進行如下資訊收集
- 對每個節點的左子樹收集如下4個資訊:左子樹上最大搜索子樹的頭結點、節點個數、樹上的最小值、樹上的最大值。
- 對每個節點的右子樹收集如下4個資訊:右子樹上最大搜索子樹的頭結點、節點個數、樹上的最小值、樹上的最大值。
- 根據前兩步的資訊判斷是否滿足出現搜尋二叉樹的兩種情況
程式設計中返回四個資訊可以通過返回長度為4的陣列實現
在遞迴中維護當前層的處理結果並返回給上一層可以通過更新全域性變數實現