淺談AVL樹,紅黑樹,B樹,B+樹原理及應用

背景:這幾天在看《高性能Mysql》,在看到創建高性能的索引,書上說mysql的存儲引擎InnoDB採用的索引類型是B+Tree,那麼,大家有沒有產生這樣一個疑問,對於數據索引,爲什麼要使用B+Tree這種數據結構,和其它樹相比,它能體現的優點在哪裏? 看完這篇文章你就會瞭解到這些數據結構的原理以及它們各自的應用場景.

二叉查找樹

簡介

二叉查找樹也稱爲有序二叉查找樹,滿足二叉查找樹的一般性質,是指一棵空樹具有如下性質:

  • 任意節點左子樹不爲空,則左子樹的值均小於根節點的值.
  • 任意節點右子樹不爲空,則右子樹的值均大於於根節點的值.
  • 任意節點的左右子樹也分別是二叉查找樹.
  • 沒有鍵值相等的節點.

侷限性及應用

一個二叉查找樹是由n個節點隨機構成,所以,對於某些情況,二叉查找樹會退化成一個有n個節點的線性鏈.如下圖:
這裏寫圖片描述
b圖爲一個普通的二叉查找樹,大家看a圖,如果我們的根節點選擇是最小或者最大的數,那麼二叉查找樹就完全退化成了線性結構,因此,在二叉查找樹的基礎上,又出現了AVL樹,紅黑樹,它們兩個都是基於二叉查找樹,只是在二叉查找樹的基礎上又對其做了限制.

AVL樹

簡介

AVL樹是帶有平衡條件的二叉查找樹,一般是用平衡因子差值判斷是否平衡並通過旋轉來實現平衡,左右子樹樹高不超過1,和紅黑樹相比,它是嚴格的平衡二叉樹,平衡條件必須滿足(所有節點的左右子樹高度差不超過1).不管我們是執行插入還是刪除操作,只要不滿足上面的條件,就要通過旋轉來保持平衡,而旋轉是非常耗時的,由此我們可以知道AVL樹適合用於插入刪除次數比較少,但查找多的情況。
這裏寫圖片描述
從上面這張圖我們可以看出,任意節點的左右子樹的平衡因子差值都不會大於1.

侷限性

由於維護這種高度平衡所付出的代價比從中獲得的效率收益還大,故而實際的應用不多,更多的地方是用追求局部而不是非常嚴格整體平衡的紅黑樹.當然,如果應用場景中對插入刪除不頻繁,只是對查找要求較高,那麼AVL還是較優於紅黑樹.

應用

Windows NT內核中廣泛存在.

紅黑樹

簡介

一種二叉查找樹,但在每個節點增加一個存儲位表示節點的顏色,可以是red或black. 通過對任何一條從根到葉子的路徑上各個節點着色的方式的限制,紅黑樹確保沒有一條路徑會比其它路徑長出兩倍.它是一種弱平衡二叉樹(由於是若平衡,可以推出,相同的節點情況下,AVL樹的高度低於紅黑樹),相對於要求嚴格的AVL樹來說,它的旋轉次數變少,所以對於搜索,插入,刪除操作多的情況下,我們就用紅黑樹.

性質

  • 每個節點非紅即黑.
  • 根節點是黑的。
  • 每個葉節點(葉節點即樹尾端NUL指針或NULL節點)都是黑的.
  • 如果一個節點是紅的,那麼它的兩兒子都是黑的.
  • 對於任意節點而言,其到葉子點樹NIL指針的每條路徑都包含相同數目的黑節點.
    這裏寫圖片描述
    每條路徑都包含相同的黑節點.

應用

  • 廣泛用於C++的STL中,map和set都是用紅黑樹實現的.
  • 著名的linux進程調度Completely Fair Scheduler,用紅黑樹管理進程控制塊,進程的虛擬內存區域都存儲在一顆紅黑樹上,每個虛擬地址區域都對應紅黑樹的一個節點,左指針指向相鄰的地址虛擬存儲區域,右指針指向相鄰的高地址虛擬地址空間.
  • IO多路複用epoll的實現採用紅黑樹組織管理sockfd,以支持快速的增刪改查.
  • ngnix中,用紅黑樹管理timer,因爲紅黑樹是有序的,可以很快的得到距離當前最小的定時器.
  • java中TreeMap的實現.

B/B+樹

注意B-樹就是B樹,-只是一個符號.

簡介

B/B+樹是爲了磁盤或其它存儲設備而設計的一種平衡多路查找樹(相對於二叉,B樹每個內節點有多個分支),與紅黑樹相比,在相同的的節點的情況下,一顆B/B+樹的高度遠遠小於紅黑樹的高度(在下面B/B+樹的性能分析中會提到).B/B+樹上操作的時間通常由存取磁盤的時間和CPU計算時間這兩部分構成,而CPU的速度非常快,所以B樹的操作效率取決於訪問磁盤的次數,關鍵字總數相同的情況下B樹的高度越小,磁盤I/O所花的時間越少.

B樹的性質

  • 定義任意非葉子結點最多隻有M個兒子;且M>2;
  • 根結點的兒子數爲[2, M];
  • 除根結點以外的非葉子結點的兒子數爲[M/2, M];
  • 每個結點存放至少M/2-1(取上整)和至多M-1個關鍵字;(至少2個關鍵字)
  • 非葉子結點的關鍵字個數=指向兒子的指針個數-1;
  • 非葉子結點的關鍵字:K[1], K[2], …, K[M-1];且K[i] < K[i+1];
  • 非葉子結點的指針:P[1], P[2], …, P[M];其中P[1]指向關鍵字小於K[1]的子樹,P[M]指向關鍵字大於K[M-1]的子樹,其它P[i]指向關鍵字屬於(K[i-1], K[i])的子樹;
  • 所有葉子結點位於同一層;
    這裏寫圖片描述
    這裏只是一個簡單的B樹,在實際中B樹節點中關鍵字很多的.上面的圖中比如35節點,35代表一個key(索引),而小黑塊代表的是這個key所指向的內容在內存中實際的存儲位置.是一個指針.

B+樹

B+樹是應文件系統所需而產生的一種B樹的變形樹(文件的目錄一級一級索引,只有最底層的葉子節點(文件)保存數據.),非葉子節點只保存索引,不保存實際的數據,數據都保存在葉子節點中.這不就是文件系統文件的查找嗎?我們就舉個文件查找的例子:有3個文件夾,a,b,c, a包含b,b包含c,一個文件yang.c, a,b,c就是索引(存儲在非葉子節點), a,b,c只是要找到的yang.c的key,而實際的數據yang.c存儲在葉子節點上.
所有的非葉子節點都可以看成索引部分

B+樹的性質(下面提到的都是和B樹不相同的性質)

  • 非葉子節點的子樹指針與關鍵字個數相同;
  • 非葉子節點的子樹指針p[i],指向關鍵字值屬於[k[i],k[i+1]]的子樹.(B樹是開區間,也就是說B樹不允許關鍵字重複,B+樹允許重複);
  • 爲所有葉子節點增加一個鏈指針.
  • 所有關鍵字都在葉子節點出現(稠密索引). (且鏈表中的關鍵字恰好是有序的);
  • 非葉子節點相當於是葉子節點的索引(稀疏索引),葉子節點相當於是存儲(關鍵字)數據的數據層.
  • 更適合於文件系統;
    看下圖:
    這裏寫圖片描述
    非葉子節點(比如5,28,65)只是一個key(索引),實際的數據存在葉子節點上(5,8,9)纔是真正的數據或指向真實數據的指針.

應用  

B和B+樹主要用在文件系統以及數據庫做索引.比如Mysql;

B/B+樹性能分析 

  • n個節點的平衡二叉樹的高度爲H(即logn),而n個節點的B/B+樹的高度爲logt((n+1)/2)+1;   
  • 若要作爲內存中的查找表,B樹卻不一定比平衡二叉樹好,尤其當m較大時更是如此.因爲查找操作CPU的時間在B-樹上是O(mlogtn)=O(lgn(m/lgt)),而m/lgt>1;所以m較大時O(mlogtn)比平衡二叉樹的操作時間大得多. 因此在內存中使用B樹必須取較小的m.(通常取最小值m=3,此時B-樹中每個內部結點可以有2或3個孩子,這種3階的B-樹稱爲2-3樹)。

爲什麼說B+tree比B樹更適合實際應用中操作系統的文件索引和數據索引.  

  • B+-tree的內部節點並沒有指向關鍵字具體信息的指針,因此其內部節點相對B樹更小,如果把所有同一內部節點的關鍵字存放在同一盤塊中,那麼盤塊所能容納的關鍵字數量也越多,一次性讀入內存的需要查找的關鍵字也就越多,相對IO讀寫次數就降低了.
  • 由於非終結點並不是最終指向文件內容的結點,而只是葉子結點中關鍵字的索引。所以任何關鍵字的查找必須走一條從根結點到葉子結點的路。所有關鍵字查詢的路徑長度相同,導致每一個數據的查詢效率相當。
    ps:我在知乎上看到有人是這樣說的,我感覺說的也挺有道理的:
    他們認爲數據庫索引採用B+樹的主要原因是:B樹在提高了IO性能的同時並沒有解決元素遍歷的我效率低下的問題,正是爲了解決這個問題,B+樹應用而生.B+樹只需要去遍歷葉子節點就可以實現整棵樹的遍歷.而且在數據庫中基於範圍的查詢是非常頻繁的,而B樹不支持這樣的操作(或者說效率太低).  
發佈了109 篇原創文章 · 獲贊 238 · 訪問量 37萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章