https://mp.weixin.qq.com/s/jXU36r9gV6nGVLPPvfnBVgmysql
在計算機中,樹隨處可在,可說是圖論和計算機科學中的重中之重,理解樹的結構、樹的思想和樹的優異性質對於程序設計大有裨益!算法
咱們都知道,數組的特色是查詢快,直接能夠經過下標獲取元素,時間複雜度爲O(1)
;可是當咱們在指定的位置插入元素或者刪除元素的時候,數組下標和所對應的元素是須要從新排列的,所須要的時間複雜度爲O(n)
!sql
因此對於頻繁的插入、刪除的場景,不建議採用有序數組!數據庫
可能有的朋友會想到,對於須要頻繁的插入、刪除的場景,可使用鏈表結構,由於對於鏈表結構來講,在進行插入或者刪除的時候,只須要改變元素的前驅或者後繼節點的引用就能夠了,所須要的時間複雜度爲O(1)
;可是若是咱們想查詢指定的內容時候,須要遍歷鏈表元素並逐步判斷,直到查找到目標元素爲止,所須要的時間複雜度爲O(n)
!數組
因此對於查找頻繁的數據,不建議使用鏈表!緩存
哪有沒有一種查詢速度快、插入刪除也很快的一種數據結構呢?數據結構
樹
就是其中一個!樹
這種數據結構,在計算機領域中有着不少的實際應用!好比說:ide
說到樹
這種數據結構,相信不少人首先想到的就是二叉樹
!函數
不錯,二叉樹做爲一個很重要的數據結構,在某些狀況下既能夠知足咱們要求查詢快的特色同時也能夠知足插入刪除也快的要求。編碼
固然,在生活中咱們能夠看到樹,實際上是分不少種類的,咱們剛剛也說了在某些狀況下,假如一個樹是任意自由的結構,那麼它可能既達不到查詢快也達不到插入刪除快的要求,所以咱們須要給樹做出一些定義。
在現實中,樹是一個根朝下、葉朝上的結構,而在計算機科學中,樹是由n
個有限節點組成一個具備層次關係的集合,看起來像一顆倒掛的樹,根朝上、葉朝下,特色以下:
計算機科學中,樹的定義:
以下圖,看起來像一顆樹,但不是樹結構:
雖然樹作出了一些基本定義,可是不足以知足咱們的需求,在計算機科學中,樹能夠被分爲如下幾種類型:
對於無序樹,也就是那種自由樹結構,沒有任何規律,因此無從查找,這種結構通常不考慮;而有序樹,由於各個子節點存在一個的順序關係,那麼在查詢的時候,就能夠以此爲基礎進行查找!
固然,有序樹又能夠進行種類細分,內容以下:
上文說到的二叉樹其實就是有序樹的一種,在程序開發中,用的也是最多的一種樹形結構!
而對於B 樹,主要在文件系統和數據庫領域中有所應用,像 Linux 操做系統的文件系統就是使用 B+ 樹進行文件的存儲。
在計算機科學中,二叉樹(英文名:Binary Tree)是每一個結點最多有兩個子樹的樹結構,一般子樹被稱做「左子樹」(left subtree)和「右子樹」(right subtree)。
按照這個定義,在邏輯上二叉樹能夠進行五種基本形態的分類:
對於 1~4 種形態的二叉樹,形狀比較簡單,對於第 5 種既有左子樹又有右子樹的二叉樹,在形態上比較複雜,咱們也能夠進行特殊類型細分,內容以下:
徹底二叉樹是一種特殊的二叉樹,特性以下:
在實際的開發中也有所應用,徹底二叉樹會使用二叉查找樹算法(會在下文介紹),來保證查找的數據是有序的,葉子節點能夠按從上到下、從左到右的順序依次添加到數組中。知道一個節點的位置,就能夠輕鬆地算出它的父節點、孩子節點的位置。
當咱們用數組存儲一個徹底二叉樹時,以上面圖中徹底二叉樹爲例,標號爲 2 的節點,它在數組中的位置也是 2,它的父節點就是 (k/2 = 1)
,它的孩子節點分別是(2k=4)
和(2k+1=5)
,別的節點也是相似。
由於葉子節點的位置比較規律,全部查詢排序效率比較高,好比堆排序就使用了它。
除最後一層結點均無任何子節點外,每一層的全部結點都有兩個子結點的樹,稱爲滿二叉樹!
也就是說,若是一個二叉樹的層數爲K
,且結點總數是(2^k) -1
,滿二叉樹形狀:
滿二叉樹,特性徹底同徹底二叉樹,可是比徹底二叉樹更嚴格,每一個葉節點到達根路徑所需的長度都相同!而徹底二叉樹的k-1
層能夠爲葉節點!
平衡二叉樹的提出就是爲了保證樹不至於太傾斜,儘可能保證兩邊平衡,特性以下:
平衡二叉樹的經常使用實現方法有紅黑樹、AVL、替罪羊樹、Treap、伸展樹等。
像 JDK1.8 中 HashMap、TreeMap 等就使用到了紅黑樹實現。
上面介紹了徹底二叉樹、滿二叉樹、平衡二叉樹都屬於特殊類型的二叉樹,須要咱們從邏輯上去控制才能夠知足要求!
上文中咱們說到,二叉樹的出現就是爲了解決查詢效率問題,按照二分進行查找,每次查詢只須要選擇其中一個子樹就進行查找,從而減小查找次數,提高查詢效率!
那麼咱們如何進行二分查找呢?
這就要求查找的數據必須是有序的,每次查找、插入刪除時都要維護一個有序的數據集,因而就有了二叉查找樹這個概念,英文全稱 Binary Search Tree,簡稱 BST。
二叉查找樹,也被稱爲二叉排序樹,能夠說是從算法層面來定義二叉樹結構,這種算法思路適用於全部的二叉樹結構,特性以下:
二叉查找樹,在最好的狀況下,按照折半查找,查詢效率獲得提高,時間複雜度爲O(logn);可是,若是構成的二叉排序樹蛻變爲單支樹,樹的深度爲 n,其查找時間複雜度與順序查找同樣爲O(n)
。
若是二叉查找樹變成了單支樹,查詢效率就大大折扣了,因而就有平衡二叉查找樹的出現!
平衡二叉查找樹,又稱 AVL 樹,由於算法的發明者爲Adel'son-Vel'skii
和 Landis
,被稱爲 AVL 樹來自於大神的姓名縮寫組合。
它除了具有二叉查找樹的基本特徵以外,還具備一個很是重要的特色:
也就是說 AVL 樹每一個節點的平衡因子只多是-一、0和1(平衡因子算法:左子樹高度減去右子樹高度)。
那麼如何保證二叉查找樹在添加元素的同時保證節點平衡呢?
基本思想就是:當在二叉排序樹中插入一個節點時,首先檢查是否因插入而破壞了平衡,若破壞,則找出其中的最小不平衡二叉樹,在保持二叉查找樹特性的狀況下,調整最小不平衡子樹中節點之間的關係,以達到新的平衡。
所謂最小不平衡子樹是指:離插入節點最近且以平衡因子的絕對值大於1的節點做爲根的子樹。
當新插入的節點致使樹結構發生失衡就會進行調整,主要操做有左旋轉、右旋轉操做!
從圖中能夠看出,在插入數據 100 以前,左圖 BST 樹只有 80 節點的平衡因子是 -1(左子樹高度減去右子樹高度),但整棵樹仍是平衡的。
插入 100 以後,80節點的平衡因子就成爲了-2,此時平衡被破壞,須要進行調整,繞節點 90 進行左旋轉,最終樹型結構變成右圖。
左旋轉場景:當樹中節點 X 的右孩子的右孩子上插入新元素,且平衡因子從 -1 變成 -2 後,就須要繞節點 X 進行左旋轉!
從圖中能夠看出,在插入數據 30 以前,左圖 BST 樹只有 80 節點的平衡因子是 1(左子樹高度減去右子樹高度),但整棵樹仍是平衡的。
插入 30 以後,80節點的平衡因子就成爲了 2,此時平衡被破壞,須要進行調整,繞節點 50 進行右旋轉,最終樹型結構變成右圖。
右旋轉場景:當樹中節點 X 的左孩子的左孩子上插入新元素,且平衡因子從 1 變成 2 後,就須要繞節點 X 進行右旋轉。
不少時候,插入元素一次調整知足不了要求,以下圖就是左旋與右旋的結合,具體操做時能夠分解成這兩種操做,只是圍繞點不同而已。
與之對應的,也有右旋與左旋的結合,以下圖:
因而可知,經過左旋轉、右旋轉操做,平衡二叉樹不會出現普通二叉查找樹的最差狀況,其查找的時間複雜度爲O(logN)!
在查詢的時候,操做與普通二叉查找樹上的查找操做相同;插入的時候,每一次插入結點操做最多隻須要單旋轉或雙旋轉,整體上插入操做的代價仍然在O(logN)級別;若是是動態刪除,刪除以後必須檢查從刪除結點開始到根結點路徑上的全部結點的平衡因子,最多可能須要O(logN)次旋轉。
爲了解決儘量少的旋轉調整,紅黑樹出現了!
紅黑樹,英文名稱:red-black tree,簡稱 RBT!紅黑樹也是基於平衡二叉樹結構的一種實現,可是它的平衡指標沒有像 AVL 算法那樣要求很嚴格,並非高度平衡但基本平衡,特性以下:
紅黑樹,在查找方面,與普通二叉查找樹上的查找操做相同;在插入、刪除方面,調整方式和平衡二叉查找樹相似,同樣是左旋轉、右旋轉,其中紅黑樹還增長一個調整操做:節點顏色轉換。
從上面的特性能夠看出,從每一個葉子到根的全部路徑上不能有兩個連續的紅色結點,對於不知足特性的節點顏色,只須要轉換顏色一些便可,比較簡單!
紅黑樹,在插入的時候,與 AVL 同樣,結點最多隻須要2次旋轉;在刪除的時候,由於沒有像 AVL 那樣高度平衡的要求,刪除一個結點最多隻須要3次旋轉操,可見紅黑樹的刪除操做代價要比 AVL 要好的多;由於不是高度平衡,在查詢方面,紅黑樹在查詢效率方面稍遜於 AVL,可是比二叉查找樹強不少!
在 JDK 中就有不少紅黑樹的具體實現,最典型的就是 JDK1.8 中的 HashMap,當衝突鏈表長度大於 8 時,鏈表就會以紅黑樹結構存儲。
哈夫曼樹是一種特殊結構的二叉樹,主要由哈夫曼編碼實現,內容定義以下:
「給定N個權值做爲N個葉子結點,構造一棵二叉樹,若這棵二叉樹的帶權路徑長度達到最小,則稱這樣的二叉樹爲最優二叉樹,也稱爲Huffman樹。
哈夫曼樹是帶權路徑長度最短的樹,權值較大的結點離根較近。
哈夫曼編碼的由來!
1951年,哈夫曼在麻省理工學院(MIT)攻讀博士學位,他和修讀信息論課程的同窗正在想選擇是完成學期報告仍是期末考試。此時的導師羅伯特·法諾(Robert Fano)出的學期報告題目是:查找最有效的二進制編碼。
因爲沒法證實哪一個已有編碼是最有效的,哈夫曼放棄對已有編碼的研究,轉向新的探索,最終發現了基於有序頻率二叉樹編碼的想法,並很快證實了這個方法是最有效的。
哈夫曼使用自底向上的方法構建二叉樹,避免了次優算法香農-範諾編碼(Shannon–Fano coding)的最大弊端──自頂向下構建樹。
並於1952年,在論文《一種構建極小多餘編碼的方法》(A Method for the Construction of Minimum-Redundancy Codes)中發表了這個編碼方法。
在上文中講的 BST、AVL、RBT 都是典型的二叉查找樹結構,其查找的時間複雜度與樹高相關。
下降樹的高度能夠提升查找效率,另外還有一個比較實際的問題:就是大量數據存儲中,實現查詢這樣一個實際背景下,平衡二叉樹因爲樹深度過大而形成磁盤IO讀寫過於頻繁,進而致使效率低下。
那麼如何減小樹的高度,一個基本的想法就是:
這樣咱們就提出來了一個新的查找樹結構 ——多路查找樹,根據 AVL 給咱們的啓發,一顆平衡多路查找樹可使得數據的查找效率保證在O(logN)這樣的對數級別上。
在計算機科學中,平衡多路查找樹,簡稱爲B樹,每一個節點能夠擁有2個以上的子節點,可以保持數據有序。
這種數據結構可以讓查找數據、順序訪問、插入數據及刪除的動做,都在對數時間內完成。
與自平衡二叉查找樹不一樣,B 樹適用於讀寫相對大的數據塊的存儲系統,例如磁盤。
B樹減小定位記錄時所經歷的中間過程,從而加快存取速度。這種數據結構常被應用在數據庫和文件系統的實現上。
B~樹,也就是咱們常說的 B 樹,其實 B~樹和 B 樹是同一種樹,咱們知道 B 樹是多叉結構,假如給定一個變量 m 來指定多叉,一棵 m 階的 B~樹(m叉樹)的特性以下:
例如:下面就是一棵3階B~樹,如圖所示:
B樹相對於平衡二叉樹的不一樣是,每一個節點包含的關鍵字增多了,特別是在 B 樹應用到數據庫中的時候,數據庫充分利用了磁盤塊的原理,把節點大小限制和充分使用在磁盤快大小範圍;把樹的節點關鍵字增多後樹的層級比原來的二叉樹少了,減小數據查找的次數和複雜度。
B+樹是B樹的一個升級版,相對於B樹來講B+樹更充分的利用了節點的空間,讓查詢速度更加穩定,其速度徹底接近於二分法查找。
一棵m階的B+樹和m階的B-樹的差別,內容以下:
例如:下面就是一棵3階B+樹,咱們能夠和B~樹作一個明顯的對比,如圖所示:
B+ 樹相比B樹的優點以下:
但B樹也不是徹底沒有優點,B樹相對於B+樹的優勢是:若是常常訪問的數據離根節點很近,而B樹的非葉子節點自己存有關鍵字其數據的地址,因此這種數據檢索的時候會要比B+樹快。
B*
樹,是 B+樹的變體,在 B+樹的非根和非葉子結點上增長了指向兄弟的指針,不一樣之處以下:
B*
樹的初始化個數爲(cei(2/3*m)
);B*
樹節點滿時會檢查兄弟節點是否滿(由於每一個節點都有指向兄弟的指針),若是兄弟節點未滿則向兄弟節點轉移關鍵字,若是兄弟節點已滿,則從當前節點和兄弟節點各拿出1/3的數據建立一個新的節點出來;B*
樹相比B+樹的優點:在B+樹的基礎上因其初始化的容量變大,使得節點空間使用率更高,而在非根和非葉子結點上增長指向兄弟的指針,能夠向兄弟節點轉移關鍵字的特性使得B*
樹的分解次數變得更少!
關於樹的故事,基本介紹完了,內容比較多,尤爲B樹模型,比較深奧複雜,有興趣的朋友能夠本身研究一些,若是有理解不當的地方,歡迎網友指出!
一、百度百科 - 樹
二、維基百科 - 樹
三、掘金 - 西召 - 樹結構與Java實現
四、掘金 - 張拭心 - 二叉樹、平衡二叉樹、二叉查找樹
五、iteye - Heart.X.Raid - 動態查找樹比較
六、知乎 - 勤勞的小手 - 平衡二叉樹、B樹、B+樹、B*