Sword B树学习笔记二
源码
/* * Copyright (C) gtc. */ #ifndef _GTC_BTREE_H_INCLUDED_ #define _GTC_BTREE_H_INCLUDED_ #ifdef __cplusplus extern "C" { #endif typedef struct gtc_btree_s gtc_btree_t; typedef struct gtc_btree_node_s gtc_btree_node_t; struct gtc_btree_s { unsigned int order; gtc_btree_node_t* root; gtc_btree_node_t* cursor; }; struct gtc_btree_node_s { gtc_btree_node_t** childs; unsigned int* nkey; void** data; gtc_btree_node_t* parent; unsigned int capacity; unsigned int leaf; gtc_btree_node_t* next; }; gtc_btree_t* gtc_btree_create(unsigned int order); void* gtc_btree_find(gtc_btree_t* bt,unsigned int key); void gtc_btree_append(gtc_btree_t* bt,unsigned int key, void* data); void gtc_btree_remove(gtc_btree_t* bt,unsigned int key); void gtc_btree_dump(gtc_btree_t* bt); void gtc_btree_destory(gtc_btree_t* bt); #ifdef __cplusplus } #endif #endif /* _GTC_NLU_H_INCLUDED_ */
#include <gtc_btree.h> #include <stdio.h> #include <stdlib.h> #include <string.h> static gtc_btree_node_t* gtc_btree_create_node(gtc_btree_t* bt); static void gtc_btree_destory_node(gtc_btree_node_t* node); static int gtc_binary_search(unsigned int* arr, unsigned int n, unsigned int key); static int gtc_btree_search(gtc_btree_node_t* cur, unsigned int key, gtc_btree_node_t** node); static void gtc_btree_insert(gtc_btree_t* bt, gtc_btree_node_t* current, unsigned int key, void* data, gtc_btree_node_t* rc); static gtc_btree_node_t* gtc_btree_rotate(gtc_btree_node_t* current, unsigned int index); static int gtc_btree_borrow(gtc_btree_t* bt, gtc_btree_node_t* current); static void gtc_btree_merge(gtc_btree_t* bt, gtc_btree_node_t* current); static void gtc_btree_print(gtc_btree_node_t* node); static void gtc_btree_update_parent(gtc_btree_node_t* current); gtc_btree_t * gtc_btree_create(unsigned int order) { gtc_btree_t* bt; bt = calloc(1, sizeof(gtc_btree_t)); if (bt == NULL) { fprintf(stderr, "alloc failed\n"); return NULL; } bt->order = order; return bt; } void * gtc_btree_find(gtc_btree_t* bt, unsigned int key) { gtc_btree_node_t * node; int index; if (bt == NULL || bt->root == NULL) { return NULL; } // 查找节点 index = gtc_btree_search(bt->root, key, &node); if (index == -1) { return NULL; } return node->data[index]; } void gtc_btree_append(gtc_btree_t* bt, unsigned int key, void* data) { gtc_btree_node_t* node; int index; if (bt == NULL) { return; } // 1.判断B树是否是空树 if (bt->root == NULL) { node = gtc_btree_create_node(bt); if (node == NULL) { return; } node->nkey[0] = key; node->data[0] = data; node->capacity = 1; /* 设计说明: 对于根节点的理解比较特别, 当只存在一个根节点的时候,根节点也是叶子节点 */ node->leaf = 1; bt->root = node; return; } // 2.查找节点 index = gtc_btree_search(bt->root, key, &node); if (index >= 0) { // 找到节点,替换数据域 node->data[index] = data; return; } // 3.未找到节点,需要执行插入操作 gtc_btree_insert(bt, node, key, data, NULL); } void gtc_btree_remove(gtc_btree_t* bt, unsigned int key) { gtc_btree_node_t* node,* leaf; int index; if (bt == NULL || bt->root == NULL) { return; } // 2.查找节点 index = gtc_btree_search(bt->root, key, &node); if (index == -1) { // 未找到相关节点,无需操作 return; } //3. 旋转 leaf = gtc_btree_rotate(node, index); //4. 合并 gtc_btree_merge(bt, leaf); } static void gtc_btree_print(gtc_btree_node_t* node) { unsigned int i; printf("["); for (i = 0; i < node->capacity; i++) { if (!node->leaf) { if (i == 0) { // 遍历左孩子节点 gtc_btree_print(node->childs[i]); } // 遍历中间 printf("%u,", node->nkey[i]); // 遍历左孩子节点 gtc_btree_print(node->childs[i + 1]); } else { printf("%u,", node->nkey[i]); } } printf("]"); } void gtc_btree_dump(gtc_btree_t* bt) { // 先序遍历 gtc_btree_node_t* node; node = bt->root; if (node) { gtc_btree_print(node); } printf("\n"); } void gtc_btree_destory(gtc_btree_t* bt) { gtc_btree_node_t* node, * next; node = bt->cursor; while (node) { next = node->next; gtc_btree_destory_node(node); node = next; } free(bt); } static gtc_btree_node_t * gtc_btree_create_node(gtc_btree_t* bt) { gtc_btree_node_t* node; node = calloc(1, sizeof(gtc_btree_node_t)); if (node == NULL) { return NULL; } /* 设计说明: 关键字的数量应该是order-1,这里数组的大小为啥是order? 这里是为了在执行插入分裂的场景下不用额外分配一块内存用来计算中间关键字 */ // 创建关键字数组 node->nkey = calloc(bt->order, sizeof(unsigned int)); if (node->nkey == NULL) { goto failed; } // 创建关键字数据数组 node->data = calloc(bt->order, sizeof(void*)); if (node->data == NULL) { goto failed; } /* 关键字的左孩子节点是 node->childs[m] 关键字的右孩子节点是 node->childs[m+1] */ // 创建子节点数组 node->childs = calloc(bt->order + 1, sizeof(gtc_btree_node_t*)); if (node->childs == NULL) { goto failed; } if (bt->cursor == NULL) { bt->cursor = node; } else { node->next = bt->cursor; bt->cursor = node; } return node; failed: if (node->childs) { free(node->childs); node->childs = NULL; } if (node->data) { free(node->data); node->data = NULL; } if (node->nkey) { free(node->nkey); node->nkey = NULL; } if (node) { free(node); } return NULL; } static void gtc_btree_destory_node(gtc_btree_node_t* node) { if (node->childs) { free(node->childs); node->childs = NULL; } if (node->data) { free(node->data); node->data = NULL; } if (node->nkey) { free(node->nkey); node->nkey = NULL; } if (node) { free(node); } } static int gtc_btree_search(gtc_btree_node_t* current, unsigned int key, gtc_btree_node_t** node) { int index; gtc_btree_node_t* choose; index = gtc_binary_search(current->nkey, current->capacity, key); if (index >= 0 && current->nkey[index] == key) { *node = current; return index; } if (current->leaf) { // 叶子节点没有找到,则证明没有,需要插入当前数据 *node = current; return -1; } // 从left下标的右孩子继续查找 choose = current->childs[index + 1]; return gtc_btree_search(choose, key, node); } /* 设计说明 关于B树的插入操作,只能从叶子节点开始插入,不可能从内部节点插入 想要插入关键字,第一步先是查询,如果可以查到对应的关键字,执行的操作就是更新 如果查不到对应的关键字,那么当前节点肯定是叶子节点 对于增加一个元素,又分为两种情况 情况一:当前叶子节点上还有剩余关键字空间,那么按照关键字顺序插入关键字即可 情况二:当前叶子节点已经没有剩余关键字空间,此时需要对该叶子节点进行分裂 对于分裂现象可能发生在叶子节点,内部节点,根节点上, 我们先将需要插入的节点放到合适的位置上,然后再对关键字数组进行分裂 以数组的中间关键字作为分隔符,多分裂出一个右孩子节点, 将中间关键字上移到父节点上 对于中间关键字而言,左孩子就是原来的节点, 右孩子就是新分裂出来的节点 这里有个特殊情况需要考虑一下,如果需要分裂的节点是根节点, 此时中间关键字还有左孩子吗? 此时中间关键字的左孩子就是原来的根节点,右孩子就是分裂出来新节点 */ static int gtc_binary_search(unsigned int* arr, unsigned int n, unsigned int key) { int left, mid, right; left = -1; right = n; while (left + 1 != right) { mid = (left + right) / 2; if (arr[mid] <= key) { left = mid; } else if (arr[mid] > key) { right = mid; } } return left; } static void gtc_btree_update_parent(gtc_btree_node_t* current) { unsigned int i; if (current->capacity == 0 || current->leaf) { return; } for (i = 0; i < current->capacity + 1; i++) { current->childs[i]->parent = current; } } static void gtc_btree_insert(gtc_btree_t* bt, gtc_btree_node_t* current, unsigned int key, void* data, gtc_btree_node_t* rchild) { int mid; size_t size; unsigned int mkey; void* mdata; gtc_btree_node_t* child, * parent; unsigned int n, ci, index; // 计算插入位置 index = gtc_binary_search(current->nkey, current->capacity, key); /* 设计说明: 当初分配nkey,data内存空间的时候就多分配了一个, 即使nkey达到order-1也不会出现溢出 index表示小于key的最近的一个关键字,因此key的位置就是 index+1 */ index += 1; // 获取当前节点关键字最大下标 n = current->capacity - 1; ci = index + 1; if (index <= n) { size = (n - index + 1) * sizeof(unsigned int); memmove(¤t->nkey[index + 1], ¤t->nkey[index], size); size = (n - index + 1) * sizeof(void*); memmove(¤t->data[index + 1], ¤t->data[index], size); if (!current->leaf) { // 非叶子节点,需要插入右孩子节点 // 获取当前节点关键字最大下标 n = current->capacity + 1 - 1; size = (n - ci + 1) * sizeof(gtc_btree_node_t*); memmove(¤t->childs[ci + 1], ¤t->childs[ci], size); } } current->nkey[index] = key; current->data[index] = data; if (!current->leaf) { current->childs[ci] = rchild; } current->capacity += 1; // 2.判断当前节点是否溢出 if (current->capacity <= bt->order - 1) { // 存在空闲位置,不需要分裂 return; } // 3.分裂 // 3.1 获取中间关键字 mid = current->capacity / 2; mkey = current->nkey[mid]; mdata = current->data[mid]; // 3.2 旧节点拆分 child = gtc_btree_create_node(bt); if (child == NULL) { return; } // 获取当前节点关键字下标最大值 n = current->capacity - 1; size = (n - mid) * sizeof(unsigned int); memmove(&child->nkey[0], ¤t->nkey[mid + 1], size); size = (n - mid) * sizeof(void*); memmove(&child->data[0], ¤t->data[mid + 1], size); if (!current->leaf) { // 非叶子节点需要考虑孩子问题 // 获取当前节点孩子下标最大值 n = current->capacity + 1 - 1; index = mid + 1; size = (n - index + 1) * sizeof(gtc_btree_node_t*); memmove(&child->childs[0], ¤t->childs[index], size); } child->parent = current->parent; child->leaf = current->leaf; // 获取当前节点关键字下标最大值 n = current->capacity - 1; child->capacity = n - mid; current->capacity = mid - 0; // 需要更新孩子节点中父节点地址 gtc_btree_update_parent(child); if (current->parent == NULL) { // 说明是根节点分裂,需要额外创建根节点 parent = gtc_btree_create_node(bt); if (parent == NULL) { return; } parent->nkey[0] = mkey; parent->data[0] = mdata; parent->childs[0] = current; parent->childs[1] = child; parent->capacity = 1; // 需要更新孩子节点中父节点地址 gtc_btree_update_parent(parent); bt->root = parent; return; } gtc_btree_insert(bt, current->parent, mkey, mdata, child); } static gtc_btree_node_t * gtc_btree_rotate(gtc_btree_node_t* current, unsigned int index) { /* 设计说明: 删除节点,直接删除内部节点中的关键字比较复杂, 直接删除内部节点上的关键字,需要考虑删除关键字 对应的孩子节点怎么处理? 删除叶子节点就会简单很多。 旋转的目的是将借孩子节点上的关键字来填充本身缺失的关键字 借孩子关键字的技巧是哪个孩子的关键字比较多,就借哪个孩子的 左孩子是借最大的关键字,右孩子是借最小的关键字 */ gtc_btree_node_t* left, * right, * choose; unsigned int idx, n; size_t size; if (current->leaf) { // 已经旋转到叶子节点 // 获取当前数组的最大下标 n = current->capacity - 1; // 如果index是最后一个元素,只需要改变容量即可 if (index < n) { // 删除无效关键字 size = (n - index) * sizeof(unsigned int); memmove(¤t->nkey[index], ¤t->nkey[index + 1], size); size = (n - index) * sizeof(void*); memmove(¤t->data[index], ¤t->data[index + 1], size); } // 容量减一 current->capacity -= 1; return current; } left = current->childs[index]; right = current->childs[index + 1]; if (left->capacity > right->capacity) { // 获取左孩子节点数组的最大下标 idx = left->capacity - 1; choose = left; } else { // 获取右孩子节点数组的最小下标 idx = 0; choose = right; } current->nkey[index] = choose->nkey[idx]; current->data[index] = choose->data[idx]; /* 设计说明: 旋转不会涉及孩子节点索引的变化,只是单纯的交换关键字 */ return gtc_btree_rotate(choose, idx); } static int gtc_btree_borrow(gtc_btree_t* bt, gtc_btree_node_t* current) { gtc_btree_node_t* parent, * brother; size_t size; unsigned int n, key; int i, index; if (current->capacity == 0) { // 如果当前节点为空,那么一定触发合并 return -1; } /* 设计说明: 前提条件:current不是根节点 借的思路是尝试从兄弟节点借一个关键字 以current作为左孩子,那么从右兄弟中借第一个关键字 以current作为右孩子,那么从左兄弟中借最后一个关键字 如果左右兄弟都借不到,就需要进行兄弟间关键字合并 */ // 尝试从兄弟节点借关键字 parent = current->parent; /* 设计说明 本身尝试作为左孩子,从右兄弟借第一个关键字 为了确定右兄弟,需要以自身关键字的最大值去父节点中定位 自身关键字的最大值所能查询到的index加一, 就是当前节点在父节点中对应的关键字 gtc_btree_search()查询到的位置肯定比 (当前节点在父节点中对应的关键字)要小, 因为父节点肯定大于左孩子节点 */ n = current->capacity - 1; key = current->nkey[n]; i = gtc_binary_search(parent->nkey, parent->capacity, key); // 得到父节点对应关键字的下标 index = i + 1; if (index < parent->capacity) { // 获得右兄弟节点 brother = parent->childs[index + 1]; if (brother->capacity > (bt->order / 2 - 1)) { // 右兄弟有关键字可以借,进行左旋操作 current->nkey[current->capacity] = parent->nkey[index]; current->data[current->capacity] = parent->data[index]; if (!current->leaf) { // 非叶子节点,需要考虑兄弟节点的左子树 current->childs[current->capacity + 1] = brother->childs[0]; } current->capacity += 1; parent->nkey[index] = brother->nkey[0]; parent->data[index] = brother->data[0]; // 计算右兄弟的关键字最大下标 n = brother->capacity - 1; // 右兄弟节点关键字减一 size = (n - 0) * sizeof(unsigned int); memmove(&brother->nkey[0], &brother->nkey[1], size); size = (n - 0) * sizeof(void*); memmove(&brother->data[0], &brother->data[1], size); // 计算右兄弟的孩子节点字最大下标 n = (brother->capacity + 1) - 1; size = (n - 0) * sizeof(gtc_btree_node_t*); memmove(&brother->childs[0], &brother->childs[1], size); brother->capacity -= 1; return 0; } } /* 设计说明 本身尝试作为右孩子,从左兄弟借第一个关键字 为了确定左兄弟,需要以自身关键字的最小值去父节点中定位 自身关键字的最小值所能查询到的index减一, 就是当前节点在父节点中对应的关键字 gtc_btree_search()查询到的位置肯定比 (当前节点在父节点中对应的关键字)要大, 因为父节点肯定小于右孩子节点 作为右孩子为啥没有重新计算gtc_btree_search()? 因为作为左孩子已经计算了一遍,作为右孩子肯定是i-1,无需重新计算 */ // 计算父节点对应关键字的下标 index = i - 1; if (index > 0) { brother = parent->childs[index]; if (brother->capacity > (bt->order / 2 - 1)) { // 左兄弟有关键字可以借,进行右旋操作 // 计算左兄弟的关键字最大下标 n = brother->capacity - 1; size = (n) * sizeof(unsigned int); memmove(¤t->nkey[1], ¤t->nkey[0], size); size = (n) * sizeof(void*); memmove(¤t->data[1], ¤t->data[0], size); if (!current->leaf) { // 计算左兄弟的孩子最大下标 n = brother->capacity + 1 - 1; size = (n) * sizeof(gtc_btree_node_t*); memmove(¤t->childs[1], ¤t->childs[0], size); } current->nkey[0] = parent->nkey[index]; current->data[0] = parent->data[index]; if (!current->leaf) { // 非叶子节点,需要考虑左右兄弟节点的右子树 current->childs[0] = brother->childs[brother->capacity]; } current->capacity += 1; // 计算左兄弟的关键字最大下标 n = brother->capacity - 1; parent->nkey[index] = brother->nkey[n]; parent->data[index] = brother->data[n]; brother->nkey[n] = 0; brother->data[n] = NULL; brother->childs[n + 1] = NULL; brother->capacity -= 1; return 0; } } return -1; } static void gtc_btree_merge(gtc_btree_t* bt, gtc_btree_node_t* current) { gtc_btree_node_t* parent, * left, * right; size_t size; unsigned int n, index, i; //int i; /* 设计说明: 合并的思路是首先尝试从兄弟节点借一个关键字, 如果借不到,那么从父亲节点借一个关键字, 将彼此兄弟节点进行合并,递归处理父亲节点 */ if (current->capacity >= (bt->order / 2 - 1)) { // 自身关键字充足,不需要进行合并 return; } // 尝试从兄弟节点借关键字 parent = current->parent; if (parent == NULL) { // 根节点可以不满足 关键字数量大于 order/2 return; } // 尝试从兄弟节点中借关键字 if (gtc_btree_borrow(bt, current) == 0) { // 成功借到关键字 return; } /* 设计说明: 兄弟节点关键字都不富裕,那么需要进行兄弟节点合并 这里选左兄弟还是右兄弟呢? 其实没有区别,左右兄弟都借不到关键字,说明左右兄弟关键字个数都是 order/2-1 */ /* 设计说明: 特殊情况考虑,假如current此时已经空了, 如何确定current在父节点中的位置呢? 我的办法是遍历父节点孩子节点,找到和current相同的节点 */ for (i = 0; i < parent->capacity + 1; i++) { if (parent->childs[i] == current) { break; } } if (i == 0) { // current只能作为左孩子,需要和右兄弟合并 // 计算出父节点关键字下标 index = 0; left = current; right = parent->childs[i + 1]; } else { // current作为右孩子,需要和左兄弟合并 // 计算出父节点关键字下标 index = i - 1; left = parent->childs[i - 1]; right = current; } // 右孩子往左孩子合并 // 获取左孩子关键字下标最大值 n = left->capacity; // 从父节点借关键字 left->nkey[n] = parent->nkey[index]; left->data[n] = parent->data[index]; // left 孩子节点先不着急处理 left->capacity += 1; // 重新计算左孩子关键字下标最大值 n = left->capacity; // 合并右兄弟 size = (right->capacity) * sizeof(unsigned int); memmove(&left->nkey[n], &right->nkey[0], size); size = (right->capacity) * sizeof(void*); memmove(&left->data[n], &right->data[0], size); if (!left->leaf) { /* 设计说明: 左孩子的孩子数组下标n 对应的是右孩子的孩子数组下标0 */ size = (right->capacity) * sizeof(gtc_btree_node_t*); memmove(&left->childs[n], &right->childs[0], size); } left->capacity += right->capacity; // 处理父节点(需要删除一个关键字) n = parent->capacity - 1; if (index < n) { size = (n - index) * sizeof(unsigned int); memmove(&parent->nkey[index], &parent->nkey[index + 1], size); size = (n - index) * sizeof(void*); memmove(&parent->data[index], &parent->data[index + 1], size); n = parent->capacity + 1 - 1; size = (n - index) * sizeof(gtc_btree_node_t*); memmove(&parent->data[index], &parent->data[index + 1], size); } // 如果被删除的关键字就是最后一个关键字,直接修改容量即可 parent->capacity -= 1; return gtc_btree_merge(bt, parent); }
#include <stdio.h> #include <stdlib.h> #include <string.h> #include<time.h> #include <gtc_btree.h> #define ARRAY_SIZE 20 void test() { // 创建5阶B树 gtc_btree_t* bt; const char* p = "hello world"; time_t ts; size_t i; unsigned int key; unsigned int nkey[ARRAY_SIZE] = { 497,932,771,669,155,348,963,584,837,754,666,965,764,887,63, 632,751,279,843,157 }; //生成随机数种子 srand((unsigned int)(time(&ts))); bt = gtc_btree_create(5); if (bt == NULL) { return; } // 插入关键字 for (i = 0; i < ARRAY_SIZE; i++) { key = (rand() % 1000); key = nkey[i]; printf("%u\t", key); gtc_btree_append(bt, key, (char*)p); } printf("\n"); // 打印B树 gtc_btree_dump(bt); // 删除关键字 965 key = 965; gtc_btree_remove(bt, key); // 打印B树 gtc_btree_dump(bt); // 删除关键字 963 key = 963; gtc_btree_remove(bt, key); // 打印B树 gtc_btree_dump(bt); // 销毁B树 gtc_btree_destory(bt); } int main(int argc, char* argv[]) { test(); return 0; }
小结
该版本还算是一个demo版本,B树本来就不应该在内存中使用,如果在内存中提升查询速度,红黑树效率应该比B树要高。
浙公网安备 33010602011771号