【转】C高级 框架开发中红黑树结构
0赞引言 -- 红黑树历史
红黑树是数据结构学习中一道卡. 底层库容器中必不可少的算法. 历经各种实战运用,性能有保障. 同样红黑树不好理解, 就算理解了, 代码也不好写.
就算写了, 工程库也难构建. 关于红黑树基础讲解推荐看下面博主的红黑树博文系列,感觉不错.
对于红黑树小背景简介摘抄如下:
红黑树(英语: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
View Code
上面代码主要基于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.
需要的资源见这个文件 http://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

