what's the 二叉樹


what's the 樹

  在了解二叉樹之前,首先我們得有樹的概念。

  樹是一種數據結構又可稱為樹狀圖,如文檔的目錄、HTML的文檔樹都是樹結構,它是由n(n>=1)個有限節點組成一個具有層次關系的集合。把它叫做“樹”是因為它看起來像一棵倒掛的樹,也就是說它是根朝上,而葉朝下的。它具有以下的特點:

    • 每個節點有零個或多個子節點;
    • 沒有父節點的節點稱為根節點;
    • 每一個非根節點有且只有一個父節點;
    • 除了根節點外,每個子節點可以分為多個不相交的子樹; 

有關樹的一些相關術語:

    •    節點的度:一個節點含有的子樹的個數稱為該節點的度;
    •  葉節點或終端節點:度為0的節點稱為葉節點;
    •  非終端節點或分支節點:度不為0的節點;
    •  雙親節點或父節點:若一個節點含有子節點,則這個節點稱為其子節點的父節點;
    •  孩子節點或子節點:一個節點含有的子樹的根節點稱為該節點的子節點;
    •  兄弟節點:具有相同父節點的節點互稱為兄弟節點;
    •  樹的度:一棵樹中,最大的節點的度稱為樹的度;
    •  節點的層次:從根開始定義起,根為第1層,根的子節點為第2層,以此類推;
    •  樹的高度或深度:樹中節點的最大層次;
    •  堂兄弟節點:雙親在同一層的節點互為堂兄弟;
    •  節點的祖先:從根到該節點所經分支上的所有節點;
    •  森林:由m(m>=0)棵互不相交的樹的集合稱為森林;

 樹的種類有:無序樹、有序樹、二叉樹、霍夫曼樹。其中最重要應用最多的就是二叉樹,下面我們來學習有關二叉樹的知識。

 

 二叉樹

  二叉樹的定義為度不超過2的樹,即每個節點最多有兩個叉(兩個分支)。上面那個例圖其實就是一顆二叉樹。

  二叉樹是每個節點最多有兩個子樹的樹結構。通常子樹被稱作 “左子樹”(left subtree)“右子樹”(right subtree)。二叉樹常被用於實現二叉查找樹和二叉堆。
  二叉樹的每個結點至多只有二棵子樹(不存在度大於2的結點),二叉樹的子樹有左右之分,次序不能顛倒。二叉樹的第i層至多有2^{i-1}個結點;深度為k的二叉樹至多有2^k-1個結點;對任何一棵二叉樹T,如果其終端結點數為n_0,度為2的結點數為n_2,則n_0=n_2+1。
  一棵深度為k,且有2^k-1個節點的二叉樹,稱為滿二叉樹。這種樹的特點是每一層上的節點數都是最大節點數。而在一棵二叉樹中,除最后一層外,若其余層都是滿的,並且最后一層或者是滿的,或者是在右邊缺少連續若干節點,則此二叉樹為完全二叉樹。具有n個節點的完全二叉樹的深度為log2n+1。深度為k的完全二叉樹,至少有2^(k-1)個節點,至多有2^k-1個節點。
  二叉樹的存儲方式分為鏈式存儲和順序存儲(類似列表)兩種
  二叉樹父節點下標i和左孩子節點的編號下標的關系為2i+1,和右孩子節點的編號下標的關系為2i+2

 

二叉樹有兩個特殊的形態:滿二叉樹完全二叉樹

滿二叉樹

  一個二叉樹,如果除了葉子節點外每一個層的結點數都達到最大值,則這個二叉樹就是滿二叉樹。

完全二叉樹

  葉節點只能出現在最下層和次下層,並且最下面一層的結點都集中在該層最左邊的若干位置的二叉樹為完全二叉樹。即右邊的最下層和次下層可以適當缺一個右子數

  完全二叉樹是效率很高的數據結構

 

二叉樹的遍歷

  二叉樹的鏈式存儲:將二叉樹的節點定義為一個對象,節點之間通過類似鏈表的鏈接方式來連接。

二叉樹結點的定義

#二叉樹結點的定義
class BiTreeNode:
    def __init__(self, data):
        self.data = data
        self.lchild = None
        self.rchild = None

 

  二叉樹的遍歷分為四種——前序遍歷、中序遍歷、后序遍歷和層級遍歷

設樹結構為:

        

  • 前序遍歷:先打印根,再遞歸其左子樹,后遞歸其右子數    E ACBD GF
  • 中序遍歷:以根為中心,左邊打印左子樹,右邊打印右子樹(注意,每個子樹也有相應的根和子樹)   A BCD E GF
  • 后序遍歷:先遞歸左子樹,再遞歸右子樹,后打印根(注意,每個子樹也有相應的根和子樹BDC A FG E
  • 層次遍歷:從根開始一層一層來,同一層的從左到右輸出E AG CF BD

四種遍歷方法的代碼實現:

from collections import deque
#結點的定義
class BiTreeNode:
    def __init__(self, data):
        self.data = data
        self.lchild = None
        self.rchild = None
#二叉樹結點
a = BiTreeNode('A')
b = BiTreeNode('B')
c = BiTreeNode('C')
d = BiTreeNode('D')
e = BiTreeNode('E')
f = BiTreeNode('F')
g = BiTreeNode('G')
#結點之間的關系
e.lchild = a
e.rchild = g
a.rchild = c
c.lchild = b
c.rchild = d
g.rchild = f

root = e

#前序遍歷:先打印根,再遞歸左孩子,后遞歸右孩子
def pre_order(root):
    if root:
        print(root.data, end='')
        pre_order(root.lchild)
        pre_order(root.rchild)
#中序遍歷:以根為中心,左邊打印左子樹,右邊打印右子樹(注意,每個子樹也有相應的根和子樹)
#(ACBD) E (GF)-->(A(CBD)) E (GF)-->(A (B C D)) E (G F)
def in_order(root):
    if root:
        in_order(root.lchild)
        print(root.data, end='')
        in_order(root.rchild)

#后序遍歷:先遞歸左子樹,再遞歸右子數,后打印根(注意,每個子樹也有相應的根和子樹)
# (ABCD)(GF)E-->((BCD)A)(GF)E-->(BDCA)(FG)E
def post_order(root):
    if root:
        post_order(root.lchild)
        post_order(root.rchild)
        print(root.data, end='')

#層次遍歷:一層一層來,同一層的從左到右輸出
def level_order(root):
    queue = deque()
    queue.append(root)
    while len(queue) > 0:
        node = queue.popleft()
        print(node.data,end='')
        if node.lchild:
            queue.append(node.lchild)
        if node.rchild:
            queue.append(node.rchild)

pre_order(root)#EACBDGF
print("")
in_order(root)#ABCDEGF
print("")
post_order(root)#BDCAFGE
print("")
level_order(root)#EAGCFBD
前序遍歷、中序遍歷、后序遍歷、層級遍歷代碼實現

 

二叉搜索樹

  二叉搜索樹(Binary Search Tree),它或者是一棵空樹,或者是具有下列性質的二叉樹: 若它的左子樹不空,則左子樹上所有結點的值均小於它的根結點的值; 若它的右子樹不空,則右子樹上所有結點的值均大於它的根結點的值; 它的左、右子樹也分別為二叉搜索樹。

二叉搜索樹一個很好玩的網址,集成了增刪改的功能:https://visualgo.net/en/bst

二叉搜索樹的中序遍歷得到的是原來列表按升序排序的列表

由列表生成二叉搜索樹、通過二叉搜索樹查詢值和刪除值的示例代碼:

#結點定義
class BiTreeNode:
    def __init__(self, data):
        self.data = data
        self.lchild = None
        self.rchild = None
#建立二叉搜索樹(循環列表,插入值)
class BST:
    def __init__(self, li=None):
        self.root = None
        if li:
            self.root = self.insert(self.root, li[0])#列表的第一個元素是根
            for val in li[1:]:
                self.insert(self.root, val)
    #生成二叉搜索樹遞歸版本
    def insert(self, root, val):
        if root is None:
            root = BiTreeNode(val)
        elif val < root.data:#插入的值小於root,要放到左子樹中(遞歸查詢插入的位置)
            root.lchild = self.insert(root.lchild, val)
        else:#插入的值大於root,要放到右子樹中(遞歸查詢插入的位置)
            root.rchild = self.insert(root.rchild, val)
        return root
    #生成二叉搜索樹不遞歸的版本
    def insert_no_rec(self, val):
        p = self.root
        if not p:
            self.root = BiTreeNode(val)
            return
        while True:
            if val < p.data:
                if p.lchild:
                    p = p.lchild
                else:
                    p.lchild = BiTreeNode(val)
                    break
            else:
                if p.rchild:
                    p = p.rchild
                else:
                    p.rchild = BiTreeNode(val)
                    break
    #查詢遞歸版本
    def query(self, root, val):
        if not root:
            return False
        if root.data == val:
            return True
        elif root.data > val:
            return self.query(root.lchild, val)
        else:
            return self.query(root.rchild, val)
    #查詢非遞歸版本
    def query_no_rec(self, val):
        p = self.root
        while p:
            if p.data == val:
                return True
            elif p.data > val:
                p = p.lchild
            else:
                p = p.rchild
        return False

    #中序遍歷,得到的是升序的列表
    def in_order(self, root):
        if root:
            self.in_order(root.lchild)
            print(root.data, end=',')
            self.in_order(root.rchild)


tree = BST()
for i in [1,5,9,8,7,6,4,3,2]:
    tree.insert_no_rec(i)
tree.in_order(tree.root)
#print(tree.query_no_rec(12))
列表生成二叉搜索樹、二叉搜索樹查詢值和刪除值的方法

 

二叉搜索樹的應用——AVL樹、B樹、B+樹

AVL樹

  AVL樹:AVL樹是一棵自平衡的二叉搜索樹。

  AVL樹具有以下性質: 根的左右子樹的高度之差的絕對值不能超過1 根的左右子樹都是平衡二叉樹

  AVL的實現方式:旋轉 

B樹

  B樹是一棵自平衡的多路搜索樹。常用於數據庫的索引。

  一棵m階B樹(balanced tree of order m)是一棵平衡的m路搜索樹。它或者是空樹,或者是滿足下列性質的樹:
    1、根結點至少有兩個子女;
    2、每個非根節點所包含的關鍵字個數 j 滿足:┌m/2┐ - 1 <= j <= m - 1;
    3、除根結點以外的所有結點(不包括葉子結點)的度數正好是關鍵字總數加1,故內部子樹個數 k 滿足:┌m/2┐ <= k <= m ;
    4、所有的葉子結點都位於同一層。
  在B-樹中,每個結點中關鍵字從小到大排列,並且當該結點的孩子是非葉子結點時,該k-1個關鍵字正好是k個孩子包含的關鍵字的值域的分划。
  因為葉子結點不包含關鍵字,所以可以把葉子結點看成在樹里實際上並不存在外部結點,指向這些外部結點的指針為空,葉子結點的數目正好等於樹中所包含的關鍵字總個數加1。
  B-樹中的一個包含n個關鍵字,n+1個指針的結點的一般形式為: (n,P0,K1,P1,K2,P2,…,Kn,Pn)其中,Ki為關鍵字,K1<K2<…<Kn, Pi 是指向包括Ki到Ki+1之間的關鍵字的子樹的指針。

  在B-樹中查找給定關鍵字的方法是,首先把根結點取來,在根結點所包含的關鍵字K1,…,Kn查找給定的關鍵字(可用順序查找或二分查找法),若找到等於給定值的關鍵字,則查找成功;否則,一定可以確定要查找的關鍵字在Ki與Ki+1之間,Pi為指向子樹根節點的指針,此時取指針Pi所指的結點繼續查找,直至找到,或指針Pi為空時查找失敗。

B+ 樹

  B+ 樹是一種樹數據結構,是一個n叉排序樹,每個節點通常有多個孩子,一棵B+樹包含根節點、內部節點和葉子節點。根節點可能是一個葉子節點,也可能是一個 包含兩個或兩個以上孩子節點的節點。
  B+ 樹通常用於數據庫和操作系統的文件系統中。NTFS, ReiserFS, NSS, XFS, JFS, ReFS 和BFS等文件系統都在使用B+樹作為元數據索引。B+ 樹的特點是能夠保持數據穩定有序,其插入與修改擁有較穩定的對數時間復雜度。B+ 樹元素自底向上插入。
  B+樹是應文件系統所需而出的一種B樹的變型樹。一棵m階的B+樹和m階的B-樹的差異在於:
    1.有n棵子樹的結點中含有n個關鍵字,每個關鍵字不保存數據,只用來索引,所有數據都保存在葉子節點。
    2.所有的葉子結點中包含了全部關鍵字的信息,及指向含這些關鍵字記錄的指針,且葉子結點本身依關鍵字的大小自小而大順序鏈接。
    3.所有的非終端結點可以看成是索引部分,結點中僅含其子樹(根結點)中的最大(或最小)關鍵字。
  通常在B+樹上有兩個頭指針,一個指向根結點,一個指向關鍵字最小的葉子結點。

 

 

B+樹的查找

  對B+樹可以進行兩種查找運算:
  1.從最小關鍵字起順序查找;
  2.從根結點開始,進行隨機查找。
  在查找時,若非終端結點上的關鍵值等於給定值,並不終止,而是繼續向下直到葉子結點。因此,在B+樹中,不管查找成功與否,每次查找都是走了一條從根到葉子結點的路徑。其余同B-樹的查找類似。
  以下是從根節點查找葉子節點k的偽代碼[1]   :
1
2
3
4
5
6
7
8
9
10
Function: search (k)  
     return  tree_search (k, root); Function: tree_search (k, node)  
     if  node is a leaf then         return  node;  
     switch  do     case  k < k_0    
         return  tree_search(k, p_0);  
     case  k_i ≤ k < k_{i+ 1 }    
         return  tree_search(k, p_{i+ 1 });  
     case  k_d ≤ k    
         return  tree_search(k, p_{d+ 1 });
//偽代碼假設沒有重復值
 

B+樹的插入

  m階B樹的插入操作在葉子結點上進行,假設要插入關鍵值a,找到葉子結點后插入a,做如下算法判別:
    ①如果當前結點是根結點並且插入后結點關鍵字數目小於等於m,則算法結束;
    ②如果當前結點是非根結點並且插入后結點關鍵字數目小於等於m,則判斷若a是新索引值時轉步驟④后結束,若a不是新索引值則直接結束;
    ③如果插入后關鍵字數目大於m(階數),則結點先分裂成兩個結點X和Y,並且他們各自所含的關鍵字個數分別為:u=大於(m+1)/2的最小整數,v=小於(m+1)/2的最大整數;
      由於索引值位於結點的最左端或者最右端,不妨假設索引值位於結點最右端,有如下操作:
      如果當前分裂成的X和Y結點原來所屬的結點是根結點,則從X和Y中取出索引的關鍵字,將這兩個關鍵字組成新的根結點,並且這個根結點指向X和Y,算法結束;
      如果當前分裂成的X和Y結點原來所屬的結點是非根結點,依據假設條件判斷,如果a成為Y的新索引值,則轉步驟④得到Y的雙親結點P,如果a不是Y結點的新索引值,則求出X和Y結點的雙親結點P;然后提取X結點中的新索引值a’,在P中插入關鍵字a’,從P開始,繼續進行插入算法;
    ④提取結點原來的索引值b,自頂向下,先判斷根是否含有b,是則需要先將b替換為a,然后從根結點開始,記錄結點地址P,判斷P的孩子是否含有索引值b而不含有索引值a,是則先將孩子結點中的b替換為a,然后將P的孩子的地址賦值給P,繼續搜索,直到發現P的孩子中已經含有a值時,停止搜索,返回地址P。

 

B+樹的刪除

  B+樹的刪除也僅在葉子結點進行,當葉子結點中的最大關鍵字被刪除時,其在非終端結點中的值可以作為一個“分界關鍵字”存在。若因刪除而使結點中關鍵字的個數少於m/2 (m/2結果取上界,如5/2結果為3)時,其和兄弟結點的合並過程亦和B-樹類似。

 

 

 

 

                                                     

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM