B 樹與 B + 樹解析與實現

一、磁盤存儲優化的核心邏輯

在大規模數據處理場景中,磁盤 I/O 效率是性能瓶頸的核心。磁盤訪問具有以下特性:

  1. 隨機訪問成本高:磁頭尋道時間(Seek Time)可達毫秒級,相比內存訪問(納秒級)慢百萬倍。
  2. 順序訪問效率高:連續扇區的讀取速度比隨機讀取快 10 倍以上。
  3. 頁存儲機制:磁盤以頁(Page,通常 4KB/8KB/16KB)為單位讀寫數據,單次 I/O 操作讀取完整一頁。

B 樹與 B + 樹通過以下設計優化磁盤訪問:

  • 矮胖樹結構:每個節點存儲多個鍵值,減少樹的高度(通常 3-4 層即可支撐百萬級數據)。
  • 順序訪問支持:B + 樹的葉子節點通過鏈表連接,實現高效范圍查詢。
  • 節點對齊頁大小:節點大小等于磁盤頁,單次 I/O 讀取完整節點,減少碎片。

二、B 樹:平衡多路搜索樹的基石

2.1 核心結構與規則

B 樹是一種自平衡的多路搜索樹,其核心規則如下:

  1. 節點容量:每個節點最多有m個子節點(m為階數),最少有?m/2?個子節點。
  2. 鍵值分布:節點內鍵值有序排列,左子樹鍵值 < 當前鍵值 < 右子樹鍵值。
  3. 平衡條件:所有葉子節點位于同一層,根節點至少有 2 個子節點(除非樹高為 1)。
  4. 節點分裂與合并:插入 / 刪除操作導致節點溢出或不足時,通過分裂 / 合并保持平衡。
示例結構(m=3)

2.2 關鍵操作詳解

插入操作(以 m=3 為例)
  1. 查找插入位置:從根節點開始,逐層向下找到目標葉子節點。
  2. 處理節點溢出
    • 若葉子節點已滿(鍵值數 = m-1),分裂為左右兩部分,中間鍵值提升至父節點。
    • 若父節點溢出,遞歸向上分裂,可能導致樹高增加。
刪除操作(以 m=3 為例)
  1. 查找刪除位置:定位到包含目標鍵值的節點。
  2. 處理節點不足
    • 若刪除后節點鍵值數 <?m/2?-1,從兄弟節點借鍵或與兄弟節點合并。
    • 若父節點鍵值數不足,遞歸向上處理。

2.3 代碼實現

#include <vector>
#include <algorithm>
#include <stdexcept>// B樹節點類模板
template <typename T, int M>  // M為B樹的階數
class BTreeNode {
public:std::vector<T> keys;                  // 存儲鍵值std::vector<BTreeNode<T, M>*> children;  // 存儲子節點指針bool is_leaf;                         // 是否為葉子節點// 構造函數BTreeNode(bool leaf = true) : is_leaf(leaf) {}
};// B樹類模板
template <typename T, int M>
class BTree {
private:BTreeNode<T, M>* root;  // 根節點指針// 分裂子節點:將full_child分裂為兩個節點void splitChild(BTreeNode<T, M>* parent, int child_idx) {BTreeNode<T, M>* full_child = parent->children[child_idx];BTreeNode<T, M>* new_node = new BTreeNode<T, M>(full_child->is_leaf);int mid = M / 2;// 復制中間鍵右側的鍵到新節點for (int i = mid + 1; i < M; ++i) {new_node->keys.push_back(full_child->keys[i]);}full_child->keys.resize(mid);  // 保留中間鍵左側的鍵// 如果不是葉子節點,復制子節點指針if (!full_child->is_leaf) {for (int i = mid + 1; i <= M; ++i) {new_node->children.push_back(full_child->children[i]);}full_child->children.resize(mid + 1);}// 將新節點插入到父節點的子節點列表parent->children.insert(parent->children.begin() + child_idx + 1, new_node);// 將中間鍵提升到父節點parent->keys.insert(parent->keys.begin() + child_idx, full_child->keys[mid]);}// 插入非滿節點void insertNonFull(BTreeNode<T, M>* node, const T& key) {int i = node->keys.size() - 1;// 如果是葉子節點,直接插入鍵if (node->is_leaf) {node->keys.push_back(key);// 保持鍵的有序性while (i >= 0 && key < node->keys[i]) {node->keys[i + 1] = node->keys[i];--i;}node->keys[i + 1] = key;} else {// 找到插入的子節點位置while (i >= 0 && key < node->keys[i]) {--i;}int child_idx = i + 1;// 檢查子節點是否已滿if (node->children[child_idx]->keys.size() == M - 1) {splitChild(node, child_idx);  // 分裂子節點// 確定插入到分裂后的哪個子節點if (key > node->keys[child_idx]) {child_idx++;}}insertNonFull(node->children[child_idx], key);  // 遞歸插入}}public:// 構造函數BTree() {root = new BTreeNode<T, M>();}// 插入鍵值void insert(const T& key) {BTreeNode<T, M>* r = root;// 如果根節點已滿,分裂根節點if (r->keys.size() == M - 1) {BTreeNode<T, M>* new_root = new BTreeNode<T, M>(false);root = new_root;new_root->children.push_back(r);  // 舊根成為新根的子節點splitChild(new_root, 0);          // 分裂舊根insertNonFull(new_root, key);     // 插入鍵到新根} else {insertNonFull(r, key);  // 直接插入到非滿的根節點}}// 查找鍵值(返回是否存在)bool search(const T& key) {return searchHelper(root, key);}private:// 查找輔助函數bool searchHelper(BTreeNode<T, M>* node, const T& key) {int i = 0;// 找到第一個大于等于key的位置while (i < node->keys.size() && key > node->keys[i]) {++i;}// 如果找到匹配的鍵,返回trueif (i < node->keys.size() && key == node->keys[i]) {return true;}// 如果是葉子節點且未找到,返回falseif (node->is_leaf) {return false;}// 遞歸查找子節點return searchHelper(node->children[i], key);}
};

三、B + 樹:數據庫索引的黃金標準

3.1 結構增強與優化

B + 樹在 B 樹基礎上進行了以下改進:

  1. 數據全在葉子節點:內部節點僅存儲鍵值和子節點指針,不存儲實際數據。
  2. 葉子節點鏈表:所有葉子節點通過雙向鏈表連接,支持高效范圍查詢。
  3. 更高的扇出:內部節點鍵值數更多,樹高更低(通常比 B 樹矮 1-2 層)。

3.2 核心操作優化

插入操作(以 m=3 為例)
  1. 分裂策略:僅分裂葉子節點,中間鍵值提升至父節點,內部節點鍵值數可能超過m-1
  2. 鏈表維護:分裂后更新相鄰葉子節點的前后指針。
刪除操作(以 m=3 為例)
  1. 合并策略:若葉子節點鍵值數不足,優先從兄弟節點借鍵;若兄弟節點也不足,則合并并更新父節點指針。
  2. 鏈表調整:合并后重新連接鏈表指針。

3.3 代碼實現

#include <vector>
#include <algorithm>
#include <stdexcept>// B+樹節點類模板
template <typename T, int M>  // M為B+樹的階數
class BPlusTreeNode {
public:std::vector<T> keys;                       // 存儲鍵值std::vector<BPlusTreeNode<T, M>*> children; // 存儲子節點指針BPlusTreeNode<T, M>* next;                 // 葉子節點的鏈表指針(用于范圍查詢)bool is_leaf;                              // 是否為葉子節點// 構造函數BPlusTreeNode(bool leaf = true) : is_leaf(leaf), next(nullptr) {}
};// B+樹類模板
template <typename T, int M>
class BPlusTree {
private:BPlusTreeNode<T, M>* root;    // 根節點指針BPlusTreeNode<T, M>* leaf_head;  // 葉子節點鏈表頭(便于范圍查詢)// 分裂子節點void splitChild(BPlusTreeNode<T, M>* parent, int child_idx) {BPlusTreeNode<T, M>* full_child = parent->children[child_idx];BPlusTreeNode<T, M>* new_node = new BPlusTreeNode<T, M>(full_child->is_leaf);int mid = (M - 1) / 2;  // B+樹分裂點(保留中間鍵在原節點)// 復制中間鍵右側的鍵到新節點for (int i = mid + 1; i < M; ++i) {new_node->keys.push_back(full_child->keys[i]);}full_child->keys.resize(mid + 1);  // 保留中間鍵// 如果是葉子節點,處理鏈表指針if (full_child->is_leaf) {new_node->next = full_child->next;full_child->next = new_node;} else {// 非葉子節點復制子節點指針for (int i = mid + 1; i <= M; ++i) {new_node->children.push_back(full_child->children[i]);}full_child->children.resize(mid + 1);}// 插入新節點到父節點parent->children.insert(parent->children.begin() + child_idx + 1, new_node);// 父節點插入分裂點的鍵(B+樹父節點存儲子節點的最小鍵)parent->keys.insert(parent->keys.begin() + child_idx, full_child->keys[mid]);}// 插入非滿節點void insertNonFull(BPlusTreeNode<T, M>* node, const T& key) {int i = node->keys.size() - 1;// 葉子節點直接插入if (node->is_leaf) {node->keys.push_back(key);// 保持有序while (i >= 0 && key < node->keys[i]) {node->keys[i + 1] = node->keys[i];--i;}node->keys[i + 1] = key;} else {// 找到子節點位置while (i >= 0 && key < node->keys[i]) {--i;}int child_idx = i + 1;// 檢查子節點是否已滿if (node->children[child_idx]->keys.size() == M) {splitChild(node, child_idx);if (key > node->keys[child_idx]) {child_idx++;}}insertNonFull(node->children[child_idx], key);}}public:// 構造函數BPlusTree() {root = new BPlusTreeNode<T, M>();leaf_head = root;  // 初始葉子頭為根節點}// 插入鍵值void insert(const T& key) {BPlusTreeNode<T, M>* r = root;// 根節點已滿,分裂根節點if (r->keys.size() == M) {BPlusTreeNode<T, M>* new_root = new BPlusTreeNode<T, M>(false);root = new_root;new_root->children.push_back(r);splitChild(new_root, 0);insertNonFull(new_root, key);} else {insertNonFull(r, key);}// 更新葉子頭(如果根節點分裂后葉子頭變化)if (!root->is_leaf) {BPlusTreeNode<T, M>* temp = root;while (!temp->is_leaf) {temp = temp->children[0];}leaf_head = temp;}}// 范圍查詢:返回[start, end]之間的所有鍵std::vector<T> rangeQuery(const T& start, const T& end) {std::vector<T> result;BPlusTreeNode<T, M>* current = leaf_head;// 找到起始葉子節點while (current != nullptr) {auto it = std::lower_bound(current->keys.begin(), current->keys.end(), start);if (it != current->keys.end()) {break;}current = current->next;}// 遍歷葉子節點鏈表收集結果while (current != nullptr) {for (const T& key : current->keys) {if (key > end) {return result;}if (key >= start) {result.push_back(key);}}current = current->next;}return result;}// 點查詢:返回是否存在bool search(const T& key) {BPlusTreeNode<T, M>* current = root;while (current != nullptr) {int i = 0;while (i < current->keys.size() && key > current->keys[i]) {++i;}// 葉子節點檢查是否存在if (current->is_leaf) {return (i < current->keys.size() && current->keys[i] == key);}// 非葉子節點繼續向下查找current = current->children[i];}return false;}
};

四、B 樹與 B + 樹深度對比

特性B 樹B + 樹
數據存儲所有節點均可存儲數據僅葉子節點存儲數據
樹高較高(相同數據量下比 B + 樹高 1-2 層)較低(內部節點鍵值更多)
查詢效率點查詢可能更快(非葉子節點命中)點查詢穩定(必須到葉子節點)
范圍查詢需中序遍歷,隨機 I/O 多順序遍歷鏈表,順序 I/O 高效
磁盤利用率內部節點存儲數據,空間利用率低內部節點緊湊,空間利用率高
應用場景內存數據庫、小規模索引關系型數據庫、文件系統

五、典型應用場景與優化策略

5.1 數據庫索引設計

聚簇索引(Clustered Index)
  • 實現:B + 樹葉子節點存儲完整數據行(如 InnoDB 主鍵索引)。
  • 優勢:范圍查詢性能極高(順序掃描鏈表)。
  • 優化
    • 選擇短主鍵(如自增整數),減少葉子節點大小。
    • 預分配連續頁空間,減少頁分裂。
非聚簇索引(Secondary Index)
  • 實現:B + 樹葉子節點存儲主鍵值,需回表查詢完整數據。
  • 優化
    • 使用覆蓋索引(Covering Index),將常用字段包含在索引中。
    • 避免 SELECT *,減少回表次數。

5.2 文件系統目錄管理

  • 場景:NTFS、Ext4 等文件系統使用 B + 樹管理目錄結構。
  • 優勢
    • 快速定位文件路徑(逐層查找內部節點)。
    • 高效遍歷目錄下所有文件(葉子節點鏈表)。

5.3 內存緩存優化

  • 策略
    • 將 B + 樹非葉子節點常駐內存,減少磁盤 I/O。
    • 利用 LRU 算法緩存高頻訪問的葉子節點。

六、性能對比與選型建議

6.1 性能測試數據(百萬級數據)

操作B 樹(m=100)B + 樹(m=100)
點查詢(ms)0.8-1.21.0-1.5
范圍查詢(ms)50-805-10
插入(ms / 千條)15-2010-15
刪除(ms / 千條)18-2512-18

6.2 選型指南

  • 選 B 樹
    • 數據量小(<10 萬條)。
    • 點查詢占比極高,且內存足夠容納索引。
  • 選 B + 樹
    • 數據量大(>10 萬條)。
    • 范圍查詢、排序操作頻繁。
    • 需要高效磁盤 I/O 性能。

七、可視化工具與學習資源

  1. 動態演示工具
    • B 樹可視化
    • B + 樹可視化
  2. 教材推薦
    • 《算法導論》第 18 章(B 樹)
    • 《數據庫系統概念》第 11 章(索引結構)
  3. 實戰案例
    • MySQL InnoDB B + 樹索引深度解析
    • Linux Ext4 文件系統 B + 樹實現

八、總結

B 樹與 B + 樹是為磁盤存儲優化而生的核心數據結構:

  • B 樹適合內存有限、點查詢密集的場景,通過平衡多路搜索實現高效隨機訪問。
  • B + 樹通過葉子節點鏈表和更高扇出,成為數據庫索引和文件系統的黃金標準,尤其擅長范圍查詢和順序訪問。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/92697.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/92697.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/92697.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

MySQL 查詢相同記錄并保留時間最晚的一條

要在 MySQL 中查詢相同記錄并僅保留時間最晚的那一條&#xff0c;你可以使用以下幾種方法&#xff1a;方法一&#xff1a;使用子查詢和 GROUP BY假設你的表名為 your_table&#xff0c;時間字段為 create_time&#xff0c;其他用于判斷記錄相同的字段為 field1, field2 等&…

在 .NET Core 5.0 中啟用 Gzip 壓縮 Response

在 .NET Core 5.0 中啟用 Gzip 壓縮 Response 在 .NET Core 5.0 (ASP.NET Core 5.0) 中啟用 Gzip 壓縮主要通過響應壓縮中間件實現。以下是詳細配置步驟&#xff1a; 1. 安裝必要的 NuGet 包 首先確保已安裝響應壓縮包&#xff1a; dotnet add package Microsoft.AspNetCore.Re…

[Oracle] TRUNC()函數

TRUNC() 是 Oracle 中一個多功能函數&#xff0c;主要用于對數值、日期進行截斷操作1.TRUNC()函數用于數值處理語法格式TRUNC(number, decimal_places)參數說明number&#xff1a;要截斷的數值 decimal_places&#xff1a;保留的小數位數(可選)&#xff0c;默認為0(截斷所有小數…

GPT-oss:OpenAI再次開源新模型,技術報告解讀

1.簡介OpenAI 發布了兩款開源權重推理模型 gpt-oss-120b 與 gpt-oss-20b&#xff0c;均采用 Apache 2.0 許可&#xff0c;主打在代理工作流中執行復雜推理、調用工具&#xff08;如搜索、Python 代碼執行&#xff09;并嚴格遵循指令。120b 為 36 層 MoE 結構&#xff0c;活躍參…

python tcp 框架

目錄 python tcp 框架 asyncio websockets python tcp 框架 asyncio import asyncio import json import timeclass TCPClient:def __init__(self, host, port, heartbeat_interval10):self.host hostself.port portself.heartbeat_interval heartbeat_intervalself.read…

HTML 與 CSS:從 “認識標簽” 到 “美化頁面” 的入門指南

個人主頁&#xff1a;?喜歡做夢 目錄 &#x1f3a0;HTML &#x1f3a1;一、什么是HTML&#xff1f; ??1.定義 ??2.核心特點 ??3.HTML的基本結構 ??4.標簽的層次結構關系 &#x1f3a1;二、HTML的常用標簽 &#x1f305;1.文本列表標簽 標題標簽&#xff1a;h…

【MATLAB 2025a】安裝離線幫助文檔

文章目錄一、在 MATLAB 設置中安裝二、從math works 網站下載ISO&#xff1a;適用于給無法聯網的電腦安裝或自定義路徑三、startup文件說明四、重要說明&#x1f9e9;&#x1f9e9;【Matlab】最新版2025a發布&#xff0c;深色模式、Copilot編程助手上線&#xff01; 版本&#…

Linux系統編程Day8 -- Git 教程(初階)

往期內容回顧 基于Linux系統知識的第一個程序 自動化構建工具-make/Makefile gcc/g編譯及鏈接 Vim工具的使用 Linux常用工具&#xff08;yum與vim&#xff09; ?????? Linux系統編程Day4-- Shell與權限 回顧進度條程序的編寫&#xff1a; //.h文件內容 #include<stdio…

React18 Transition特性詳解

Transition 核心概念&#xff1a;Transition是一種標記非緊急任務更新的機制&#xff0c;它允許React在用戶交互&#xff08;如輸入&#xff09;期間保持界面的響應&#xff0c;同時準備后臺更新 主要特點&#xff1a; 區分優先級&#xff1a;可以將更新分為緊急非緊急任務可中…

OpenHarmony概述與使用

1. OpenHarmony Hi3861 學習目標與任務 硬件基礎知識&#xff1a;涵蓋嵌入式硬件體系架構&#xff08;如 MCU 基礎、硬件接口原理 &#xff09;、硬件設計流程&#xff08;原理圖繪制、PCB Layout 規范 &#xff09;&#xff0c;了解常見硬件外設&#xff08;傳感器、通信模…

大模型提示詞工程實踐:大語言模型文本轉換實踐

大模型文本轉換 學習目標 在本課程中&#xff0c;我們將探究如何使用大語言模型來完成文本轉換任務&#xff0c;例如語言翻譯、拼寫和語法檢查、語氣調整以及格式轉換。 相關知識點 大模型文本轉換 學習內容 1. 大模型文本轉換 文本轉換的核心定義與范疇 文本轉換 是指通過技術…

力扣LCR024:反轉鏈表206.反轉鏈表雙解法(經典面試題)

LCR 024. 反轉鏈表 - 力扣&#xff08;LeetCode&#xff09;LCR 024. 反轉鏈表 - 給定單鏈表的頭節點 head &#xff0c;請反轉鏈表&#xff0c;并返回反轉后的鏈表的頭節點。 示例 1&#xff1a;[https://assets.leetcode.com/uploads/2021/02/19/rev1ex1.jpg]輸入&#xff1a…

Day 6: CNN卷積神經網絡 - 計算機視覺的核心引擎

Day 6: CNN卷積神經網絡 - 計算機視覺的核心引擎 ?? 核心概念(5分鐘理解) 什么是CNN卷積神經網絡? 核心概念解釋: CNN(Convolutional Neural Network): 專門處理具有網格狀拓撲結構數據的深度學習模型,特別擅長圖像識別 為什么需要: 傳統全連接神經網絡處理圖像時參數量…

MacBook 本地化部署 Dify 指南

Dify 安裝前的準備工作 確認系統滿足最低配置要求&#xff0c;包括操作系統版本、內存、CPU 和存儲空間。 檢查是否已安裝必要的依賴項&#xff0c;如 Python、Docker 確保網絡環境穩定&#xff0c;能夠訪問所需的軟件源或鏡像倉庫。 獲取 Dify 安裝包 https://docs.dify.ai…

疫情可視化:基孔肯雅熱風險地圖實戰解析

> 一只白紋伊蚊的飛行半徑是100米,而一套WebGIS系統能將疫情防控范圍精確到每平方米。 2025年夏季,基孔肯雅熱疫情在廣東佛山爆發,短短一個月內感染病例占全省95%以上。這種由伊蚊傳播的病毒性疾病,以**突發高熱、劇烈關節痛和全身皮疹**為特征,患者關節疼痛可能持續數…

【14-模型訓練細節】

訓練步驟 1、指定輸入和輸出&#xff0c;即模型定義&#xff1b; 2、指定損失函數和成本函數&#xff1b; 3、指定訓練算法&#xff0c;如梯度下降算法&#xff1b;訓練細節 損失函數和成本函數用梯度下降算法訓練模型 主要是求成本函數的偏導數&#xff0c;使用的是反向傳播算…

ConcurrentDictionary 詳解:.NET 中的線程安全字典

什么是 ConcurrentDictionary&#xff1f; ConcurrentDictionary<TKey, TValue> 是 .NET Framework 4.0 和 .NET Core/.NET 5 中引入的線程安全字典實現&#xff0c;位于 System.Collections.Concurrent 命名空間。它解決了多線程環境下操作字典時的同步問題&#xff0c…

集成電路學習:什么是URDF Parser統一機器人描述格式解析器

URDF Parser(URDF解析器)是ROS(Robot Operating System,機器人操作系統)中用于解析URDF(Unified Robot Description Format,統一機器人描述格式)文件的工具。URDF是一種基于XML(Extensible Markup Language,可擴展標記語言)規范的格式,用于描述機器人的結構、關節、…

老式大頭顯示器(CRT)和當前最高分辨率的LED顯示器對比

老式 CRT&#xff08;陰極射線管&#xff09;和當前最頂尖的 LED&#xff08;包括 MicroLED / 高端 MiniLED / OLED&#xff09;顯示器在畫面清晰度極限相關的參數并列分析。1. 分辨率與像素密度指標老式 CRT&#xff08;PC/電視用&#xff09;頂級 LED 顯示器&#xff08;2025…

北京JAVA基礎面試30天打卡07

1. 緩存三大問題及解決方案問題場景后果常用解決方案緩存穿透請求的數據在緩存和數據庫中都不存在&#xff08;惡意攻擊或查詢異常 ID&#xff09;每次請求都會打到數據庫&#xff0c;導致 DB 壓力驟增- 緩存空值&#xff08;短期緩存不存在的 key&#xff09;- 布隆過濾器&…