项目设计
B树数据结构设计报告
1 软件结构设计
1.1 软件功能结构
本系统实现了完整的B树数据结构及其核心操作,功能结构如下:
B树管理系统
├── 创建与初始化模块
├── 数据操作模块
│ ├── 关键字插入
│ ├── 关键字删除
│ ├── 关键字查找
│ └── 关键字修改
├── 遍历展示模块
│ ├── 层序遍历
│ └── 结构打印
└── 辅助功能模块
├── 节点分裂
├── 节点合并
├── 兄弟节点借用
└── 内存管理
graph TB
%% 全局样式定义
classDef root fill:#5B8FB9,stroke:#2D6BA6,color:#FFF,stroke-width:1.5px,font-size:30px
classDef primary fill:#5B8FB9,stroke:#2D6BA6,color:#FFF,stroke-width:1.5px,font-size:30px
classDef secondary fill:#E5F6FF,stroke:#73A6FF,color:#333,stroke-width:1px,font-size:30px
classDef operation fill:#FFF,stroke:#5B8FB9,color:#333,stroke-width:1.5px,font-size:30px
%% 根节点
ROOT["B树操作系统"]:::root
%% 一级功能模块
ROOT --> INIT["1.构建与初始化"]:::primary
ROOT --> INSERT["2.关键字插入"]:::primary
ROOT --> SEARCH["3.关键字查找"]:::primary
ROOT --> DELETE["4.关键字删除"]:::primary
ROOT --> VISUAL["5.结构可视化"]:::primary
%% 构建与初始化分支
INIT --> INIT1["1.1 接收阶数m"]:::secondary
INIT --> INIT2["1.2 创建根节点"]:::secondary
INIT --> INIT3["1.3 初始化属性"]:::secondary
INIT3 --> INIT3a["• 关键字数组"]:::operation
INIT3 --> INIT3b["• 子节点指针"]:::operation
INIT3 --> INIT3c["• 叶节点标记"]:::operation
%% 插入操作分支
INSERT --> INS1["2.1 关键字插入"]:::secondary
INSERT --> INS2["2.2 平衡维护"]:::secondary
INS2 --> INS2a["2.2.1 节点分裂"]:::operation
INS2a --> INS2a1["检查节点满"]:::operation
INS2a --> INS2a2["中间键提升"]:::operation
INS2a --> INS2a3["子节点分裂"]:::operation
INS2 --> INS2b["2.2.2 递归插入"]:::operation
%% 查找操作分支
SEARCH --> SRCH1["3.1 二分查找"]:::secondary
SEARCH --> SRCH2["3.2 递归查找"]:::secondary
SEARCH --> SRCH3["3.3 返回结果"]:::secondary
SRCH3 --> SRCH3a["存在状态"]:::operation
SRCH3 --> SRCH3b["位置信息"]:::operation
%% 删除操作分支
DELETE --> DEL1["4.1 关键字删除"]:::secondary
DELETE --> DEL2["4.2 平衡维护"]:::secondary
DEL2 --> DEL2a["4.2.1 借位操作"]:::operation
DEL2a --> DEL2a1["从左兄弟借"]:::operation
DEL2a --> DEL2a2["从右兄弟借"]:::operation
DEL2 --> DEL2b["4.2.2 节点合并"]:::operation
DEL2 --> DEL2c["4.2.3 递归删除"]:::operation
%% 可视化分支
VISUAL --> VIS1["5.1 层序遍历"]:::secondary
VIS1 --> VIS1a["使用队列实现"]:::operation
VIS1 --> VIS1b["打印每层节点"]:::operation
VISUAL --> VIS2["5.2 结构验证"]:::secondary
VIS2 --> VIS2a["检查平衡性"]:::operation
VIS2 --> VIS2b["验证关键字分布"]:::operation
%% 连接线样式
linkStyle default stroke:#5B8FB9,stroke-width:1.5px
linkStyle 0,1,2,3,4 stroke:#2565AE,stroke-width:2px
1.2 功能模块说明
1.2.1 创建B树
- 根据用户输入的阶数m动态创建B树
- 初始化根节点为叶子节点
- 验证阶数有效性(m ≥ 2)
BTree* init_btree(int m) {
if (m < 2) {
printf("阶数必须大于等于2\n");
return NULL;
}
BTree *tree = (BTree*)malloc(sizeof(BTree));
tree->m = m;
tree->root = create_node(m, true);
return tree;
}
1.2.2 关键字插入
- 检查关键字是否已存在
- 处理根节点分裂情况
- 递归插入并维护B树平衡性
- 自动处理节点分裂
void insert(BTree* tree, int key) {
if (search(tree->root, key)) {
printf("关键字 %d 已存在\n", key);
return;
}
// 根节点分裂处理
if (root->key_num == 2 * m - 1) {
BTreeNode* new_root = create_node(m, false);
new_root->children[0] = root;
tree->root = new_root;
split_child(new_root, 0);
// ...
}
// 递归插入
insert_non_full(root, key);
}
1.2.3 关键字删除
- 处理6种删除情况:
- 关键字在叶子节点
- 关键字在内部节点(左子节点足够)
- 关键字在内部节点(右子节点足够)
- 关键字在内部节点(子节点合并)
- 从左兄弟借关键字
- 从右兄弟借关键字
- 自动处理节点合并和关键字借用
- 更新根节点状态
void delete_key_from_tree(BTree* tree, int key) {
// 验证输入
if (!tree || !tree->root) return;
// 递归删除
delete_key(tree->root, key);
// 根节点更新
if (tree->root->key_num == 0) {
BTreeNode* old_root = tree->root;
tree->root = old_root->leaf ? NULL : old_root->children[0];
free_node(old_root);
}
}
1.2.4 关键字查找
- 递归实现高效查找
- 时间复杂度O(log n)
- 支持大规模数据检索
bool search(BTreeNode* root, int key) {
if (!root) return false;
int idx = find_key(root, key);
if (idx < root->key_num && key == root->keys[idx])
return true;
return root->leaf ? false : search(root->children[idx], key);
}
1.2.5 关键字修改
- 基于删除和插入实现
- 先删除旧关键字
- 再插入新关键字
- 自动维护树平衡
void modify(BTree* tree, int old_key, int new_key) {
if (search(tree->root, old_key)) {
delete_key_from_tree(tree, old_key);
insert(tree, new_key);
} else {
printf("旧关键字 %d 不存在\n", old_key);
}
}
1.2.6 层序遍历
- 使用队列实现层级遍历
- 直观展示B树结构
- 清晰显示节点层级关系
void print_tree_level_order(BTree *tree) {
Queue *q = create_queue();
enqueue(q, tree->root, 0);
while (!is_queue_empty(q)) {
QueueNode *qn = dequeue(q);
// 处理节点打印
// ...
// 子节点入队
if (!node->leaf) {
for (int i = 0; i <= node->key_num; i++) {
enqueue(q, node->children[i], level + 1);
}
}
}
}
1.2.7 构造队列
- 实现队列数据结构
- 支持B树层序遍历
- 包含入队、出队操作
typedef struct QueueNode {
BTreeNode *tree_node;
int level;
struct QueueNode *next;
} QueueNode;
typedef struct {
QueueNode *front;
QueueNode *rear;
} Queue;
1.2.8 入队/出队操作
- 队列基本操作实现
- 支持层序遍历需求
void enqueue(Queue *q, BTreeNode *node, int level) {
QueueNode *qn = (QueueNode*)malloc(sizeof(QueueNode));
qn->tree_node = node;
qn->level = level;
qn->next = NULL;
if (q->rear == NULL) {
q->front = q->rear = qn;
} else {
q->rear->next = qn;
q->rear = qn;
}
}
QueueNode* dequeue(Queue *q) {
if (q->front == NULL) return NULL;
QueueNode *temp = q->front;
q->front = q->front->next;
return temp;
}
2 数据结构设计
2.1 B树节点结构
typedef struct BTreeNode {
int *keys; // 动态关键字数组
struct BTreeNode **children; // 动态子节点指针数组
int key_num; // 当前关键字数量
bool leaf; // 是否为叶子节点
int m; // 节点阶数
} BTreeNode;
2.2 B树结构
typedef struct BTree {
BTreeNode *root; // 根节点指针
int m; // 树阶数
} BTree;
2.3 队列结构(用于层序遍历)
// 队列节点
typedef struct QueueNode {
BTreeNode *tree_node; // B树节点指针
int level; // 节点层级
struct QueueNode *next; // 下一个节点指针
} QueueNode;
// 队列结构
typedef struct {
QueueNode *front; // 队首指针
QueueNode *rear; // 队尾指针
} Queue;
3 关键算法设计
3.1 节点分裂算法
- 当节点关键字数达到2m-1时触发
- 将节点分裂为两个新节点
- 中间关键字提升到父节点
void split_child(BTreeNode* parent, int idx) {
int m = parent->m;
BTreeNode* child = parent->children[idx];
BTreeNode* new_node = create_node(m, child->leaf);
new_node->key_num = m - 1;
// 复制后半部分关键字
for (int j = 0; j < m - 1; j++) {
new_node->keys[j] = child->keys[j + m];
}
// 调整父节点
for (int j = parent->key_num; j > idx; j--) {
parent->children[j + 1] = parent->children[j];
}
parent->children[idx + 1] = new_node;
// ... (详细实现见完整代码)
}
3.2 节点合并算法
- 当节点关键字数不足时触发
- 合并相邻节点
- 父节点关键字下移
void merge(BTreeNode* node, int idx) {
int m = node->m;
BTreeNode* child = node->children[idx];
BTreeNode* sibling = node->children[idx + 1];
// 父节点关键字下移
child->keys[m - 1] = node->keys[idx];
// 合并兄弟节点
for (int i = 0; i < sibling->key_num; i++) {
child->keys[i + m] = sibling->keys[i];
}
// 调整父节点
for (int i = idx + 1; i < node->key_num; i++) {
node->keys[i - 1] = node->keys[i];
}
// ... (详细实现见完整代码)
}
3.3 关键字删除算法
处理六种删除情况:
- 关键字在叶子节点:直接删除
- 关键字在内部节点(左子节点足够):用前驱替换
- 关键字在内部节点(右子节点足够):用后继替换
- 关键字在内部节点(子节点需合并):合并后递归删除
- 从左兄弟借关键字:调整关键字分布
- 从右兄弟借关键字:调整关键字分布
void delete_key(BTreeNode* node, int key) {
int idx = find_key(node, key);
int m = node->m;
if (关键字在当前节点) {
if (叶子节点) {
// 情况1: 直接删除
} else {
if (左子节点足够) {
// 情况2a: 用前驱替换
} else if (右子节点足够) {
// 情况2b: 用后继替换
} else {
// 情况2c: 合并子节点
}
}
} else {
if (叶子节点) {
// 关键字不存在
} else {
if (子节点关键字不足) {
if (左兄弟足够) {
// 情况3a: 从左兄弟借
} else if (右兄弟足够) {
// 情况3b: 从右兄弟借
} else {
// 情况3c: 合并子节点
}
}
// 递归删除
delete_key(node->children[idx], key);
}
}
}
3.4 层序遍历算法
- 使用队列实现层级遍历
- 按层级打印节点内容
- 直观展示B树结构
void print_tree_level_order(BTree *tree) {
Queue *q = create_queue();
enqueue(q, tree->root, 0);
int current_level = 0;
printf("层级 %d: ", current_level);
while (!is_queue_empty(q)) {
QueueNode *qn = dequeue(q);
// 层级变化处理
if (qn->level != current_level) {
current_level = qn->level;
printf("\n层级 %d: ", current_level);
}
// 打印节点关键字
printf("[");
for (int i = 0; i < node->key_num; i++) {
printf("%d", node->keys[i]);
if (i < node->key_num - 1) printf(", ");
}
printf("] ");
// 子节点入队
if (!node->leaf) {
for (int i = 0; i <= node->key_num; i++) {
enqueue(q, node->children[i], level + 1);
}
}
}
printf("\n");
}
项目文档结构与内容指南
1. 问题描述与分析
1.1 总体要求描述
- 核心目标:实现功能完整的B树数据结构,满足外部存储环境中高效管理和检索数据的需求。
- 重要性:
- 解决传统二叉搜索树在海量数据场景下的I/O瓶颈问题。
- 广泛应用于数据库、文件系统等领域。
- 平衡性维护:通过分裂、合并、借位机制确保树平衡,保证操作的对数时间复杂度,减少磁盘I/O。
- 可扩展性与通用性:设计为模板类,支持存储整数、字符串等不同类型数据。
1.2 模块功能具体要求
1.2.1 B树构建与初始化
- 功能:用户指定B树阶数
m
,程序动态分配节点内存并初始化根节点为nullptr
。 - 输入:阶数
m
(整数)。 - 输出:初始化后的空B树结构。
1.2.2 关键字插入
- 功能:插入关键字
k
并维护平衡性,处理节点分裂及根节点分裂导致的树高增加。 - 输入:待插入关键字
k
(类型T
)。 - 输出:插入后的B树结构。
1.2.3 关键字查找
- 功能:高效查找关键字
k
,返回存在性及所在节点和索引(可选)。 - 输入:待查找关键字
k
(类型T
)。 - 输出:布尔值(是否存在)、节点指针和索引(可选)。
1.2.4 关键字删除
- 功能:删除关键字
k
,处理叶子节点删除、内部节点替换、借位、合并及根节点变化。 - 输入:待删除关键字
k
(类型T
)。 - 输出:删除后的B树结构。
1.2.5 B树结构展示
- 功能:通过层序遍历打印B树结构,展示各层节点关键字。
- 输入:无。
- 输出:控制台打印的层次结构信息。
1.3 模块划分表
模块名称 | 功能描述 |
---|---|
节点管理模块 | 实现节点创建、分裂、合并、借位等基础操作 |
插入控制模块 | 处理插入流程,维护B树平衡特性 |
删除控制模块 | 处理删除流程,确保节点关键字数符合最小要求 |
可视化模块 | 层序遍历输出树结构 |
内存管理模块 | 自动回收删除节点内存,防止内存泄漏 |
2. 数据结构方案选择与设计
2.1 节点结构设计
template <typename T>
struct BTreeNode {
T* keys; // 关键字数组(动态分配)
BTreeNode<T>** children; // 子节点指针数组(动态分配)
int n; // 当前关键字数量
bool leaf; // 叶子节点标记(true表示叶子节点)
int t; // 最小度数(m/2,用于判断节点状态)
};
选择理由:
- 动态数组:适应不同阶数
m
的需求,灵活扩展关键字和子节点数量。 - leaf标记:区分叶子节点与非叶子节点,简化插入/删除逻辑。
- 最小度数
t
:快速判断节点是否需要分裂、合并或借位。
2.2 树结构设计
template <typename T>
class BTree {
private:
BTreeNode<T>* root; // 根节点指针
int m; // B树阶数(每个节点最大子节点数)
// 私有成员函数(如插入、删除、分裂、合并等)
public:
// 公有接口(如插入、删除、查找、显示等)
};
设计优势:
- 封装性:隐藏节点操作细节,通过类接口对外提供服务。
- 集中管理:根节点由类统一维护,确保树结构的一致性。
- 可扩展性:模板类设计支持不同数据类型。
3. 软件结构设计
3.1 软件架构
- 设计思想:面向对象编程(OOP)。
- 核心组件:
BTree
类:主控制器,封装外部可见操作(插入、删除、查找、显示)。BTreeNode
结构体:内部辅助结构,由BTree
类管理,不直接对外暴露。
- 模块关系:
BTree
类通过私有成员函数操作BTreeNode
节点,实现递归遍历和节点调整。BTreeNode
的生命周期和逻辑完全由BTree
类控制。
3.2 算法设计说明
3.2.1 存储结构定义
BTreeNode
结构体:见2.1节代码定义,keys
和children
为动态分配数组。
3.2.2 核心算法描述
查找(Search)算法
- 流程:
- 从根节点开始,在当前节点关键字数组中查找目标关键字
k
(线性或二分查找)。 - 若找到
k
,返回成功;若当前节点为叶子节点且未找到,返回失败。 - 否则,根据查找位置进入对应子节点递归查找。
- 从根节点开始,在当前节点关键字数组中查找目标关键字
- 输入:当前节点
x
、目标关键字k
、输出索引idx
。 - 输出:找到的节点指针(或
nullptr
)。
插入(Insert)算法
- 流程:
- 递归查找插入位置,若子节点满则先分裂,将中间关键字提升到父节点。
- 到达叶子节点后,插入关键字并保持有序。
- 若根节点满,创建新根节点,分裂旧根节点,树高增加。
- 输入:待插入关键字
k
。 - 输出:更新后的B树结构。
删除(Remove)算法
- 流程:
- 定位关键字
k
,分情况处理:- 叶子节点删除:直接删除,不足时借位或合并。
- 内部节点删除:用前驱/后继替换,递归删除替换节点。
- 递归回溯时处理节点关键字不足问题(借位或合并)。
- 若根节点变空,更新根节点。
- 定位关键字
- 输入:待删除关键字
k
。 - 输出:更新后的B树结构。
节点分裂(SplitChild)算法
- 流程:
- 将满子节点
y
分裂为y
和z
,y
保留前半部分,z
接收后半部分。 y
的中间关键字提升到父节点x
,父节点增加子节点指针z
。
- 将满子节点
- 输入:父节点
x
、子节点索引i
、满子节点y
。 - 输出:父节点
x
和新节点z
被修改。
层序遍历(Display)算法
- 流程:
- 使用队列实现广度优先遍历,根节点入队。
- 循环处理队列中的节点,打印当前层关键字,并将子节点入队。
- 输入:无(通过根节点访问)。
- 输出:控制台打印的层次结构。
3.3 系统架构图
graph TD
A[用户接口] --> B[插入模块]
A --> C[删除模块]
A --> D[搜索模块]
A --> E[显示模块]
B --> F[节点管理模块]
C --> F
D --> F
E --> F
3.4 模块关系说明
- 插入模块:依赖节点分裂(
splitChild
)维护节点约束。 - 删除模块:依赖节点合并(
merge
)和借位操作保持平衡。 - 搜索模块:递归遍历节点关键字数组。
- 显示模块:使用队列实现层序遍历,无需修改树结构。
4. 算法设计
4.1 核心算法流程图
4.1.1 插入算法流程图
graph TD
Start[开始插入] --> CheckRoot{根节点满?}
CheckRoot -- 是 --> SplitRoot[分裂根节点]
CheckRoot -- 否 --> InsertNonFull[非满节点插入]
SplitRoot --> InsertNonFull
InsertNonFull --> FindPos[查找插入位置]
FindPos --> CheckChild{子节点满?}
CheckChild -- 是 --> SplitChild[分裂子节点]
CheckChild -- 否 --> Recurse[递归插入叶子节点]
SplitChild --> AdjustParent[调整父节点关键字和子节点]
AdjustParent --> Recurse
4.1.2 删除算法流程图
graph TD
Start[开始删除] --> Locate{定位关键字k}
Locate -->|在叶子节点| LeafRemove[直接删除]
Locate -->|在内部节点| InternalRemove[前驱/后继替换]
LeafRemove --> CheckUnderflow{节点关键字不足?}
InternalRemove --> CheckUnderflow
CheckUnderflow -- 是 --> BorrowOrMerge[借位或合并]
BorrowOrMerge --> UpdateParent[更新父节点]
UpdateParent --> End[结束]
4.2 算法实现代码(示例)
4.2.1 节点分裂函数
template <typename T>
void BTree<T>::splitChild(BTreeNode<T>* x, int i) {
// 分裂满子节点x->children[i]
BTreeNode<T>* y = x->children[i]; // 待分裂的子节点
BTreeNode<T>* z = new BTreeNode<T>(y->t, y->leaf); // 创建新节点z
z->n = t - 1; // 新节点初始关键字数为t-1
// 复制y的后t-1个关键字到z
for (int j = 0; j < t - 1; j++) {
z->keys[j] = y->keys[j + t];
}
// 复制y的后t个子节点到z(若非叶子节点)
if (!y->leaf) {
for (int j = 0; j < t; j++) {
z->children[j] = y->children[j + t];
}
}
// 调整y的关键字数
y->n = t - 1;
// 在x中为z腾出位置
for (int j = x->n; j >= i + 1; j--) {
x->children[j + 1] = x->children[j];
}
x->children[i + 1] = z;
// 将y的中间关键字提升到x
for (int j = x->n - 1; j >= i; j--) {
x->keys[j + 1] = x->keys[j];
}
x->keys[i] = y->keys[t - 1];
x->n++;
}
4.2.2 删除函数(简化版)
template <typename T>
void BTree<T>::remove(BTreeNode<T>* x, T k) {
int i = 0;
while (i < x->n && k > x->keys[i]) {
i++;
}
if (i < x->n && x->keys[i] == k) {
// 情况1:k在当前节点
if (x->leaf) {
// 叶子节点直接删除
removeFromLeaf(x, i);
} else {
// 内部节点,用前驱或后继替换
removeFromInternal(x, i);
}
} else {
// 情况2:k在子节点中
if (x->leaf) {
// 不存在,直接返回
return;
}
bool underflow = (x->children[i]->n < t);
if (underflow) {
// 处理子节点关键字不足
fill(x, i);
}
// 递归删除
if (k < x->keys[i]) {
remove(x->children[i], k);
} else {
remove(x->children[i + 1], k);
}
}
}
4.3 算法复杂度分析
算法 | 时间复杂度 | 空间复杂度 | 说明 |
---|---|---|---|
查找(Search) | $O(\log_m N)$ | $O(1)$ | 树高为$\log_m N$,每次节点内查找为$O(m)$或$O(\log m)$ |
插入(Insert) | $O(\log_m N)$ | $O(1)$ | 递归查找+分裂操作均在树高路径上完成 |
删除(Delete) | $O(\log_m N)$ | $O(1)$ | 递归回溯处理借位/合并,复杂度与树高相关 |
空间复杂度 | $O(N)$ | — | 存储所有$N$个关键字,节点动态分配内存 |
优化说明:
- 标题层级:使用
##
、###
明确区分章节和子模块,逻辑层次清晰。 - 列表与表格:模块功能、复杂度分析等采用列表或表格呈现,信息结构化。
- 代码块:使用语法高亮区分C++代码,关键函数添加注释说明逻辑。
- 图表排版:流程图和架构图独立成块,与文字描述对应,增强可读性。
- 一致性:统一缩进(4空格)、命名规范(驼峰式)和符号使用(如
m
表示阶数)。