rbtree 设计

来源:互联网 发布:动漫网络黑名单 编辑:程序博客网 时间:2024/06/05 14:51
什么是树?
大街上到处都是,大伙别说没看过,除非你在沙漠中。
树大致可以分为根,茎,枝,叶。
大树枝会套小树枝,树枝上都有叶子。
西方人有个圣诞节,圣诞来了,圣庭老人在树上的每个枝丫上都挂了礼物,礼物上都写上一个小朋友的名字,让小朋友去树上摘。
问题来了,现在有两颗树,
A                                                                  B


A是身材高大的水杉,不管有多少礼物,总有一个高度能满足你。
B是不太高但枝繁叶茂的荔枝树,每个枝丫的高度都差不多。
选哪颗树呢,如果选择A,分在低枝上的小朋友很容易拿,高一点的就会抱怨了。
看来要果断点选择B了。
可是还有一个问题,如何才能让小朋友快速知道自己的礼物在哪个枝上呢?

这个问题直到1972年才被Rudolf Bayer这个家伙解决了,解决思路是这样的:

1.把所有枝丫都挂个颜色标签,要么是红的要么是黑的。
2.根枝丫是黑的。
3.每颗叶子都是黑色的。
4.红色枝丫的分枝或叶子是黑色的。
5.任一枝丫到其分枝的任何一个叶子路径中都经过相同的黑色支丫。
6.礼物挂在枝丫或者叶子上
7.按小朋友的出生年月排序挂上。

看问题看实质,这个解决方案的实质是解决了小朋友的公平性,圣诞老人要累点,每插入一个礼物,可能都要改变一下相邻节点的方位,没关系,圣诞老人可以请零时工哈,树枝挂断了也不用负责多好。

这样,我们的红黑树就应运而生了。

我们先来看看红黑树的定义,维基百科上是这么说的:
红黑树是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组。它是在1972年由鲁道夫·贝尔发明的,他称之为"对称二叉B树",它现代的名字是在 Leo J. Guibas 和 Robert Sedgewick 于1978年写的一篇论文中获得的。它是复杂的,但它的操作有着良好的最坏情况运行时间,并且在实践中是高效的: 它可以在O(log n)时间内做查找,插入和删除,这里的n是树中元素的数目。
红黑树具有以下的性质:
红黑树是每个节点都带有颜色属性的二叉查找树,颜色为红色或黑色。在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:
性质1. 节点是红色或黑色。
性质2. 根是黑色。
性质3. 所有叶子都是黑色(叶子是NIL节点)。
性质4. 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
性质5. 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。

实际开发中,如何定义一个红黑树呢?
我是这样定义的:

#define YUMEI_RBTREE_COLOR_BLACK  0#define YUMEI_RBTREE_COLOR_RED    1typedef struct yumei_rbtree_s yumei_rbtree_t;typedef struct yumei_rbtree_node_s yumei_rbtree_node_t;typedef struct yumei_rbtree_key_s yumei_rbtree_key_t;struct yumei_rbtree_key_s{int key[ 8 ];};struct yumei_rbtree_s{yumei_rbtree_node_t   *root;yumei_rbtree_node_t   *leaf;int                    count;yumei_mem_pool_t      *pool;};struct yumei_rbtree_node_s{yumei_rbtree_node_t *left;yumei_rbtree_node_t *right;yumei_rbtree_node_t *parent;int                  color;yumei_rbtree_key_t   key;long                 data[0];};#define yumei_rbtree_black( node ) \( node )->color = YUMEI_RBTREE_COLOR_BLACK #define yumei_rbtree_red( node ) \( node )->color = YUMEI_RBTREE_COLOR_RED#define yumei_rbtree_is_red( node ) \( node )->color#define yumei_rbtree_is_black( node ) \!( node )->color

yumei_rbtree_key_t 这个结构以现实遇到的为准,可以是你现基本类型。
为什么要定义一个leaf呢,其实它是所有末节点的叶子,为了能够在操作中有相同的操作方式,所以不用NULL,而指向同一个叶子。
初始函数
yumei_rbtree_t* yumei_rbtree_create(){yumei_rbtree_t         *tree;yumei_rbtree_node_t    *leaf;yumei_mem_pool_t       *pool;yumei_mem_buf_t        *buf;int                     size;#define YUMEI_RBTREE_POOL_BLOCK_SIZE 1024#define YUMEI_RBTREE_POOL_BLOCK_NUM  8pool = yumei_mem_pool_create( YUMEI_RBTREE_POOL_BLOCK_SIZE, YUMEI_RBTREE_POOL_BLOCK_NUM );if( !pool ){goto error;}size = sizeof( yumei_rbtree_t );buf = yumei_mem_buf_malloc( pool, size );if( !buf ){goto pool_error;}tree = buf->data;size = sizeof( yumei_rbtree_node_t );buf = yumei_mem_buf_malloc( pool, size );if( !buf ){goto pool_error;}leaf = buf->data;tree->root = tree->leaf = leaf;tree->pool = pool;tree->count = 0;yumei_rbtree_black( leaf );return tree;pool_error:yumei_mem_pool_free( pool );error:return 0;}

释放函数
#define YUMEI_RBTREE_ERROR  -1#define YUMEI_RBTREE_OK      0int yumei_rbtree_free( yumei_rbtree_t *tree ){yumei_mem_pool_t        *pool;if( !tree ){return YUMEI_RBTREE_ERROR;}pool = tree->pool;yumei_mem_pool_free( pool );return YUMEI_RBTREE_OK;}

插入函数:

#define YUMEI_RBTREE_PARAM_ERROR 2#define YUMEI_RBTREE_KEY_EXIST   3int yumei_rbtree_insert( yumei_rbtree_t *tree,  yumei_rbtree_key_t* key, long data ){yumei_rbtree_node_t         *node, *leaf, *root, *cur, **temp;yumei_rbtree_key_t          *nkey;yumei_mem_pool_t            *pool;yumei_mem_buf_t             *buf;int                          i;int                          flag;int                          size;if( !tree || !key ){return YUMEI_RBTREE_PARAM_ERROR;}leaf = tree->leaf;size = sizeof( yumei_rbtree_node_t );if( data ){size += sizeof( long );}pool = tree->pool;if( !pool ){return YUMEI_RBTREE_ERROR;}buf = yumei_mem_buf_malloc( pool, size );if( !buf ){return YUMEI_RBTREE_ERROR;}node = buf->data;nkey = &node->key;i = 8;while( i-- ){nkey->key[ i ] = key->key[ i ];}if( data ){node->data = data;}    node->left = node->right = leaf;if( !tree->count ){yumei_rbtree_black( node );node->parent = 0;tree->root = node;return YUMEI_RBTREE_OK;}cur = tree->root;while( 1 ){          for( i = 0; i < 8; ++i ){          flag = key->key[ i ] - cur->key->key[ i ];          if( flag < 0 ){          temp = &cur->left;          break;          }          else if( flag > 0 ){          temp = &cur->right;          break;          }          }          if( !flag ){          return YUMEI_RBTREE_KEY_EXIST;          }          if( leaf == *temp ){          break;          }          cur = *temp;}*temp = node;yumei_rbtree_red( node );root = tree->root;while( node != root ){if( node->parent == node->parent->parent->right ){cur = node->parent->parent->left;flag = yumei_rbtree_is_black( cur );if( flag ){if( node ==  node->parent->left ){node = node->parent;yumei_rbtree_right_rot( tree, node );}yumei_rbtree_black( node->parent );yumei_rbtree_red( node->parent->parent );yumei_rbtree_left_rot( tree, node>parent->parent );}else{yumei_rbtree_black( node->parent );yumei_rbtree_black( cur );yumei_rbtree_red( cur->parent );node = cur->parent;}}else{cur = node->parent->parent->right;flag = yumei_rbtree_is_black( cur );if( flag ){if( node == node->parent->right ){node = node->parent;yumei_rbtree_left_rot( tree, node );}yumei_rbtree_black( node->parent );yumei_rbtree_red( node->parent->parent );yumei_rbtree_right_rot( tree, node->parent->parent );}else{yumei_rbtree_black( node->parent );yumei_rbtree_black( cur );yumei_rbtree_red( cur->parent );node = cur->parent;}}flag = yumei_rbtree_is_black( node->parent );if( flag ){break;}}yumei_rbtree_black( root );return YUMEI_RBTREE_OK;}

旋转函数和查询就不贴上来了,有兴趣的同学可以练一练,把大学里学到的知识又回顾了一遍,一个红黑树就实现了,圣诞老人可以无忧矣。
大家可以看到我的比较函数是这样的:

 for( i = 0; i < 8; ++i ){          flag = key->key[ i ] - cur->key->key[ i ];          if( flag < 0 ){          temp = &cur->left;          break;          }          else if( flag > 0 ){          temp = &cur->right;          break;          } }

对于一个32字的key,比较函数可以有很多种,可以用字符串比较,也可以多字节一齐比较,这里用的是化零是整,转成int 再作比较。比如
以下面两个字符串:

A: 0x65 0x66 0x67 0x68 0x69 0x70 0x71 0x72B: 0x65 0x66 0x67 0x68 0x69 0x70 0x72 0x71

用字符串比较要比较7次才有结果,而用整型只需要2次,用长整型只需要一次,是不是节省了太多时间。



原创粉丝点击