05:樹結構

算法其餘篇

目錄:

1.1 樹的概念     返回頂部

  一、樹的特性html

      1)一棵樹中的任意兩個結點有且僅有惟一的一條路徑連通; node

      2)一棵樹若是有n個結點,則它必定有n−1條邊; python

      3)在一棵樹中加一條邊將會構成一個迴路。mysql

  二、二叉樹算法

      1)二叉樹是一種特殊的樹,二叉樹的特色是每一個結點最多有兩個兒子。sql

      2)二叉樹使用範圍最廣,一顆多叉樹也能夠轉化爲二叉樹。數據庫

  三、滿二叉樹數據結構

      1)二叉樹中每一個內部節點都有兩個兒子,滿二叉樹全部的葉節點都有相同的深度。app

      2)滿二叉樹是一棵深度爲h且有2h−1個結點的二叉樹。ide

  四、徹底二叉樹

      1)若設二叉樹的高度爲h,除了第h層外,其餘層的結點數都達到最大個數,第h層從右向左連續 缺若干個結點,則爲徹底二叉樹。

      

  五、樹的特色

      1. 若是一棵徹底二叉樹的父節點編號爲K,則其左兒子的編號是2K,右兒子的結點編號爲2K+1

      2. 已知徹底二叉樹的總節點數爲n求葉子節點個數:
        當n爲奇數時:(n+1)/2
        當n爲偶數時 : (n)/2

      3. 已知徹底二叉樹的總節點數爲n求父節點個數:爲:n/2

      4. 已知徹底二叉樹的總節點數爲n求葉子節點爲2的父節點個數:
        當n爲奇數時:n/2
        當n爲偶數時 : n/2-1

      五、若是一棵徹底二叉樹有N個結點,那麼這棵二叉樹的深度爲【log2(N+1)log2(N+1)】(向上取整)

1.2 二叉樹基本操做     返回頂部

    參考博客: https://www.cnblogs.com/freeman818/p/7252041.html

  一、生成樹結構

      1. 前序遍歷:  DBACEGF(根節點排最早,而後同級先左後右)
      2. 中序遍歷:  ABCDEFG  (先左後根最後右)
      3. 後序遍歷:  ACBFGED   (先左後右最後根)

#! /usr/bin/env python
# -*- coding: utf-8 -*-
class Node:
    def __init__(self,value=None,left=None,right=None):
        self.value=value
        self.left=left    #左子樹
        self.right=right  #右子樹

if __name__=='__main__':
    root=Node('D',Node('B',Node('A'),Node('C')),Node('E',right=Node('G',Node('F'))))
生成樹形結構
#! /usr/bin/env python
# -*- coding: utf-8 -*-
class Node:
    def __init__(self,value=None,left=None,right=None):
        self.value=value
        self.left=left    #左子樹
        self.right=right  #右子樹

def preTraverse(root):
     '''
     前序遍歷
     '''
     if root==None:
         return
     print(root.value)
     preTraverse(root.left)
     preTraverse(root.right)

if __name__=='__main__':
    root=Node('D',Node('B',Node('A'),Node('C')),Node('E',right=Node('G',Node('F'))))
    print('前序遍歷:')
    preTraverse(root)   #  DBACEGF
前序遍歷
#! /usr/bin/env python
# -*- coding: utf-8 -*-
class Node:
    def __init__(self,value=None,left=None,right=None):
        self.value=value
        self.left=left    #左子樹
        self.right=right  #右子樹

def midTraverse(root):
    '''
    中序遍歷
    '''
    if root == None:
        return
    midTraverse(root.left)
    print(root.value)
    midTraverse(root.right)

if __name__=='__main__':
    root=Node('D',Node('B',Node('A'),Node('C')),Node('E',right=Node('G',Node('F'))))
    print('中序遍歷:')
    midTraverse(root)   #  ACBFGED
中序遍歷
#! /usr/bin/env python
# -*- coding: utf-8 -*-
class Node:
    def __init__(self,value=None,left=None,right=None):
        self.value=value
        self.left=left    #左子樹
        self.right=right  #右子樹

def afterTraverse(root):
    '''
    後序遍歷
    '''
    if root == None:
        return
    afterTraverse(root.left)
    afterTraverse(root.right)
    print(root.value)

if __name__=='__main__':
    root=Node('D',Node('B',Node('A'),Node('C')),Node('E',right=Node('G',Node('F'))))
    print('後序遍歷:')
    afterTraverse(root)   #  ACBFGED
後序遍歷

   

前序排列原理:
#####此時執行preTraverse(root.left) 函數
'''
一、第一步 root=Node(D) print D,D入棧[D]
二、第二步 root=Node(D).left=Node(B) print B, B入棧[D,B]
三、第三步 root=Node(B).left=Node(A) print A, A入棧[D,B,A]
四、第四步 root=Node(A).left=None,沒有進入遞歸,順序執行preTraverse(root.right)
五、第五步 Node(A).right==None,也沒有進入遞歸,此時preTraverse(A) 函數纔會正真返回,A出棧[D,B]
六、第六步 A的上級調用函數爲:preTraverse(B.left),因此接着會順序執行preTraverse(B.right),B的左右節點訪問後B出棧[D]
七、第七步 Node(B).right==Node(C) print C,C入棧[D,C]
八、第八步 Node(C).left==None, Node(C).right==None,訪問完C的左右節點後函數返回C出棧,返回上級調用[D]
九、第九步 此時返回上級調用執行preTraverse(D.right)=Node(E) print E,D出棧,E入棧[E] 
'''

'''此時輸出結果:DBACE'''
前序遍歷步驟推演
#! /usr/bin/env python
# -*- coding: utf-8 -*-
class Node:
    def __init__(self,value=None,left=None,right=None):
        self.value=value
        self.left=left    #左子樹
        self.right=right  #右子樹

def layered_print( root):
    if not root:
        return []
    curLayer = [root]                           # 當前層的全部節點
    while curLayer:
        layerValue = []                         # 當前層的值
        nextLayer = []                          # 下一層的全部節點
        for node in curLayer:                   # 循環當前層全部節點並並獲取全部value值
            layerValue.append(node.value)
            if node.left:
                nextLayer.append(node.left)        # 將當前層的左節點加入列表
            if node.right:
                nextLayer.append(node.right)        # 將當前層的右節點加入列表
                
        print layerValue                           # 打印當前層的值
        curLayer = nextLayer                      # 將循環下移一層


'''
['D']
['B', 'E']
['A', 'C', 'G']
['F']
'''

if __name__=='__main__':
    root=Node('D',Node('B',Node('A'),Node('C')),Node('E',right=Node('G',Node('F'))))
    layered_print(root)
分層打印二叉樹

1.3 hash樹     返回頂部

  一、hash樹描述(就是散列樹)

      1. 散列樹選擇從2開始的連續質數來創建一個十層的哈希樹。

      2. 第一層結點爲根結點,根結點下有2個結點;

      3. 第二層的每一個結點下有3個結點;

      4. 依此類推,即每層結點的子節點數目爲連續的質數。

  二、hash樹特色

    注:關係型數據庫中,索引大多采用B/B+樹來做爲存儲結構,而全文搜索引擎的索引則主要採用hash的存儲結構,這兩種數據結構有什麼區別?

      1. 若是是等值查詢,那麼哈希索引明顯有絕對優點,由於只須要通過一次算法便可找到相應的鍵值;

      2. 固然了,這個前提是,鍵值都是惟一的,若是鍵值不是惟一的,就須要先找到該鍵所在位置,而後再根據鏈表日後掃描,直到找到相應的數據;

      3. 若是是範圍查詢檢索,這時候哈希索引就毫無用武之地了,由於原先是有序的鍵值,通過哈希算法後,
          有可能變成不連續的了,就沒辦法再利用索引完成範圍查詢檢索;

      4. 同理,哈希索引也沒辦法利用索引完成排序,以及like ‘xxx%’ 這樣的部分模糊查詢(這種部分模糊查詢,其實本質上也是範圍查詢);

  三、創建hash樹 

      1. 選擇從2開始的連續質數來創建一個十層的哈希樹。
      2. 第一層結點爲根結點,根結點下有2個結點;第二層的每一個結點下有3個結點;

      3. 依此類推,即每層結點的子節點數目爲連續的質數。到第十層,每一個結點下有29個結點。

      4. 同一結點中的子結點,從左到右表明不一樣的餘數結果。
        例如:第二層結點下有三個子節點。那麼從左到右分別表明:除3餘0,除3餘1,除3餘2.對質數進行取餘操做獲得的餘數決定了處理的路徑。
      5. 以隨機的10個數的插入爲例,來圖解HashTree的插入過程。

        

 

      6. 其實也能夠把全部的鍵-值節點放在哈希樹的第10層葉節點處,這第10層的滿節點數就包含了全部的整數個數,
          可是若是這樣處理的話,全部的非葉子節點做爲鍵-值節點的索引,這樣使樹結構龐大,浪費空間。

  四、查找編輯

      1. 哈希樹的節點查找過程和節點插入過程相似,就是對關鍵字用質數序列取餘,根據餘數肯定下一節點的分叉路徑,直到找到目標節點。

      2. 如上圖,最小」哈希樹(HashTree)在從4G個對象中找出所匹配的對象,比較次數不超過10次,也就是說:最多屬於O(10)。

      3. 在實際應用中,調整了質數的範圍,使得比較次數通常不超過5次。

      4. 也就是說:最多屬於O(5),所以能夠根據自身須要在時間和空間上尋求一個平衡點。

  五、刪除編輯

      1. 哈希樹的節點刪除過程也很簡單,哈希樹在刪除的時候,並不作任何結構調整。

      2. 只是先查到到要刪除的節點,而後把此節點的「佔位標記」置爲false便可(即表示此節點爲空節點,但並不進行物理刪除)。

  六、hash樹優勢

    1)結構簡單

        1. 從哈希樹的結構來講,很是的簡單,每層節點的子節點個數爲連續的質數。
        2. 子節點能夠隨時建立,所以哈希樹的結構是動態的,也不像某些哈希算法那樣須要長時間的初始化過程。
        3. 哈希樹也沒有必要爲不存在的關鍵字提早分配空間。

    2)查找迅速

        1. 從算法過程咱們能夠看出,對於整數,哈希樹層級最多能增長到10。
        2. 所以最多隻須要十次取餘和比較操做,就能夠知道這個對象是否存在,這個在算法邏輯上決定了哈希樹的優越性。

    3)結構不變

        1. 從刪除算法中能夠看出,哈希樹在刪除的時候,並不作任何結構調整。

        2. 常規樹結構在增長元素和刪除元素的時候都要作必定的結構調整,不然他們將可能退化爲鏈表結構,而致使查找效率的下降。

        3. 哈希樹採起的是一種「見縫插針」的算法,歷來不用擔憂退化的問題,也沒必要爲優化結構而採起額外的操做,所以大大節約了操做時間。

  七、缺點編輯

      1. 哈希樹不支持排序,沒有順序特性。

      2. 若是在此基礎上不作任何改進的話並試圖經過遍從來實現排序,那麼操做效率將遠遠低於其餘類型的數據結構。

  八、hash索引使用範圍

      總結:哈希適用在小範圍的精確查找,在列數據很大,又不須要排序,不須要模糊查詢,範圍查詢時有用

      一、hash索引僅知足「=」、「IN」和「<=>」查詢,不能使用範圍查詢

        由於hash索引比較的是常常hash運算以後的hash值,所以只能進行等值的過濾,不能基於範圍的查找,
        由於通過hash算法處理後的hash值的大小關係,並不能保證與處理前的hash大小關係對應。

      二、hash索引沒法被用來進行數據的排序操做

        因爲hash索引中存放的都是通過hash計算以後的值,而hash值的大小關係不必定與hash計算以前的值同樣,
        因此數據庫沒法利用hash索引中的值進行排序操做。

      三、對於組合索引,Hash 索引在計算 Hash 值的時候是組合索引鍵合併後再一塊兒計算 Hash 值,

        而不是單獨計算 Hash 值,因此經過組合索引的前面一個或幾個索引鍵進行查詢的時候,Hash 索引也沒法被利用。

      四、Hash 索引遇到大量Hash值相等的狀況後性能並不必定就會比B-Tree索引高。

        對於選擇性比較低的索引鍵,若是建立 Hash 索引,那麼將會存在大量記錄指針信息存於同一個 Hash 值相關聯。
        這樣要定位某一條記錄時就會很是麻煩,會浪費屢次表數據的訪問,而形成總體性能低下。

1.4 B-tree 和 B+tree     返回頂部

    參考博客: https://blog.csdn.net/chuixue24/article/details/80027689

  一、一棵m階的B-Tree有以下特性

      1. 每一個節點最多有m個孩子。
      2. 除了根節點和葉子節點外,其它每一個節點至少有Ceil(m/2)個孩子(Ceil返回大於或者等於指定表達式的最小整數)。
      3. 若根節點不是葉子節點,則至少有2個孩子
      4. 全部葉子節點都在同一層,且不包含其它關鍵字信息
      5. 每一個非終端節點包含n個關鍵字信息(P0,P1,…Pn, k1,…kn)
      6. 關鍵字的個數n知足:ceil(m/2)-1 <= n <= m-1
      7. ki(i=1,…n)爲關鍵字,且關鍵字升序排序。
      8. Pi(i=1,…n)爲指向子樹根節點的指針。P(i-1)指向的子樹的全部節點關鍵字均小於ki,但都大於k(i-1)

  二、以一個3階的B-Tree舉例

      1. 每一個節點佔用一個盤塊的磁盤空間,一個節點上有兩個升序排序的關鍵字和三個指向子樹根節點的指針,指針存儲的是子節點所在磁盤塊的地址。

      2. 兩個關鍵詞劃分紅的三個範圍域對應三個指針指向的子樹的數據的範圍域。

      3. 以根節點爲例,關鍵字爲17和35,P1指針指向的子樹的數據範圍爲小於17,P2指針指向的子樹的數據範圍爲17~35,P3指針指向的子樹的數據範圍爲大於35。

      

'''模擬查找關鍵字29的過程:'''
# 根據根節點找到磁盤塊1,讀入內存。【磁盤I/O操做第1次】
# 比較關鍵字29在區間(17,35),找到磁盤塊1的指針P2。
# 根據P2指針找到磁盤塊3,讀入內存。【磁盤I/O操做第2次】
# 比較關鍵字29在區間(26,30),找到磁盤塊3的指針P2。
# 根據P2指針找到磁盤塊8,讀入內存。【磁盤I/O操做第3次】
# 在磁盤塊8中的關鍵字列表中找到關鍵字29。
模擬查找關鍵字29的過程

  三、B+tree特色 

      1. B+Tree是在B-Tree基礎上的一種優化,使其更適合實現外存儲索引結構,InnoDB存儲引擎就是用B+Tree實現其索引結構。

      2. 從上一節中的B-Tree結構圖中能夠看到每一個節點中不只包含數據的key值,還有data值。

      3. 而每個頁的存儲空間是有限的,若是data數據較大時將會致使每一個節點(即一個頁)能存儲的key的數量很小

      4. 當存儲的數據量很大時一樣會致使B-Tree的深度較大,增大查詢時的磁盤I/O次數,進而影響查詢效率。

      5. 在B+Tree中,全部數據記錄節點都是按照鍵值大小順序存放在同一層的葉子節點上而非葉子節點上只存儲key值信息,
          這樣能夠大大加大每一個節點存儲的key值數量,下降B+Tree的高度。

      6. B+Tree相對於B-Tree有幾點不一樣:

          1)非葉子節點只存儲鍵值信息。
          2)全部葉子節點之間都有一個鏈指針。
          3)數據記錄都存放在葉子節點中。

  四、B+tree(以每一個節點可存4個建值及指針信息爲例)

      1. B+Tree的非葉子節點只存儲鍵值信息,假設每一個磁盤塊能存儲4個鍵值及指針信息

      2. 在B+Tree上有兩個頭指針,一個指向根節點,另外一個指向關鍵字最小的葉子節點,並且全部葉子節點(即數據節點)之間是一種鏈式環結構。

      3. 所以能夠對B+Tree進行兩種查找運算:一種是對於主鍵的範圍查找和分頁查找,另外一種是從根節點開始,進行隨機查找。

      

  五、B+Tree優勢

      1. InnoDB存儲引擎中頁的大小爲16KB,通常表的主鍵類型爲INT(佔用4個字節)或BIGINT(佔用8個字節),指針類型也通常爲4或8個字節

      2. 也就是說一個頁(B+Tree中的一個節點)中大概存儲16KB/(8B+8B)=1K個鍵值(這裏的K取值爲〖10〗^3)。

      3. 也就是說一個深度爲3的B+Tree索引能夠維護10^3 * 10^3 * 10^3 = 10億 條記錄。

      說明:

        實際狀況中每一個節點可能不能填充滿,所以在數據庫中,B+Tree的高度通常都在2~4層。

        mysql的InnoDB存儲引擎在設計時是將根節點常駐內存的,也就是說查找某一鍵值的行記錄時最多隻須要1~3次磁盤I/O操做。

  六、B-tree與哈希索引的區別

    1)B-tree的索引:

        是按照順序存儲的,因此,若是按照B-tree索引,能夠直接返回,帶順序的數據,但這個數據只是該索引列含有的信息。所以是順序I/O

        適用於: 精確匹配 、範圍匹配 、最左匹配

    2)Hash索引:

        索引列值的哈希值+數據行指針:所以找到後還須要根據指針去找數據,形成隨機I/O 

        適合: 精確匹配 

        不適合: 模糊匹配 、範圍匹配 、不能排序

相關文章
相關標籤/搜索