二叉樹

本頁使用了標題或全文手工轉換
維基百科,自由的百科全書
一棵有9個節點且深度為3的二叉樹,其根節點的值為2,它既不平衡亦未經過排序
一棵簡單的滿二叉樹

電腦科學中,二叉樹(英語:Binary tree)是每個節點最多只有兩個分支(即不存在分支度大於2的節點)的樹結構[1]。通常分支被稱作「左子樹」或「右子樹」。二叉樹的分支具有左右次序,不能隨意顛倒。

二叉樹的第層至多擁有個節點;深度為的二叉樹至多總共有個節點(定義根節點所在深度 ),而總計擁有節點數符合的,稱為「滿二叉樹」;深度為個節點的二叉樹,當且僅當其中的每一節點,都可以和深度的滿二叉樹,序號1到的節點一對一對應時,稱為完全二叉樹。對任何一棵非空的二叉樹,如果其葉片(終端節點)數為,分支度為2的節點數為,則

與普通樹不同,普通樹的節點個數至少為1,而二叉樹的節點個數可以為0;普通樹節點的最大分支度沒有限制,而二叉樹節點的最大分支度為2;普通樹的節點無左、右次序之分,而二叉樹的節點有左、右次序之分。

二叉樹通常作為資料結構應用,典型用法是對節點定義一個標記函數,將一些值與每個節點相關聯。這樣標記的二叉樹就可以實現二叉搜尋樹二叉堆積,並應用於高效率的搜尋和排序。

圖論中的定義[編輯]

二叉樹是一個連通的無環圖,並且每一個頂點的度不大於3。有根二叉樹還要滿足根節點的度不大於2。有了根節點之後,每個頂點定義了唯一的父節點,和最多2個子節點。然而,沒有足夠的資訊來區分左節點和右節點。如果不考慮連通性,允許圖中有多個連通分量,這樣的結構叫做森林

性質[編輯]

二叉樹是一個有根,並且每個節點最多有2個子節點。非空的二叉樹,若樹葉總數為 n0,分支度為2的總數為 n2,則 n0 = n2 + 1。

特殊類型[編輯]

完全二叉樹 完美二叉樹
總節點k <= k <= k =
樹高h h = h =

完美二叉樹[編輯]

一棵深度為k,且有個節點的二叉樹,稱為完美二叉樹(Perfect Binary Tree)。這種樹的特點是每一層上的節點數都是最大節點數。

性質[編輯]

對於一棵深度為 的完美二叉樹:

  • 共有 個結點
  • 結點個數一定為奇數
  • 層有 個結點
  • 個葉子

完全二叉樹[編輯]

在一顆二叉樹中,若除最後一層外的其餘層都是滿的,並且最後一層要麼是滿的,要麼在右邊缺少連續若干節點,則此二叉樹完全二叉樹(Complete Binary Tree)。具有n個節點的完全二叉樹的深度為。深度為k的完全二叉樹,至少有個節點,至多有個節點。

儲存[編輯]

程式設計語言中能用多種方法來構造二叉樹。

順序儲存表示[編輯]

一個儲存在陣列中的完全二叉樹

二叉樹可以用陣列鏈結串列來儲存,若是滿二叉樹就能緊湊排列而不浪費空間。如果某個節點的索引為i,(假設根節點的索引為0)則在它左子節點的索引會是,以及右子節點會是;而它的父節點(如果有)索引則為。這種方法更有利於緊湊儲存和更好的訪問的局部性,特別是在前序遍歷中。然而,它需要連續的儲存空間,這樣在儲存高度為hn個節點所組成的一般樹時,將浪費很多空間。在最糟糕的情況下,如果深度為h的二叉樹其每個節點都只有右孩子,則該儲存結構需要佔用的空間,實際上卻有h個節點,浪費了不少空間,是順序儲存結構的一大缺點。

儲存結構[編輯]

 /* 二叉树的顺序存储表示 */
 #define MAX_TREE_SIZE 100 /* 二叉树的最大节点数 */
 typedef TElemType SqBiTree[MAX_TREE_SIZE]; /* 0号单元存储根节点 */

 typedef struct
 {
   int level,order; /* 即节点的层(按[满二叉树]计算) */
 }position;

基本操作[編輯]

二元連結串列儲存表示[編輯]

基於連結串列的二叉樹邏輯結構示意

在使用記錄記憶體地址指標的程式設計語言中,二叉樹通常用樹結點結構來儲存。有時也包含指向唯一的父節點的指標。如果一個結點的子結點個數小於2,一些子結點指針可能為空值,或者為特殊的哨兵結點。 使用連結串列能避免順序儲存浪費空間的問題,演算法和結構相對簡單,但使用二元連結串列,由於缺乏父鏈的指引,在找回父節點時需要重新掃描樹得知父節點的節點地址。

儲存結構[編輯]

/* 二叉樹的二叉鏈表存儲表示 */
 typedef struct BiTNode
 {
   TElemType data;
   struct BiTNode *lchild,*rchild; /* 左右孩子指針 */
 }BiTNode,*BiTree;

基本操作[編輯]

三元連結串列儲存表示[編輯]

基於三元連結串列的二叉樹的邏輯結構

改進於二元連結串列,增加父節點的指引,能更好地實現節點間的訪問,不過演算法相對複雜。 當二叉樹用三元連結串列表示時,有N個結點,就會有N+2個空指針。

儲存結構[編輯]

 /* 二叉樹的三叉鏈表存儲表示 */
 typedef struct BiTPNode
 {
   TElemType data;
   struct BiTPNode *parent,*lchild,*rchild; /* 父、左右孩子指針 */
 }BiTPNode,*BiPTree;

基本操作[編輯]

遍歷[編輯]

我們經常希望訪問樹中的每一個結點並且檢視它的值。有很多常見的順序來訪問所有的結點,而且每一種都有有用的性質。

深度優先遍歷[編輯]

在深度優先順序中,我們希望從根結點訪問最遠的結點。和圖的深度優先搜尋不同的是,不需記住訪問過的每一個結點,因為樹中不會有環。前序,中序和後序遍歷都是深度優先遍歷的特例。

前(先)序、中序、後序遍歷[編輯]

遍歷二叉樹:L、D、R分別表示遍歷左子樹、訪問根結點和遍歷右子樹,則先(根)序遍歷二叉樹的順序是DLR,中(根)序遍歷二叉樹的順序是LDR,後(根)序遍歷二叉樹的順序是LRD。還有按層遍歷二叉樹。這些方法的時間複雜度都是O(n),n為結點個數。

如果T2是由有序樹T轉換而來的二叉樹,那麼T中結點的前序就是T2中結點的前序,T中結點的後序就是T2中結點的中序。任何一棵二叉樹的葉結點在先序、中序和後序遍歷中的相對次序不發改變。設n,m為一棵二叉樹上的兩個結點,在中序遍歷時,n在m前的條件是n在m的左方。前序序列和中序序列相同的二叉樹為空樹或任一結點均無左孩子的非空二叉樹;中序序列和後序序列相同的二叉樹為空樹或任一結點均無右孩子的非空二叉樹;前序序列和後序序列相同的二叉樹為空樹或僅有一個結點的二叉樹。

假設我們有一個包含值的value和指向兩個子結點的leftright的樹結點結構。我們可以寫出這樣的過程:

visit(node)
    print node.value
    if node.left  != null then visit(node.left)
    if node.right != null then visit(node.right)

這樣會用前序列印出樹中的值。在前序,每個結點在訪問它的子結點之前訪問。類似地,如果列印陳述式在最後,每個結點在訪問他的子節點之後訪問,樹中的值會用後序來列印。在這兩種情況中,左子樹中的值比右子樹中得值先列印。

visit(node)
    if node.left  != null then visit(node.left)
    print node.value
    if node.right != null then visit(node.right)

最後,上面的中序遍歷,每個結點在訪問左子樹和右子樹之間訪問。這在遍歷二叉搜尋樹時很常用,因為它能用遞增的順序來遍歷所有的值。

為什麼呢?如果n是二叉搜尋樹的結點,那麼n的左子樹的所有結點的值都比n的值要小,而且n的右子樹的所有節點的值都比n的值要大。因此,如果我們順序遍歷左子樹,然後訪問n,然後順序遍歷右子樹。我們就已經循序存取了整個樹。

後序遍歷偽代碼如下:

visit(node)
    if node.left  != null then visit(node.left)
    if node.right != null then visit(node.right)
    print node.value
BinaryTree
在這個二叉樹中,
  • 前序遍歷的結果:M,G,D,B,A,C,F,E,J,H,I,K,L,S,P,O,N,Q,R,W,U,T,V,X,Z,Y
  • 後序遍歷的結果:A,C,B,E,F,D,I,H,L,K,J,G,N,O,R,Q,P,T,V,U,Y,Z,X,W,S,M
  • 中序遍歷的結果:A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z


以上的遞歸演算法使用與樹的高度成比例的棧空間。如果我們在每個結點中儲存指向父結點的指標,那樣可以使用反覆運算演算法,只使用常數空間實現所有這些遍歷。然而,指向父結點的指標佔用更多的空間。這只在需要指向父節點的指標或棧空間有限時才使用。例如, 這是一個中序遍歷的反覆運算演算法:

visit(root)
    prev    := null
    current := root
    next    := null
    
    while current != null
        if prev == current.parent
            prev := current
            next := current.left
        if next == null or prev == current.left
            print current.value
            prev := current
            next := current.right
        if next == null or prev == current.right
            prev := current
            next := current.parent
        current := next


用二叉樹表示下述運算式:a+b*(c-d)-e/f
  • 先序遍歷的序列是:-+a*b-cd/ef
  • 中序遍歷的序列是:a+b*c-d-e/f
  • 後序遍歷的序列是:abcd-*+ef/-

廣度優先遍歷[編輯]

和深度優先遍歷不同,廣度優先遍歷會先訪問離根節點最近的節點。二叉樹的廣度優先遍歷又稱按層次遍歷。演算法藉助佇列實現。

轉換自多叉樹[編輯]

一般有序樹可對映為二叉樹,但反之未必成立。

n叉樹轉換為二叉樹的方法:二叉樹中結點x的左子結點為n叉樹中結點x的左子結點;二叉樹中結點x的右子結點為n叉樹中結點x的第一個右邊的同級結點y。

二叉樹當且僅當根節點沒有右子結點時可轉換為n叉樹。

例如,在左邊的樹中,A有6個子結點{B,C,D,E,F,G}。它能被轉換成右邊的二叉樹。

將n叉樹轉換為二叉樹的例子


  • 將一棵樹轉換為二叉樹的方法:
  1. 在兄弟之間加一連線;
  2. 對每個結點,除了其左孩子外,去除其與其餘孩子之間的聯繫;
  3. 以樹的根結點為軸心,將整樹順時針轉45度。

樹的二元連結串列標記法(孩子兄弟標記法)[編輯]

樹的二元連結串列標記法(孩子兄弟標記法)是樹和二叉樹轉換的媒介。

儲存結構[編輯]

二叉樹與森林相互轉換的邏輯示意
 /* 樹的二叉鏈表(孩子—兄弟)存儲表示 */
 typedef struct CSNode
 {
   TElemType data;
   struct CSNode *firstchild,*nextsibling;
 }CSNode,*CSTree;

基本操作[編輯]

線索二叉樹[編輯]

線索二叉樹(英語:threaded binary tree,保留遍歷時結點在任一序列的前驅和後繼的資訊):若結點有左子樹,則其lchild域指示其左孩子,否則令lchild域指示其前驅;若結點有右子樹,則其rchild域指示其右孩子,否則令rchild指示其後繼。還需在結點結構中增加兩個標誌域LTag和RTag。LTag=0時,lchild域指示結點的左孩子,LTag=1時,lchild域指示結點的前驅;RTag=0時,rchild域指示結點的右孩子,RTag=1時,rchild域指示結點的後繼。以這種結點結構構成的二元連結串列作為二叉樹的儲存結構,叫做線索連結串列,其中指向結點前驅和後繼的指標叫做線索,加上線索的二叉樹稱為線索二叉樹。對二叉樹以某種次序遍歷使其變為線索二叉樹的過程叫做線索化。若對二叉樹進行中序遍歷,則所得的線索二叉樹稱為中序線索二叉樹,線索連結串列稱為為中序線索連結串列。線索二叉樹是一種物理結構。

在中序線索樹找結點後繼的規律是:若其右標誌為1,則右鏈為線索,指示其後繼,否則遍歷其右子樹時訪問的第一個結點(右子樹最左下的結點)為其後繼;找結點前驅的規律是:若其左標誌為1,則左鏈為線索,指示其前驅,否則遍歷左子樹時最後訪問的一個結點(左子樹中最右下的結點)為其前驅。
在後序線索樹中找到結點的後繼分三種情況:

  1. 若結點是二叉樹的根,則其後繼為空;
  2. 若結點是其雙親的右孩子,或是其雙親的左孩子且其雙親沒有右子樹,則其後繼即為雙親結點;
  3. 若結點是其雙親的左孩子,且其雙親有右子樹,則其後繼為雙親右子樹上按後序遍歷列出的第一個結點。

二元線索儲存表示[編輯]

儲存結構[編輯]

二叉樹的二元線索儲存表示:在線索連結串列上添加一個頭結點,並令其lchild域的指標指向二叉樹的根結點,其rchild域的指標指向中序遍歷時訪問的最後一個結點。令二叉樹中序序列中的第一個結點的lchild域指標和最後一個結點的rchild域的指標均指向頭結點,這樣就建立了一個雙向線索連結串列。二叉樹常採用二元連結串列方式儲存。

 /* 二叉樹的二叉線索存儲表示 */
 typedef enum{Link,Thread}PointerTag; /* Link(0):指針,Thread(1):線索 */
 typedef struct BiThrNode
 {
   TElemType data;
   struct BiThrNode *lchild,*rchild; /* 左右孩子指針 */
   PointerTag LTag,RTag; /* 左右標誌 */
 }BiThrNode,*BiThrTree;

基本操作[編輯]

外部連結[編輯]

  1. ^ Thomas H. Cormen; Charles E. Leiserson; Ronald L. Rivest; Clifford Stein. B.5.3 Binary and positional trees. Introduction to algorithms Third. Cambridge, Mass.: MIT Press. 2009: 1177–1178 [2023-02-06]. ISBN 978-0-262-03384-8.