咬文嚼圖式的介紹二叉樹、B樹/B-樹

灯火消逝的码头發表於2024-07-16

前言

因為本人天資愚鈍,所以總喜歡將抽象化的事務具象化表達。對於各類眼花繚亂的樹,只需要認知到它們只是一種資料結構,類似陣列,切片,列表,對映等這些耳熟能詳的詞彙。對於一個資料結構而言,無非就是增刪改查而已,既然各類樹也是資料結構,它們就不能逃離增刪改查的桎梏。

那麼,為什麼我們需要樹這種資料結構呢,直接用陣列不行嗎,用切片不行嗎?當然可以,只不過現實世界是繽紛雜亂的,而又沒有一種萬能藥式的資料結構以應對千變萬化的業務需求。所以,才會有各類樹,而且一些“高階”資料結構是基於樹形資料結構的,例如對映。

二叉樹

在中文語境中,節點結點傻傻分不清楚,故後文以 node 代表 "結點",root node 代表根節點,child node 代表 “子節點”

二叉樹是諸多樹狀結構的始祖,至於為什麼不是三叉樹,四叉樹,或許是因為計算機只能數到二吧,哈哈,開個玩笑。二叉樹很簡單,每個 node 最多存在兩個 child node,第一個節點稱之為 root node。

咬文嚼圖式的介紹二叉樹、B樹/B-樹

二叉樹具備著一些基本的數學性質,不過很簡單,定義從 i 從 0 開始:

  • i 層至多有 2i 個 node;
  • 深度為 i 層二叉樹至多有 2i+1-1 個 node。

二叉樹的特殊型別

這裡有興趣的可以瞭解一下,不影響後文的閱讀。二叉樹根據 child node 的不同,衍生出了幾種特殊型別:在一顆二叉樹中,如果每個 node 都有 0 或 2 個 child node,則二叉樹是滿二叉樹;定義從 i 從 0 開始,一棵深度為 i,且僅有 2i+1−1 個 node 的二叉樹,稱為完美二叉樹;若除最後一層外的其餘層都是滿的,並且最後一層要麼是滿的,要麼在右邊缺少連續若干 node,則此二叉樹為完全二叉樹

二叉搜尋樹

二叉搜尋樹(Binary Search Tree),也叫二叉查詢樹,有序二叉樹,排序二叉樹(名字還挺多)。它是一種常用且特殊的二叉樹,它具備一個特有的性質,left node(左結點)始終小於 parent node (父結點),right node 始終大於 parent node。

咬文嚼圖式的介紹二叉樹、B樹/B-樹

二叉搜尋樹的查詢

  1. 二叉搜尋樹從 root node 開始,如果命中則返回;
  2. 否則,目標值比 node 小進入 left node;
  3. 比 node 大進入 right node;
  4. 如果左右都為空,則未命中。

咬文嚼圖式的介紹二叉樹、B樹/B-樹

二叉搜尋樹的遍歷

二叉搜尋樹有不同的遍歷方式,這裡介紹常用的中序遍歷方式:

  1. 先遍歷左子樹;
  2. 然後查詢當前左子樹的 parent node;
  3. 遍歷右子樹。

咬文嚼圖式的介紹二叉樹、B樹/B-樹

二叉搜尋樹的插入

  1. 二叉搜尋樹從 root node 開始,如果命中則不進行操作;
  2. 否則,目標值比 node 小進入 left node;
  3. 比 node 大進入 right node;
  4. 最終將值插入搜尋停止的地方。

咬文嚼圖式的介紹二叉樹、B樹/B-樹

二叉搜尋樹的刪除

二叉樹的刪除和查詢基本一致,只要在命中時刪除即可。

  1. 二叉搜尋樹從 root node 開始,如果命中則刪除;
  2. 否則,目標值比 node 小進入 left node;
  3. 比 node 大進入 right node;
  4. 刪除後使用該 node 左子樹最大值或者右子樹最小值替代該 node。

咬文嚼圖式的介紹二叉樹、B樹/B-樹

自平衡二叉樹

從上面的幾張動圖中我們知曉,二叉搜尋樹不同於線性結構,它可以大大降低查詢,插入的時間複雜度。但在特殊情況下,二叉搜尋樹可能退化為線性結構,假如我們依次插入1,2,3,4,5:

咬文嚼圖式的介紹二叉樹、B樹/B-樹

此時,二叉搜尋樹退化為線性結構,效率重新變回遍歷。於是,便出現了自平衡二叉樹,例如 AVL 樹,紅黑樹,替罪羊樹等。但它們並不是本文重點,下面我要介紹的是另外一種很常見的自平衡二叉樹:B樹。

B樹

B樹和B-樹是同一個概念。B樹相對於二叉樹有兩點最大的不同:

  • 每個 node 可以有不止一個數值
  • 每個 node 也可以有不止兩個 child node

B樹有兩種型別 node:

  • internal node(內部結點):不僅僅儲存資料,也具備 child node;
  • leaf node(葉子結點):僅儲存資料,不具備 child node。

這兩種 node 不同於前文所提的 root node 和 child node。root 和 child 是相對於階層的概念,而 internal 和 leaf 是相對於性質的概念

一個簡單的圖例如下:

咬文嚼圖式的介紹二叉樹、B樹/B-樹

圖中的藍色方塊是 internal node,綠色則是 leaf node。

B樹有一些需要滿足的性質,這裡的抽象的邏輯有些燒腦,我會對照前面的圖片來解釋。設定一顆 m 階的B樹,m = 3

設 internal node 的 child node 個數為 k

  1. 如果 internal node 是 root node,那麼 k = [2, m],比如上圖的 8 有兩個 child node(3|6, 10/12);
  2. 如果 internal node 不是 root node,那麼 k = [m/2, m],m/2 向上取整,比如上圖的 3|6 有三個 child node;
  3. 如果 root node 的 k 為 0,那麼 root node 是 leaf 型別的;
  4. 所有 leaf node 在同一層,上圖最後一行的六個 node。

設任意 node 鍵值個數為 n

  1. 對於 internal node, n = k-1, 升序排序,滿足 k[i] < k[i+1],比如上圖的三個 internal(8,3|6,10|12) 都滿足此規律;
  2. 對於 leaf node,n = [0, m-1],同樣升序排序,比如上圖最後一個的六個 leaf,其鍵值最多為兩個。

上述的概念有些抽象,但是這是理解B樹關鍵的地方所在,後面在B樹的插入講解,會有更多具象的動圖來解釋這些概念。

B樹的查詢

B樹的查詢類似於二叉樹:

  1. 從 root node 開始,如果目標值小於 root node,進入左子樹,否則進入右子樹;
  2. 遍歷 child node 的多個鍵值;
  3. 如果匹配到鍵值,則返回;
  4. 如果不匹配,則根據目標值的範圍選擇對應的子樹;
  5. 重複步驟2、3、4,直到匹配成功返回或者未找到。

假如我們要查詢 11:

咬文嚼圖式的介紹二叉樹、B樹/B-樹

B樹的遍歷

B樹的遍歷方式類似二叉搜尋樹,不過因為B樹一個 node 有多個鍵值和多個 child node,所以需要遍歷每個左右子樹和鍵值:

  1. 先遍歷第一個左子樹,也就是 parent node 第一個鍵值的左邊;
  2. 然後查詢當前 parent node 的第一個鍵值;
  3. 遍歷第二個左子樹,也就是 parent node 第二個鍵值的左邊;
  4. 遍歷完搜尋的左子樹,最後遍歷當前 parent 的最右子樹,即最後一個鍵值的右邊。

咬文嚼圖式的介紹二叉樹、B樹/B-樹

B樹的插入

插入前面的過程和查詢一致,在插入後可能需要重整 node,以符合B樹的性質,例如插入 16:

  1. 先查詢到目標 node,也就是 13|15
  2. 因為這是一顆 3 階B樹,所以 node 最多隻能有兩個鍵值,於是向上傳遞中間值 15;
  3. parent node 最多也只能有兩個鍵值,於是繼續向上傳遞中間值 12;
  4. 此時 root node 是 8|12,需要有三個 child node,於是 10|15 需要拆分,再向下進一步調整,至此,插入 16 完成。

咬文嚼圖式的介紹二叉樹、B樹/B-樹

B樹的刪除

刪除是插入的逆操作,但是往往比插入更復雜,因為刪除後經常需要重整 node:

  1. 先查詢到目標 node,也就是 16
  2. 刪除 16,此時 15 child node 剩下一個,不符合條件,遞迴向上調整,一直到根節點;
  3. 直到所有的條件都滿足後,刪除 16 完成。

咬文嚼圖式的介紹二叉樹、B樹/B-樹

相關文章