项目设计

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种删除情况:
    1. 关键字在叶子节点
    2. 关键字在内部节点(左子节点足够)
    3. 关键字在内部节点(右子节点足够)
    4. 关键字在内部节点(子节点合并)
    5. 从左兄弟借关键字
    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 关键字删除算法

处理六种删除情况:

  1. 关键字在叶子节点:直接删除
  2. 关键字在内部节点(左子节点足够):用前驱替换
  3. 关键字在内部节点(右子节点足够):用后继替换
  4. 关键字在内部节点(子节点需合并):合并后递归删除
  5. 从左兄弟借关键字:调整关键字分布
  6. 从右兄弟借关键字:调整关键字分布
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节代码定义,keyschildren为动态分配数组。

3.2.2 核心算法描述

查找(Search)算法
  • 流程
    1. 从根节点开始,在当前节点关键字数组中查找目标关键字k(线性或二分查找)。
    2. 若找到k,返回成功;若当前节点为叶子节点且未找到,返回失败。
    3. 否则,根据查找位置进入对应子节点递归查找。
  • 输入:当前节点x、目标关键字k、输出索引idx
  • 输出:找到的节点指针(或nullptr)。
插入(Insert)算法
  • 流程
    1. 递归查找插入位置,若子节点满则先分裂,将中间关键字提升到父节点。
    2. 到达叶子节点后,插入关键字并保持有序。
    3. 若根节点满,创建新根节点,分裂旧根节点,树高增加。
  • 输入:待插入关键字k
  • 输出:更新后的B树结构。
删除(Remove)算法
  • 流程
    1. 定位关键字k,分情况处理:
      • 叶子节点删除:直接删除,不足时借位或合并。
      • 内部节点删除:用前驱/后继替换,递归删除替换节点。
    2. 递归回溯时处理节点关键字不足问题(借位或合并)。
    3. 若根节点变空,更新根节点。
  • 输入:待删除关键字k
  • 输出:更新后的B树结构。
节点分裂(SplitChild)算法
  • 流程
    1. 将满子节点y分裂为yzy保留前半部分,z接收后半部分。
    2. y的中间关键字提升到父节点x,父节点增加子节点指针z
  • 输入:父节点x、子节点索引i、满子节点y
  • 输出:父节点x和新节点z被修改。
层序遍历(Display)算法
  • 流程
    1. 使用队列实现广度优先遍历,根节点入队。
    2. 循环处理队列中的节点,打印当前层关键字,并将子节点入队。
  • 输入:无(通过根节点访问)。
  • 输出:控制台打印的层次结构。

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$个关键字,节点动态分配内存

优化说明

  1. 标题层级:使用#####明确区分章节和子模块,逻辑层次清晰。
  2. 列表与表格:模块功能、复杂度分析等采用列表或表格呈现,信息结构化。
  3. 代码块:使用语法高亮区分C++代码,关键函数添加注释说明逻辑。
  4. 图表排版:流程图和架构图独立成块,与文字描述对应,增强可读性。
  5. 一致性:统一缩进(4空格)、命名规范(驼峰式)和符号使用(如m表示阶数)。
posted @ 2025-06-02 17:02  GJ504b  阅读(19)  评论(0)    收藏  举报