what's the 二叉樹

what's the 樹

  在瞭解二叉樹以前,首先咱們得有樹的概念。java

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

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

有關樹的一些相關術語:算法

    •    節點的度:一個節點含有的子樹的個數稱爲該節點的度;
    •  葉節點或終端節點:度爲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

 

二叉樹有兩個特殊的形態:滿二叉樹徹底二叉樹app

滿二叉樹ide

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

徹底二叉樹學習

  葉節點只能出如今最下層和次下層,而且最下面一層的結點都集中在該層最左邊的若干位置的二叉樹爲徹底二叉樹。即右邊的最下層和次下層能夠適當缺一個右子數spa

  徹底二叉樹是效率很高的數據結構

 

二叉樹的遍歷

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

二叉樹結點的定義

#二叉樹結點的定義
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路搜索樹。它或者是空樹,或者是知足下列性質的樹:
    一、根結點至少有兩個子女;
    二、每一個非根節點所包含的關鍵字個數 j 知足:┌m/2┐ - 1 <= j <= m - 1;
    三、除根結點之外的全部結點(不包括葉子結點)的度數正好是關鍵字總數加1,故內部子樹個數 k 知足:┌m/2┐ <= k <= m ;
    四、全部的葉子結點都位於同一層。
  在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-樹相似。
相關文章
相關標籤/搜索