C高级 框架开发中红黑树结构
引言 -- 红黑树历史
红黑树是数据结构学习中一道卡. 底层库容器中必不可少的算法. 历经各种实战运用,性能有保障. 同样红黑树不好理解, 就算理解了, 代码也不好写.
就算写了, 工程库也难构建. 关于红黑树基础讲解推荐看下面博主的红黑树博文系列,感觉不错.
对于红黑树小背景简介摘抄如下:
红黑树(英语:Red–black tree)是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组。它是在1972年由鲁道夫·贝尔发明的,他称之为"对称二叉B树",它现代的名字是在Leo J. Guibas和Robert Sedgewick于1978年写的一篇论文中获得的。它是复杂的,但它的操作有着良好的最坏情况运行时间,并且在实践中是高效的:它可以在O(log n)时间内做查找,插入和删除,这里的n是树中元素的数目。
对于红黑树更加详细的历史参照下面资料.
红黑树 https://zh.wikipedia.org/wiki/%E7%BA%A2%E9%BB%91%E6%A0%91
本文重点介绍工程开发中, 红黑树工程基库的封装.直接用现成的最爽.
前言 -- 红黑树工程库源码
一言不合就上源码!
rbtree.h
#ifndef _H_RBTREE #define _H_RBTREE struct rbnode { unsigned long parent_color; struct rbnode * right; struct rbnode * left; }; typedef void * (* new_f)(void *); typedef int (* cmp_f)(const void *, const void *); typedef void (* die_f)(void *); typedef struct { struct rbnode * root; new_f new; cmp_f cmp; die_f die; } * rbtree_t; /* * 每个想使用红黑树的结构, 需要在头部插入下面宏. * 例如 : struct person { _HEAD_RBTREE; ... // 自定义信息 }; */ #define _HEAD_RBTREE struct rbnode __node /* * 创建一颗红黑树头结点 * new : 注册创建结点的函数 * cmp : 注册比较的函数 * die : 注册程序销毁函数 * : 返回创建好的红黑树结点 */ extern rbtree_t rb_new(new_f new, cmp_f cmp, die_f die); /* * 插入一个结点, 会插入 new(pack) * tree : 红黑树头结点 * pack : 待插入的结点当cmp(x, pack) 右结点 */ extern void rb_insert(rbtree_t tree, void * pack); /* * 删除能和pack匹配的结点 * tree : 红黑树结点 * pack : 当cmp(x, pack) 右结点 */ extern void rb_remove(rbtree_t tree, void * pack); /* * 得到红黑树中匹配的结点 * tree : 匹配的结点信息 * pack : 当前待匹配结点, cmp(x, pack)当右结点处理 */ extern void * rb_get(rbtree_t tree, void * pack); /* * 销毁这颗二叉树 * tree : 当前红黑树结点 */ extern void rb_die(rbtree_t tree); #endif /* _H_RBTREE */
rbtree.c
#include "rbtree.h" #include <stdio.h> #include <stdlib.h> #include <string.h> /* * 操作辅助宏, 得到红黑树中具体父结点, 颜色. 包括详细设置信息 * r : 头结点 * p : 父结点新值 * c : 当前颜色 */ #define rb_parent(r) ((struct rbnode *)((r)->parent_color & ~3)) #define rb_color(r) ((r)->parent_color & 1) #define rb_is_red(r) (!rb_color(r)) #define rb_is_black(r) rb_color(r) #define rb_set_black(r) (r)->parent_color |= 1 #define rb_set_red(r) (r)->parent_color &= ~1 static inline void rb_set_parent(struct rbnode * r, struct rbnode * p) { r->parent_color = (r->parent_color & 3) | (unsigned long)p; } static inline void rb_set_color(struct rbnode * r, int color) { r->parent_color = (r->parent_color & ~1) | (1 & color); } static inline int _rb_cmp(const void * ln, const void * rn) { return (const char *)ln - (const char *)rn; } /* * 创建一颗红黑树头结点 * new : 注册创建结点的函数 * cmp : 注册比较的函数 * die : 注册程序销毁函数 * : 返回创建好的红黑树结点 */ rbtree_t rb_new(new_f new, cmp_f cmp, die_f die) { rbtree_t tree = malloc(sizeof(*tree)); if(NULL == tree) { fprintf(stderr, "rb_new malloc is error!"); return NULL; } tree->root = NULL; tree->new = new; tree->cmp = cmp ? cmp : _rb_cmp; tree->die = die; return tree; } static inline struct rbnode * _rb_new(rbtree_t tree, void * pack) { struct rbnode * node = tree->new ? tree->new(pack) : pack; memset(node, 0, sizeof(struct rbnode)); return node; } /* * 对红黑树的节点(x)进行左旋转 * * 左旋示意图(对节点x进行左旋): * px px * / / * x y * / \ --(左旋)--> / \ # * lx y x ry * / \ / \ * ly ry lx ly * */ static void _rbtree_left_rotate(rbtree_t tree, struct rbnode * x) { // 设置x的右孩子为y struct rbnode * y = x->right; struct rbnode * xparent = rb_parent(x); // 将 “y的左孩子” 设为 “x的右孩子”; x->right = y->left; // 如果y的左孩子非空,将 “x” 设为 “y的左孩子的父亲” if (y->left != NULL) rb_set_parent(y->left, x); // 将 “x的父亲” 设为 “y的父亲” rb_set_parent(y, xparent); if (xparent == NULL) tree->root = y; // 如果 “x的父亲” 是空节点,则将y设为根节点 else { if (xparent->left == x) xparent->left = y; // 如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子” else xparent->right = y; // 如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子” } // 将 “x” 设为 “y的左孩子” y->left = x; // 将 “x的父节点” 设为 “y” rb_set_parent(x, y); } /* * 对红黑树的节点(y)进行右旋转 * * 右旋示意图(对节点y进行左旋): * py py * / / * y x * / \ --(右旋)--> / \ # * x ry lx y * / \ / \ # * lx rx rx ry * */ static void _rbtree_right_rotate(rbtree_t tree, struct rbnode * y) { // 设置x是当前节点的左孩子。 struct rbnode * x = y->left; struct rbnode * yparent = rb_parent(y); // 将 “x的右孩子” 设为 “y的左孩子”; y->left = x->right; // 如果"x的右孩子"不为空的话,将 “y” 设为 “x的右孩子的父亲” if (x->right != NULL) rb_set_parent(x->right, y); // 将 “y的父亲” 设为 “x的父亲” rb_set_parent(x, yparent); if (yparent == NULL) tree->root = x; // 如果 “y的父亲” 是空节点,则将x设为根节点 else { if (y == yparent->right) yparent->right = x; // 如果 y是它父节点的右孩子,则将x设为“y的父节点的右孩子” else yparent->left = x; // (y是它父节点的左孩子) 将x设为“x的父节点的左孩子” } // 将 “y” 设为 “x的右孩子” x->right = y; // 将 “y的父节点” 设为 “x” rb_set_parent(y, x); } /* * 红黑树插入修正函数 * * 在向红黑树中插入节点之后(失去平衡),再调用该函数; * 目的是将它重新塑造成一颗红黑树。 * * 参数说明: * tree 红黑树的根 * node 插入的结点 // 对应《算法导论》中的z */ static void _rbtree_insert_fixup(rbtree_t tree, struct rbnode * node) { struct rbnode * parent, * gparent, * uncle; // 若“父节点存在,并且父节点的颜色是红色” while ((parent = rb_parent(node)) && rb_is_red(parent)) { gparent = rb_parent(parent); //若“父节点”是“祖父节点的左孩子” if (parent == gparent->left) { // Case 1条件:叔叔节点是红色 uncle = gparent->right; if (uncle && rb_is_red(uncle)) { rb_set_black(uncle); rb_set_black(parent); rb_set_red(gparent); node = gparent; continue; } // Case 2条件:叔叔是黑色,且当前节点是右孩子 if (parent->right == node) { _rbtree_left_rotate(tree, parent); uncle = parent; parent = node; node = uncle; } // Case 3条件:叔叔是黑色,且当前节点是左孩子。 rb_set_black(parent); rb_set_red(gparent); _rbtree_right_rotate(tree, gparent); } else { //若“z的父节点”是“z的祖父节点的右孩子” // Case 1条件:叔叔节点是红色 uncle = gparent->left; if (uncle && rb_is_red(uncle)) { rb_set_black(uncle); rb_set_black(parent); rb_set_red(gparent); node = gparent; continue; } // Case 2条件:叔叔是黑色,且当前节点是左孩子 if (parent->left == node) { _rbtree_right_rotate(tree, parent); uncle = parent; parent = node; node = uncle; } // Case 3条件:叔叔是黑色,且当前节点是右孩子。 rb_set_black(parent); rb_set_red(gparent); _rbtree_left_rotate(tree, gparent); } } // 将根节点设为黑色 rb_set_black(tree->root); } /* * 插入一个结点, 会插入 new(pack) * tree : 红黑树头结点 * pack : 待插入的结点当cmp(x, pack) 右结点 */ void rb_insert(rbtree_t tree, void * pack) { cmp_f cmp; struct rbnode * node, * x, * y; if((!tree) || (!pack) || !(node = _rb_new(tree, pack))) { fprintf(stderr, "rb_insert param is empty! tree = %p, pack = %p.\n", tree, pack); return; } cmp = tree->cmp; // 开始走插入工作 y = NULL; x = tree->root; // 1. 将红黑树当作一颗二叉查找树,将节点添加到二叉查找树中。从小到大 while (x != NULL) { y = x; if (cmp(x, node) > 0) x = x->left; else x = x->right; } rb_set_parent(node, y); if (y != NULL) { if (cmp(y, node) > 0) y->left = node; // 情况2:若“node所包含的值” < “y所包含的值”,则将node设为“y的左孩子” else y->right = node; // 情况3:(“node所包含的值” >= “y所包含的值”)将node设为“y的右孩子” } else tree->root = node; // 情况1:若y是空节点,则将node设为根 // 2. 设置节点的颜色为红色 rb_set_red(node); // 3. 将它重新修正为一颗二叉查找树 _rbtree_insert_fixup(tree, node); } /* * 红黑树删除修正函数 * * 在从红黑树中删除插入节点之后(红黑树失去平衡),再调用该函数; * 目的是将它重新塑造成一颗红黑树。 * * 参数说明: * tree 红黑树的根 * node 待修正的节点 */ static void _rbtree_delete_fixup(rbtree_t tree, struct rbnode * node, struct rbnode * parent) { struct rbnode * other; while ((!node || rb_is_black(node)) && node != tree->root) { if (parent->left == node) { other = parent->right; if (rb_is_red(other)) { // Case 1: x的兄弟w是红色的 rb_set_black(other); rb_set_red(parent); _rbtree_left_rotate(tree, parent); other = parent->right; } if ((!other->left || rb_is_black(other->left)) && (!other->right || rb_is_black(other->right))) { // Case 2: x的兄弟w是黑色,且w的俩个孩子也都是黑色的 rb_set_red(other); node = parent; parent = rb_parent(node); } else { if (!other->right || rb_is_black(other->right)) { // Case 3: x的兄弟w是黑色的,并且w的左孩子是红色,右孩子为黑色。 rb_set_black(other->left); rb_set_red(other); _rbtree_right_rotate(tree, other); other = parent->right; } // Case 4: x的兄弟w是黑色的;并且w的右孩子是红色的,左孩子任意颜色。 rb_set_color(other, rb_color(parent)); rb_set_black(parent); rb_set_black(other->right); _rbtree_left_rotate(tree, parent); node = tree->root; break; } } else { other = parent->left; if (rb_is_red(other)) { // Case 1: x的兄弟w是红色的 rb_set_black(other); rb_set_red(parent); _rbtree_right_rotate(tree, parent); other = parent->left; } if ((!other->left || rb_is_black(other->left)) && (!other->right || rb_is_black(other->right))) { // Case 2: x的兄弟w是黑色,且w的俩个孩子也都是黑色的 rb_set_red(other); node = parent; parent = rb_parent(node); } else { if (!other->left || rb_is_black(other->left)) { // Case 3: x的兄弟w是黑色的,并且w的左孩子是红色,右孩子为黑色。 rb_set_black(other->right); rb_set_red(other); _rbtree_left_rotate(tree, other); other = parent->left; } // Case 4: x的兄弟w是黑色的;并且w的右孩子是红色的,左孩子任意颜色。 rb_set_color(other, rb_color(parent)); rb_set_black(parent); rb_set_black(other->left); _rbtree_right_rotate(tree, parent); node = tree->root; break; } } } if (node) rb_set_black(node); } /* * 删除rb_get得到的结点 * root : 红黑树结点 * pack : 当cmp(x, pack) 右结点 */ void rb_remove(rbtree_t tree, void * pack) { struct rbnode * child, * parent, * node = NULL; int color; if ((!tree) || !(node = (struct rbnode *)pack)) { fprintf(stderr, "rb_remove check is error, tree = %p, node = %p.", tree, node); return; } // 被删除节点的"左右孩子都不为空"的情况。 if (NULL != node->left && node->right != NULL) { // 被删节点的后继节点。(称为"取代节点") // 用它来取代"被删节点"的位置,然后再将"被删节点"去掉。 struct rbnode * replace = node; // 获取后继节点 replace = replace->right; while (replace->left != NULL) replace = replace->left; // "node节点"不是根节点(只有根节点不存在父节点) if ((parent = rb_parent(node))) { if (parent->left == node) parent->left = replace; else parent->right = replace; } else // "node节点"是根节点,更新根节点。 tree->root = replace; // child是"取代节点"的右孩子,也是需要"调整的节点"。 // "取代节点"肯定不存在左孩子!因为它是一个后继节点。 child = replace->right; parent = rb_parent(replace); // 保存"取代节点"的颜色 color = rb_color(replace); // "被删除节点"是"它的后继节点的父节点" if (parent == node) parent = replace; else { // child不为空 if (child) rb_set_parent(child, parent); parent->left = child; replace->right = node->right; rb_set_parent(node->right, replace); } rb_set_parent(replace, rb_parent(node)); rb_set_color(replace, rb_color(node)); replace->left = node->left; rb_set_parent(node->left, replace); if (color) // 黑色结点重新调整关系 _rbtree_delete_fixup(tree, child, parent); // 结点销毁操作 if(tree->die) tree->die(node); return ; } if (node->left !=NULL) child = node->left; else child = node->right; parent = rb_parent(node); // 保存"取代节点"的颜色 color = rb_color(node); if (child) rb_set_parent(child, parent); // "node节点"不是根节点 if (parent) { if (parent->left == node) parent->left = child; else parent->right = child; } else tree->root = child; if (!color) _rbtree_delete_fixup(tree, child, parent); if(tree->die) tree->die(node); } /* * 得到红黑树中匹配的结点 * root : 匹配的结点信息 * pack : 当前待匹配结点, cmp(x, pack)当右结点处理 */ void * rb_get(rbtree_t tree, void * pack) { cmp_f cmp; struct rbnode * node; if((!tree) || !pack) { fprintf(stderr, "rb_get param is empty! tree = %p, pack = %p.\n", tree, pack); return NULL; } cmp = tree->cmp; node = tree->root; while(node) { int ct = cmp(node, pack); if(ct == 0) return node; node = ct > 0 ? node->left : node->right; } return NULL; } // 后序遍历删除操作 static void _rb_die(struct rbnode * root, die_f die) { if(NULL == root) return; _rb_die(root->left, die); _rb_die(root->right, die); die(root); } /* * 销毁这颗二叉树 * root : 当前红黑树结点 */ void rb_die(rbtree_t tree) { if(!tree || !tree->root || !tree->die) return; // 后续递归删除 _rb_die(tree->root, tree->die); // 销毁树本身内存 tree->root = NULL; free(tree); }
上面代码主要基于linux内核中红黑树扒下来构建的工程库. 有些细节我们简单解释一下结构. 例如
/* * 每个想使用红黑树的结构, 需要在头部插入下面宏. * 例如 : struct person { _HEAD_RBTREE; ... // 自定义信息 }; */ #define _HEAD_RBTREE struct rbnode __node
等同于'继承'用法, 放在没一个希望用在红黑树结构的头部. 这些都是从linux内核结构中学到的技巧. libuv框架中也常用这种技巧.
也是C开发中通用潜规则! 还有一个技巧, 如下
struct rbnode { unsigned long parent_color; struct rbnode * right; struct rbnode * left; }; #define rb_parent(r) ((struct rbnode *)((r)->parent_color & ~3)) #define rb_color(r) ((r)->parent_color & 1)
也是在看内核源码中学到的技巧, 将指针的后2位地址, 用于保存结点颜色. 为什么可行呢,
因为 struct rbnode 结构体内存是以 sizeof (unsigned long) 大小对齐. 那么该结构地址也是以 n*sizeof(unsigned long) 递增.
后两位都是0空出来的. 用于保存红黑树结点的颜色信息(RED | BLACK). 不得不佩服linux内核代码的精巧.
后面还有一个自己补充的技巧
typedef void * (* new_f)(void *); typedef int (* cmp_f)(const void *, const void *); typedef void (* die_f)(void *); typedef struct { struct rbnode * root; new_f new; cmp_f cmp; die_f die; } * rbtree_t;
实现注册, 创建, 比较, 销毁行为函数, 方便使用. 采用匿名结构, 也是一个C中开发一个小技巧, 这个结构只能是堆上创建. 对外可见, 但是不可构建.
后面会基于这个红黑树基础库, 构建一个简繁对照字典. 最后重申一下, 红黑树是软件开发层最后的堡垒. 数据结构算法也就到这了.
正文 -- 简单分析设计和测试
C的设计, 主要看结构. 同样C的难点也是结构. 后面我们做一个简单的简繁转换的字典, 通过C.
需要的资源见这个文件 https://files.cnblogs.com/files/life2refuel/C%E9%AB%98%E7%BA%A7%E5%B7%A5%E7%A8%8B%E4%B8%AD%E4%BD%BF%E7%94%A8%E7%BA%A2%E9%BB%91%E6%A0%91%E5%9F%BA%E5%BA%93.zip
简繁变换的词典,window上截图如下
采用的是ascii编码, 这里一个汉字2字节表示. 上传到linux上后, 采用utf-8编码, 一个中文3个字节. 需要小心!
词典主程序 main.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include "rbtree.h" #define _STR_PATH "常用汉字简繁对照表.txt" #define _INT_DICT (4) struct dict { _HEAD_RBTREE; char key[_INT_DICT]; char value[_INT_DICT]; }; // 需要注册的内容 static void * _dict_new(void * arg) { struct dict * node = malloc(sizeof(struct dict)); if (NULL == node) { fprintf(stderr, "_dict_new malloc is error!\n"); return NULL; } *node = *(struct dict *)arg; return node; } static inline int _dict_cmp(const void * ln , const void * rn) { return strcmp(((const struct dict *)ln)->key, ((const struct dict *)rn)->key); } static inline void _dict_die(void * arg) { free(arg); } // 创建内容 void dict_create(rbtree_t tree); // 得到内容 const char * dict_get(rbtree_t tree, const char * key); /* * 这里测试字典数据, 通过红黑树库 */ int main(int argc, char * argv[]) { // 创建字典树, 再读取内容 rbtree_t tree = rb_new(_dict_new, _dict_cmp, _dict_die); if (NULL == tree) { fprintf(stderr, "main rb_new rb is error!\n"); return -1; } // 为tree填充字典数据 dict_create(tree); // 我们输出一下 '你好' printf("你好吗 -> %s%s%s\n", dict_get(tree, "你"), dict_get(tree, "好"), dict_get(tree, "吗") ); // 字典书删除 rb_die(tree); getchar(); return 0; } // 创建内容 void dict_create(rbtree_t tree) { char c; struct dict kv; // 打开文件内容 FILE * txt = fopen(_STR_PATH, "rb"); if (NULL == txt) { fprintf(stderr, "main fopen " _STR_PATH " rb is error!\n"); return; } while ((c = fgetc(txt))!=EOF) { memset(&kv, 0, sizeof kv); // 读取这一行key, 并设值 kv.key[0] = c; kv.key[1] = fgetc(txt); // 去掉\\t c = fgetc(txt); if(c < 0) { kv.key[2] = c; fgetc(txt); } // 再设置value kv.value[0] = fgetc(txt); kv.value[1] = fgetc(txt); c = fgetc(txt);
if (c != '\r') {// 这些SB的代码, 都是解决不同系统版本的编码冲突的
kv.value[2] = c;
fgetc(txt);
} // 去掉\n fgetc(txt); // 插入数据 rb_insert(tree, &kv); } // 合法读取内容部分 fclose(txt); } // 得到内容 const char * dict_get(rbtree_t tree, const char * key) { struct dict kv; strncpy(kv.key, key, sizeof(kv.key) / sizeof(char)); struct dict * pkv = rb_get(tree, &kv); return pkv ? pkv->value : NULL; }
先看 window上测试结果
上面关于 dict_create 关于配置文件解析, 采用最原始的编码字符数解析的.
linux上 测试过程如下
linux上测试结果很正常. 到这里, 红黑树基库demo演示完毕. 也许你觉得好复杂, 但是已经很简单了. 因为C程序一个要求就是,
你需要懂得实现. 才能运用流畅. 一切都是钻木取火, 自生自灭.
扯一点, C要是有那种万能数据结构 array 或者 table 那生产率预估会提升10倍. 写代码就和玩似的.
今天完工等价于C基础数据结构已经全线通工了. C的代码写的越多, 越发觉得喜欢就好!
后记 -- 一些客气话
错误是难免的, 欢迎指正交流提高.
回家 http://music.163.com/#/song?id=157336