2023-12-18 C语言实现一个最简陋的B-Tree


点击 <C 语言编程核心突破> 快速C语言入门


C语言实现一个最简陋的B-Tree

  • 前言
        • 要解决问题:
        • 想到的思路:
        • 其它的补充:
  • 一、C语言B-Tree
        • 基本架构:
  • 二、可视化
  • 总结


前言

要解决问题:

实现一个最简陋的B-Tree, 研究B-Tree的性质.

对于B树, 我是心向往之, 因为他是数据库的基石, 描述语言好像很容易理解, 但不造个轮子就不能彻底弄明白, 于是, 造个轮子.

想到的思路:

根据AI给的代码架子进行修改, 现在AI是个好东西, 虽说给的代码不一定靠谱, 但是debug一下, 还能深入了解, 总之是很有用.

其它的补充:

有一份C++ B-Tree, 是通过算法4的java代码移植的, 但是C++ 的内存管理教育了我, 太难整了, 于是一气之下, 全改为智能指针, 头疼的事就解决了. 也是很简陋的代码, 只有增查, 没有删改, 就暂时不提供了.


一、C语言B-Tree

基本架构:

为了适应不同的B-Tree节点, 通过宏BTREE_ORDER_SIZE 规定子节点的数量, 使用typedef int keyOfBTree;定义节点的key类型, 以适应不同需求.

BTreeNode的结构中, 对于值和子节点存储, 直接使用数组, 而不是指针, 好处是初始化的时候比较容易, free的时候也不容易出错, 毕竟都是数组, delete BTreeNode直接就完事了, 不用一个个的删除值, 省时间.

不好之处, 可能是自由度和空间利用度受限, 毕竟到最后叶子节点, 不管用不用子节点, 都要开辟子节点数组内存, 有一点点浪费.

打印节点内容以及释放树, 是用的递归, 毕竟这个用递归太容易了.

代码中最复杂的是分裂节点和向树中插入值, 需要慢慢体会, 多琢磨也不是太难.

至于删除节点和更改节点, 这里没有实现.

BTree.h头文件.

#ifndef BTREE_
#define BTREE_#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

对于B树, 如果形象的比喻, 就是拍平的二叉树, 并且是平衡二叉树, 每个节点可以容纳Nkey, 同时容纳N+1个子节点, 这是一条非常重要的性质, 同时, 节点存放的key是按顺序排列的, 子节点也是按照顺序排列的, 是完全有序的.

一般子节点数量是偶数.

// B树的阶数,决定每个节点的孩子数量
#define BTREE_ORDER_SIZE 6

为了泛型, 我们只能用比较函数指针进行比较, 毕竟C语言不可能重载操作符.

// 比较函数指针类型
typedef int (*cmpFuncPtr)(void *, void *);

修改keyOfBTree可以让BTree使用不同的key

// 定义B树的key类型, 利于泛型
typedef int keyOfBTree;// 打印函数指针类型
typedef void (*printFun)(keyOfBTree);

B树的节点构成决定了其性质, B树含有一个key的数组, 以及子节点指针数组, 同时因为不一定数组全部是满的, 必须有一个num值指示究竟含有多少个key, 以及有多少个子节点, 也就是num + 1.

// B树的节点结构
typedef struct BTreeNode
{keyOfBTree keys[BTREE_ORDER_SIZE - 1];      // 关键字数组struct BTreeNode *childs[BTREE_ORDER_SIZE]; // 孩子节点指针数组uint32_t num; // 当前节点中的关键字数量int is_leaf;  // 是否为叶子节点
} BTreeNode;typedef BTreeNode *BTree;

B树有一些必须接口, 也是不能再精简的接口包括节点创建, 查找索引, 在节点中插入值, 分裂节点, 在B树中插入值, 以及B树的释放. 打印B树是为了展示B树的结构, 在现实中, 一般是没有的.

// 创建节点
BTreeNode *createNode(int is_leaf);// 查找关键字在节点中的索引位置
int searchKeyIndex(BTreeNode *node, keyOfBTree key, cmpFuncPtr cmp);// 插入关键字到节点中的指定位置
void insertKey(BTreeNode *node, keyOfBTree key, cmpFuncPtr cmp);// 分裂一个满节点,将中间的关键字提升为父节点,并创建两个新的子节点
void splitNode(BTreeNode *parent, int child_index);// 在B树中插入关键字
void insert(BTreeNode **root, keyOfBTree key, cmpFuncPtr cmp);// 打印B树的关键字
void printBTree(BTreeNode *node, printFun printKey, int left, int *cnt);// 释放BTree
void freeBTree(BTreeNode **node);#endif

BTree.c实现.

#include "BTree.h"

创建节点很简单, 要给一个参数, 识别是不是叶子节点, 叶子节点不含任何子节点, 只含有值,

非叶子节点, 既有值又有子节点.

通过malloc分配内存, 初始化置零, 赋值是否为叶子节点.

// 创建节点
BTreeNode *createNode(int is_leaf)
{BTreeNode *node = (BTreeNode *)malloc(sizeof(BTreeNode));memset(node, 0, sizeof(BTreeNode));node->is_leaf = is_leaf;return node;
}

查找索引位置是B树的基本函数, 通过比较key和节点内部key数组中的值确定索引位置.

比如值是5, 节点内值数组是{1,3,8}, 用5和它们比较, 索引从0开始, 如果5大于1, 索引增加1, 大于3, 又增加1, 所以最终的索引值是2,

这个索引值非常重要, 通过它, 才能找到正确的子节点, 一步一步的深入找到最终的子节点.

// 查找关键字在节点中的索引位置
int searchKeyIndex(BTreeNode *node, keyOfBTree key, cmpFuncPtr cmp)
{int index = 0;while (index < node->num && cmp(&key, &(node->keys[index])) > 0){index++;}return index;
}

这个插入函数是在确定了究竟要在哪个子节点插入值后使用的, 过程需要挪动数组中的元素.

// 插入关键字到节点中的指定位置
void insertKey(BTreeNode *node, keyOfBTree key, cmpFuncPtr cmp)
{int index = (int)node->num - 1;while (index >= 0 && cmp(&key, &(node->keys[index])) < 0){node->keys[index + 1] = node->keys[index];index--;}node->keys[index + 1] = key;node->num++;
}

分裂节点比较复杂, 为了理解, 需要阐述一下

  1. 分裂的是父节点的子节点, 所以传入的是父节点指针以及子节点索引.
  2. 过程中会创建一个与子节点同样性质, 也就是是否是叶子节点的节点.
  3. 如果要分裂的子节点是叶子节点, 就不会分裂子节点的子节点, 因为没有, 否则值数组和子节点指针数组要同时分裂.
  4. 分裂会把子节点的中间值提升给父节点, 比如满值是{1,2,3,4,5}, 那么就分裂为{1,2}{4,5}两个节点, 3提升给父节点接收.
  5. 被分裂的子节点的值数量num以及父节点的num都要被修改.
// 分裂一个满节点,将中间的关键字提升为父节点,并创建两个新的子节点
void splitNode(BTreeNode *parent, int child_index)
{BTreeNode *child = parent->childs[child_index];BTreeNode *new_node = createNode(child->is_leaf);new_node->num = BTREE_ORDER_SIZE / 2 - 1;for (int i = 0; i < new_node->num; i++){new_node->keys[i] = child->keys[BTREE_ORDER_SIZE / 2 + i];}if (!child->is_leaf){for (int i = 0; i < BTREE_ORDER_SIZE / 2; i++){new_node->childs[i] = child->childs[BTREE_ORDER_SIZE / 2 + i];}}child->num = BTREE_ORDER_SIZE / 2 - 1;for (int i = (int)parent->num; i > child_index; i--){parent->childs[i + 1] = parent->childs[i];}parent->childs[child_index + 1] = new_node;for (int i = (int)parent->num - 1; i >= child_index; i--){parent->keys[i + 1] = parent->keys[i];}parent->keys[child_index] = child->keys[BTREE_ORDER_SIZE / 2 - 1];parent->num++;
}

向B树插入值, 过程也比较复杂, 需要阐述:

  1. 由于可能分裂根节点, 所以传入的是根节点的二级指针, 保证不丢失节点.
  2. 分三种情况, 根节点为空, 这个最简单, 直接生成节点, 在此节点插入值, 令根节点指向它.
  3. 根节点已满, 必须分裂根节点, 而为了分裂根节点, 需要给根节点整个父节点, 然后再将root指针指向这个父节点, 并进行分裂.
  4. 根节点非空非满, 如果根节点是叶子节点, 直接插入, 如果不是叶子节点, 那就要取得索引, 看索引地址的子节点是否是满的, 是则分裂, 然后进入子节点循环插入, 不是满的, 则直接进入子节点循环.
  5. 大家可能看出来了, 最终都是插入到叶子节点.
// 在B树中插入关键字
void insert(BTreeNode **root, keyOfBTree key, cmpFuncPtr cmp)
{BTreeNode *node = *root;// 如果根节点为空,则创建新的根节点if (node == NULL){*root = createNode(1);insertKey(*root, key, cmp);return;}// 如果根节点已满,则需要创建一个新的根节点if (node->num == BTREE_ORDER_SIZE - 1){BTreeNode *new_root = createNode(0);new_root->childs[0] = node;*root = new_root;splitNode(new_root, 0);insert(root, key, cmp); // 递归插入return;}// 如果根节点既非空也未满,直接插入while (1){if (node->is_leaf){insertKey(node, key, cmp);break;}int index = searchKeyIndex(node, key, cmp);if (node->childs[index]->num == BTREE_ORDER_SIZE - 1){splitNode(node, index);if (cmp(&key, &(node->keys[index])) > 0){index++;}}node = node->childs[index];}
}

打印B树, 可视化, 有利于理解B树的插入规律.

// 打印B树的关键字
void printBTree(BTreeNode *node, printFun printKey, int left, int *cnt)
{if (node){printf("%c%.2d([", "ABCDEFG"[left++], ++*cnt);for (int i = 0; i < node->num; i++){printKey(node->keys[i]);}printf("]);\n");if (!node->is_leaf){int leftL = left - 1;int cntL = *cnt;for (int i = 0; i <= node->num; i++){printf("%c%.2d==>", "ABCDEFG"[leftL], cntL);printBTree(node->childs[i], printKey, left, cnt);}printf("\n");}}
}

释放B树, 传入节点的二级指针, 最终确保随后节点指针指向NULL, 使用递归, 因为节点内部都是数组和整型值, 没有需要特殊处理的元素, 递归删除整个节点指针即可.

// 释放BTree
void freeBTree(BTreeNode **node)
{if (*node){// 非叶子节点必有子节点, 递归删除子节点if (!(*node)->is_leaf){// 子节点的数量不会大于key数量加1, 所以不用free child数组中所有节点;for (int i = 0; i <= (*node)->num; i++){freeBTree(&((*node)->childs[i]));}}free(*node);*node = NULL;}
}

查找keyBTree中的位置.

对于一个set, 查找key的位置可能并不重要, 但是可以变通一下, 如果keyOfBTree是一个struct, 内部有一个key和一个value, cmp负责比较key, 那么我们则可以变相的将这个BTreeSet变成BTreeMap.

// 查找key在BTree中的位置
keyOfBTree *search(BTreeNode *root, keyOfBTree key, cmpFuncPtr cmp)
{// 如果root为空, 返回NULLif (!root){return NULL;}// 查找key在节点中的索引int index = searchKeyIndex(root, key, cmp);// 如果节点索引小于节点中key数量, 且key等于node在索引处的key值if (index < root->num && cmp(&key, &(root->keys[index])) == 0){// 返回key在node中的指针return &(root->keys[index]);}// 如果节点不是叶子节点, 递归搜索索引为index的子节点if (!root->is_leaf){return search(root->childs[index], key, cmp);}// 以上全没找到, 返回空指针return NULL;
}

测试用例, 向B树插入32个区间在0-999的整数值, 打印成mermaid文本, 可在markdown软件下图形化.

#include "BTree.h"
#include <stdlib.h>#define SIZE 32void printKey(keyOfBTree key)
{printf("%d\t", key);
}int cmpInt(const int *lhs, const int *rhs)
{return *lhs - *rhs;
}int main()
{int arr[SIZE];for (int i = 0; i != SIZE; ++i){arr[i] = rand() % 1000;}// 创建一个空的B树BTree root = NULL;// 依次插入关键字for (int j = 0; j != SIZE; ++j){insert(&root, arr[j], (cmpFuncPtr)cmpInt);printf("```mermaid\ngraph TD;\nsubgraph BTree\n");int cnt = 0;// 打印B树printBTree(root, printKey, 0, &cnt);printf("end\n```\n\n");}// 释放内存freeBTree(&root);return 0;
}

二、可视化

通过运行测试用例, 导出mermaid文本, 可以在markdown编辑器中实现可视化, 看随着输入, 树的分裂成长.

BTree
Insert
41
41
BTree
Insert
41 467
467
BTree
Insert
41 334 467
334
BTree
Insert
41 334 467 500
500
BTree
Insert
41 169 334 467 500
169
BTree
Insert
334
41 169
467 500 724
724
BTree
Insert
334
41 169
467 478 500 724
478
BTree
Insert
334
41 169
358 467 478 500 724
358
BTree
Insert
334 478
41 169
358 467
500 724 962
962
BTree
Insert
334 478
41 169
358 464 467
500 724 962
464
BTree
Insert
334 478
41 169
358 464 467
500 705 724 962
705
BTree
Insert
334 478
41 145 169
358 464 467
500 705 724 962
145
BTree
Insert
334 478
41 145 169 281
358 464 467
500 705 724 962
281
BTree
Insert
334 478
41 145 169 281
358 464 467
500 705 724 827 962
827
BTree
Insert
334 478 724
41 145 169 281
358 464 467
500 705
827 961 962
961
BTree
Insert
334 478 724
41 145 169 281
358 464 467
491 500 705
827 961 962
491
BTree
Insert
334 478 724
41 145 169 281
358 464 467
491 500 705
827 961 962 995
995
BTree
Insert
334 478 724
41 145 169 281
358 464 467
491 500 705
827 942 961 962 995
942
BTree
Insert
334 478 724 961
41 145 169 281
358 464 467
491 500 705
827 827 942
962 995
827
BTree
Insert
334 478 724 961
41 145 169 281
358 436 464 467
491 500 705
827 827 942
962 995
436
BTree
Insert
334 478 724 961
41 145 169 281
358 391 436 464 467
491 500 705
827 827 942
962 995
391
BTree
Insert
334 478 724 961
41 145 169 281
358 391 436 464 467
491 500 604 705
827 827 942
962 995
604
BTree
Insert
334 478 724 961
41 145 169 281
358 391 436 464 467
491 500 604 705
827 827 902 942
962 995
902
BTree
Insert
334 478 724 961
41 145 153 169 281
358 391 436 464 467
491 500 604 705
827 827 902 942
962 995
153
BTree
Insert
153 334 478 724 961
41 145
169 281 292
358 391 436 464 467
491 500 604 705
827 827 902 942
962 995
292
BTree
Insert
478
153 334 436
41 145
169 281 292
358 382 391
464 467
724 961
491 500 604 705
827 827 902 942
962 995
382
BTree
Insert
478
153 334 436
41 145
169 281 292
358 382 391 421
464 467
724 961
491 500 604 705
827 827 902 942
962 995
421
BTree
Insert
478
153 334 436
41 145
169 281 292
358 382 391 421
464 467
724 961
491 500 604 705 716
827 827 902 942
962 995
716
BTree
Insert
478
153 334 436
41 145
169 281 292
358 382 391 421
464 467
604 724 961
491 500
705 716 718
827 827 902 942
962 995
718
BTree
Insert
478
153 334 436
41 145
169 281 292
358 382 391 421
464 467
604 724 961
491 500
705 716 718
827 827 895 902 942
962 995
895
BTree
Insert
478
153 334 436
41 145
169 281 292
358 382 391 421
447 464 467
604 724 961
491 500
705 716 718
827 827 895 902 942
962 995
447
BTree
Insert
478
153 334 436
41 145
169 281 292
358 382 391 421
447 464 467
604 724 895 961
491 500
705 716 718
726 827 827
902 942
962 995
726

总结

通过以上的代码, 基本可以粗略了解B-Tree的性质,

就是树高增长缓慢,

单节点可以存储非常多的值,

查询速度优秀,

更贴近硬盘优化,

我们常见的数据库, mysql, sqlite, postgresql的基础都是B-Tree以及其变种, B+Tree,

了解底层, 期待更好的理解数据库, 在进行数据库设置时, 可以进行贴近底层的思考.


点击 <C 语言编程核心突破> 快速C语言入门


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/234140.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

云原生系列2-CICD持续集成部署-GitLab和Jenkins

1、CICD持续集成部署 传统软件开发流程&#xff1a; 1、项目经理分配模块开发任务给开发人员&#xff08;项目经理-开发&#xff09; 2、每个模块单独开发完毕&#xff08;开发&#xff09;&#xff0c;单元测试&#xff08;测试&#xff09; 3、开发完毕后&#xff0c;集成部…

3A服务器 (hcia)

原理 认证&#xff1a;验证用户是否可以获得网络访问权。 授权&#xff1a;授权用户可以使用哪些服务。 计费&#xff1a;记录用户使用网络资源的情况 实验 步骤 1.配置ip地址 2.配置认证服务器 aaa authentication-scheme datacom&#xff08;认证服务器名字&#xf…

2024 年 8 个顶级开源 LLM(大语言模型)

如果没有所谓的大型语言模型&#xff08;LLM&#xff09;&#xff0c;当前的生成式人工智能革命就不可能实现。LLM 基于 transformers&#xff08;一种强大的神经架构&#xff09;是用于建模和处理人类语言的 AI 系统。它们之所以被称为“大”&#xff0c;是因为它们有数亿甚至…

iPhone手机开启地震预警功能

iPhone手机开启地震预警功能 地震预警告警开启方式 地震预警 版权&#xff1a;成都高新减灾研究所 告警开启方式

CSS浮动

前置传统网页布局的三种方式&#xff1a; 标准流&#xff08;普通流/文档流&#xff09;&#xff1a; 浮动流&#xff1a; 定位流&#xff1a; 浮动: 实现元素在一行中向哪个方向排列 浮动后的元素还是可以设置边距的。 float默认是不会继承&#xff0c;但是可以强制设置flo…

ESP32WiFi(Blinker)-室内舒适度检测装置

一、硬件 ESP32 白色LED DHT11温湿度传感器 有源蜂鸣器 USB转串口&#xff08;只用到VCC,GND&#xff09; 面包板 二、软件 Arduino IDE版ESP32开发板 Blinker,apk 三、电路连接 const int LED18; LED控制管脚 const int BUZ2; 有源蜂鸣器VCC管脚 #define DHTPIN…

使用Matlab实现声音信号处理

利用Matlab软件对声音信号进行读取、放音、存储 先去下载一个声音文件&#xff1b;使用这个代码即可 clear; clc; [y, Fs] audioread(xxx.wav); plot(y); y y(:, 1); spectrogram(y); sound(y, Fs); % player audioplayer(y, Fs);y1 diff(y(:, 1)); subplot(2, 1, 1); pl…

美国第二大互联网供应商泄露3600万用户数据

12月18日&#xff0c;美国第二大互联网服务供应商Xfinity 透露&#xff0c;10月份发生的一起网络攻击泄露了多达3600万用户的敏感数据。 Xfinity由康卡斯特公司所属&#xff0c;为美国用户提供宽带互联网和有线电视等服务。 该公司表示&#xff0c;攻击是受Citrix Bleed的 CVE…

vue3挂载全局方法

比如某个js方法&#xff0c;项目很多地方都能用到&#xff0c;每次去重新写一遍太麻烦&#xff0c;放在一个js里面&#xff0c;每次去引入也懒得引&#xff0c;就可以挂载在全局上 1.创建tool.js文件&#xff0c;里面放常用的方法 const tools {getCurrentTim(){const curre…

基于PHP的蛋糕购物商城系统

有需要请加文章底部Q哦 可远程调试 基于PHP的蛋糕购物商城系统 一 介绍 此蛋糕购物商城基于原生PHP开发&#xff0c;数据库mysql&#xff0c;前端bootstrap。系统角色分为用户和管理员。 技术栈&#xff1a;phpmysqlbootstrapphpstudyvscode 二 功能 用户 1 注册/登录/注销…

08.queue 容器

8、queue 容器 概念&#xff1a; Queue 是一种先进先出&#xff08;First In First Out&#xff0c;FIFO&#xff09;的数据结构&#xff0c;他有两个出口 队列容器允许从一端新增元素&#xff0c;从另一端移除元素队列中只有队头和队尾才可以被外界使用&#xff0c;因此队列…

Oracle:JDBC链接Oracle的DEMO

1、引入jar包&#xff1a; 2、DEMO&#xff1a; package jdbc;import java.sql.*;public class OracleConnectionExample {public static void main(String[] args) throws SQLException {Connection conn null;PreparedStatement statement null;try {// Register JDBC dri…

基于Hadoop的农产品价格信息检测分析系统

基于Hadoop的农产品价格信息检测分析系统 前言数据处理模块1. 数据爬取2. 数据清洗与处理3. 数据存储 数据分析与检测模块1. 农产品价格趋势分析2. 农产品价格检索3. 不同市场价格对比 创新点 前言 为了更好地了解农产品市场价格趋势和不同市场之间的价格差异&#xff0c;我设…

Leetcode—151.反转字符串中的单词【中等】

2023每日刷题&#xff08;六十五&#xff09; Leetcode—151.反转字符串中的单词 实现代码 class Solution { public:string reverseWords(string s) {stringstream strs(s);string word;vector<string> res;while(strs >> word) {res.push_back(word);}reverse(…

springboot集成邮件发送的使用示例

springboot集成邮件发送的使用示例 步骤概述1. 引入依赖2. 配置邮件发送3. 发送简单邮件4. 发送 HTML 邮件5. 发送带附件的邮件6. 定时邮件发送 使用 Spring Boot 发送邮件时&#xff0c;通常的应用场景包括用户注册、密码重置和各种验证流程。这里我将展示一个以验证码发送为例…

Labview Vision 机器视觉使用,从下载程序安装应用,到实战找硬币并输出值

1.前言 大家好,今天我要和机器人一起配合来打算 做机器视觉 用Labview 和 Vision 联动实现机器的视觉 2.下载软件-软件的安装 我们除了基础款的labview软件 还要安装视觉四件套 1.Labview 编程平台&#xff08;我是 2023 q3&#xff09; 2. NI - IMAQdx &#xff08;驱动软…

为什么uboot/pmon会随机修改开发板的mac地址

U-Boot&#xff08;Universal Boot Loader&#xff09;在某些情况下可能会随机生成MAC地址&#xff0c;这主要是出于以下原因&#xff1a; 安全考虑&#xff1a; 为了增加设备的安全性&#xff0c;防止恶意用户通过MAC地址追踪或识别特定的设备&#xff0c;U-Boot可能会在每次…

Ubuntu 常用命令之 ifconfig 命令用法介绍

ifconfig 是一个用于配置和显示 Linux 内核中网络接口的系统管理命令。它用于配置&#xff0c;管理和查询 TCP/IP 网络接口参数。 ifconfig 命令的参数有很多&#xff0c;以下是一些常见的参数 up&#xff1a;激活指定的网络接口。down&#xff1a;关闭指定的网络接口。add&a…

Qnx wfd_be wfd_fe Android 通讯

在android 侧和 qnx 侧都指定mmid 使用habmm_socket_open 创建通讯channel 使用user_os_utils_send_recv完成消息的发送和接收 apps/qnx_ap/AMSS/multimedia/display/Hoya/wfd_be_qnx/src /* -----------------------------------------------------------------------------…

蓝桥杯常用库heapq

堆的常用方法 使用list表示一个堆 将无序List转换成最小堆;heapq.heapify(a) 最小堆a中添加元素x:heapq.heappush(a, x) 弹出并返回最小元素:heapq.heappop(a) 弹出并返回最小元素&#xff0c;同时添加元素x:heapq.heapreplace(a,x) import heapqa [11, 6, 9, 8, 7, 3] heapq.…