二叉树

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

一棵有9個節點且深度為3的二元樹,其根節點的值為2,它既不平衡亦未經過排序
一棵簡單的滿二叉樹

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

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

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

圖論中的定義

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

性质

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

特殊类型

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

满二叉树

在一颗二元樹中,如果每个节点都有 0 或 2 个子节点,则二叉树是满二叉树。

完美二叉树

一棵深度為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;

基本操作

線索二叉樹

線索二叉樹(英語:,保留遍歷時結點在任一序列的前驅和後繼的資訊):若結點有左子樹,則其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. . Third. Cambridge, Mass.: MIT Press. 2009: 1177–1178 [2023-02-06]. ISBN 978-0-262-03384-8.
This article is issued from Wikipedia. The text is licensed under Creative Commons - Attribution - Sharealike. Additional terms may apply for the media files.