引言 - 初识 Size Balanced Tree

  最近在抽细碎的时间看和学习 random 的 randnet 小型网络库.

  iamrandom/randnet - https://github.com/iamrandom/randnet (1)

  了解到 陈启峰 2006-12-29 高中的时候写的论文 Size Balanced Tree(一种变种的 AVI 树) 感觉好精彩.

  就着手翻译成实战代码.

  陈启峰 Size Balanced Tree - https://files.cnblogs.com/files/life2refuel/%E9%99%88%E5%90%AF%E5%B3%B0%E3%80%8ASize-Balanced-Tree%E3%80%8B.pdf (2)

    翻译过程中前驱和后继参照了下面同行的代码

   二叉查找树的前驱后继 - http://www.cnblogs.com/Renyi-Fan/p/8252227.html (3)

: )  - , -

  推荐朋友学习的时候, 可以先看 (2) 和 (3) . 其中 (3) 最简单, 看完之后应该收获满满.

对于 (2) 还是深深佩服的, 毕竟一样大时候, 才刚脱离趣味玩泥巴. 大佬就可以证明算法的完备性.  如果你看到

(2) 证明部分,  这里不妨补充一些关于 f[h] 求证过程.

其实也就是高中二阶等比数列求证.

  那后面就开始扯了正题了  : ) 如何用 C 实现上面论文思路, 用于实战开发中.. 我这里抛砖砸高铁.  -:

前言 - 结构接口先行

  先看设计的数据结构

#ifndef _H_STREE
#define _H_STREE typedef unsigned long long stree_key_t; typedef union {
void * ptr;
double number;
signed long long i;
unsigned long long u;
} stree_value_t; struct stree {
struct stree * left; // 左子树
struct stree * right; // 右子树
unsigned size; // 树节点个数 stree_key_t key; // tree node key
stree_value_t value; // tree node value
}; typedef struct stree * stree_t; inline unsigned stree_size(const struct stree * const node) {
return node ? node->size : ;
} #endif//_H_STREE

Size Balanced Tree 中多了一个特色节点 unsigned size; 用户记录当前树上节点个数.  相比 red black tree

后者多记录了 父亲节点和红黑性质. 每种平衡搜索树, 都有自己的特殊字段. 看的出后续核心操作都是

围绕 unsigend size; 字段.

(对于 key 和 value 设计持开放态度. 其中 key 是必须的, 不过这里直接 unsigned long long 走起了)

首先看看最好理解的旋转部分(出道就是巅峰), 的 rotate 旋转操作

// stree_left_rotate - size tree left rotate
static void stree_left_rotate(stree_t * pode) {
stree_t node = *pode;
stree_t right = node->right; node->right = right->left;
right->left = node;
right->size = node->size;
node->size = stree_size(node->left) + stree_size(node->right) + ; *pode = right;
} // stree_right_rotate - size tree right rotate
//
// (y) left rotate (x)
// / \ ------------> / \
// (x) [C] [A] (y)
// / \ <------------ / \
// [A] [B] right rotate [B] [C]
//
static void stree_right_rotate(stree_t * pode) {
stree_t node = *pode;
stree_t left = node->left; node->left = left->right;
left->right = node;
left->size = node->size;
node->size = stree_size(node->left) + stree_size(node->right) + ; *pode = left;
}

强烈好好看看练习练习, 否则后面更加不懂.

  : ) 好想说, 后面不用看了, 复制我的代码. 如果用的时候用吧, 否则就别再见了.

正文 - 有时候要认命

当前核心是翻译论文, 并且终结 Size Balanced Tree 各种花式的代码.

细节部分看论文, 看作者原文思路.这里只是贴代码, 带你感受一下一个上的了台面的 Size Balanced Tree 实现  ~

stree.h

#ifndef _H_STREE
#define _H_STREE typedef unsigned long long stree_key_t; typedef union {
void * ptr;
double number;
signed long long i;
unsigned long long u;
} stree_value_t; struct stree {
struct stree * left; // 左子树
struct stree * right; // 右子树
unsigned size; // 树节点个数 stree_key_t key; // tree node key
stree_value_t value; // tree node value
}; typedef struct stree * stree_t; inline unsigned stree_size(const struct stree * const node) {
return node ? node->size : ;
} //
// stree_delete - Size Balanced Tree delete destroy
// poot : 指向 tree 对象指针
// return : void
//
extern void stree_delete(stree_t * poot); //
// stree_insert - size tree insert node
// poot : 指向 tree 对象指针
// key : 插入 node key
// value : 插入 node value
// return : void
//
extern void stree_insert(stree_t * poot, stree_key_t key, stree_value_t value); //
// stree_remove - size tree remove node
// poot : 指向 tree 对象指针
// key : 查找 node key
// return : void
//
extern void stree_remove(stree_t * poot, stree_key_t key); //
// stree_find - 寻找到指定的节点
// root : tree 对象
// key : 查找 node key
// return : 查找 tree node, NULL 表示没有
//
extern stree_t stree_find(stree_t root, stree_key_t key); //
// stree_rank - 返回 key 在 root 树中排名, 也就是比 key 小的那颗树的大小上加 1
// root : tree 对象
// key : 查找 node key
// return : 排名
//
extern unsigned stree_rank(stree_t root, stree_key_t key); //
// stree_select - root 根节点树种排名为 k 的节点
// root : tree 对象
// k : 排名 [1, stree_size(root)]
// return : 返回查询到节点
//
extern stree_t stree_select(stree_t root, unsigned k); //
// stree_pred - 查找 root 前驱节点, 比 key 小的最大的数
// root : tree 对象
// key : 查找 node key
// return : 返回查询到节点
//
extern stree_t stree_pred(stree_t root, stree_key_t key); //
// stree_succ - 查找 root 后继节点, 比 key 大的最小的数
// root : tree 对象
// key : 查找 node key
// return : 返回查询到节点
//
extern stree_t stree_succ(stree_t root, stree_key_t key); #endif//_H_STREE

  上面 delete 和 remove 单词和论文中比对有些不一样. 因为在 C / C++ 中 delete 语义是销毁.

所以采用 remove 去除的节点的语义单词. 应该更加贴合实际开发的代码函数定义.

stree.c

#include "stree.h"
#include <stdlib.h>
#include <stdbool.h> static void stree_free(stree_t root) {
if (root->left)
stree_free(root->left);
if (root->right)
stree_free(root->right);
free(root);
} //
// stree_delete - Size Balanced Tree delete destroy
// poot : 指向 tree 对象指针
// return : void
//
inline void
stree_delete(stree_t * poot) {
if (!poot || !*poot)
return;
stree_free(*poot);
*poot = NULL;
} // stree_left_rotate - size tree left rotate
static void stree_left_rotate(stree_t * pode) {
stree_t node = *pode;
stree_t right = node->right; node->right = right->left;
right->left = node;
right->size = node->size;
node->size = stree_size(node->left) + stree_size(node->right) + ; *pode = right;
} // stree_right_rotate - size tree right rotate
//
// (y) left rotate (x)
// / \ ------------> / \
// (x) [C] [A] (y)
// / \ <------------ / \
// [A] [B] right rotate [B] [C]
//
static void stree_right_rotate(stree_t * pode) {
stree_t node = *pode;
stree_t left = node->left; node->left = left->right;
left->right = node;
left->size = node->size;
node->size = stree_size(node->left) + stree_size(node->right) + ; *pode = left;
} // stree_maintain - Size Balanced Tree Maintain
static void stree_maintain(stree_t * pode, bool flag) {
unsigned size;
stree_t node = *pode;
if (!node) return; if (!flag) {
if (!node->left) return; size = stree_size(node->right);
if (size < stree_size(node->left->left))
stree_right_rotate(pode);
else if (size < stree_size(node->left->right)) {
stree_left_rotate(&node->left);
stree_right_rotate(pode);
} else return; stree_maintain(&node->left, false);
} else {
if (!node->right) return; size = stree_size(node->left);
if (size < stree_size(node->right->right))
stree_left_rotate(pode);
else if (size < stree_size(node->right->left)) {
stree_right_rotate(&node->right);
stree_left_rotate(pode);
} else return; stree_maintain(&node->right, true);
} stree_maintain(pode, false);
stree_maintain(pode, true);
} static void stree_insert_node(stree_t * poot, stree_t node) {
bool flag;
stree_t root = *poot;
if (!root) {
(*poot = node)->size = ;
return;
} ++root->size; // 插入到非空树, 节点数加 1
if ((flag = node->key < root->key))
stree_insert_node(&root->left, node);
else
stree_insert_node(&root->right, node);
stree_maintain(poot, !flag);
} //
// stree_insert - size tree insert node
// poot : 指向 tree 对象指针
// key : 插入 node key
// value : 插入 node value
// return : void
//
inline void
stree_insert(stree_t * poot, stree_key_t key, stree_value_t value) {
stree_t node = calloc(, sizeof(struct stree));
node->key = key;
node->value = value;
stree_insert_node(poot, node);
} static stree_t stree_remove_node(stree_t * pode, const stree_key_t key, bool find) {
stree_t record;
stree_t node = *pode;
if (!node) return NULL; if (find && !node->right) {
// the right end node of right child tree replace the delete node
*pode = node->left;
return node;
} if (!find && key == node->key) {
if (node->size == ) {
// leaf node, need delete
*pode = NULL;
return node;
}
if (node->size == ) {
// single branch node, need child node replace delete node
record = node->left ? node->left : node->right;
node->left = node->right = NULL;
} else {
// max key node of left tree replace pnode
record = stree_remove_node(&node->left, key, true);
}
if (record) {
node->key = record->key;
node->value = record->value;
}
--node->size;
stree_maintain(pode, true);
} else if (!find && key < node->key) {
record = stree_remove_node(&node->left, key, false);
if (record)
--node->size;
} else {
record = stree_remove_node(&node->right, key, find);
if (record)
--node->size;
} stree_maintain(pode, !find && key < node->key);
return record;
} //
// stree_remove - size tree remove node
// poot : 指向 tree 对象指针
// key : 查找 node key
// return : void
//
inline void
stree_remove(stree_t * poot, stree_key_t key) {
stree_t node;
if (!poot) return;
node = stree_remove_node(poot, key, false);
if (node) free(node);
} //
// stree_find - 寻找到指定的节点
// root : tree 对象
// key : 查找 node key
// return : 查找 tree node, NULL 表示没有
//
stree_t
stree_find(stree_t root, stree_key_t key) {
while (root) {
if (key < root->key)
root = root->left;
else if (root->key < key)
root = root->right;
else break;
}
return root;
} //
// stree_rank - 返回 key 在 root 树中排名, 也就是比 key 小的那颗树的大小上加 1
// root : tree 对象
// key : 查找 node key
// return : 排名
//
unsigned
stree_rank(stree_t root, stree_key_t key) {
int k = ;
while (root) {
if (key < root->key)
root = root->left;
else if (root->key < key) {
k += stree_size(root->left) + ;
root = root->right;
} else {
k += stree_size(root->left);
break;
}
}
return k;
} //
// stree_select - root 根节点树种排名为 k 的节点
// root : tree 对象
// k : 排名 [1, stree_size(root)]
// return : 返回查询到节点
//
stree_t
stree_select(stree_t root, unsigned k) {
while (root) {
unsigned size = stree_size(root->left);
if (k < size)
root = root->left;
else if (size < k) {
root = root->right;
k -= size + ;
} else break;
}
return root;
} /*
前驱节点
1. 若一个节点有左子树,那么该节点的前驱节点是其左子树中 key 值最大的节点
2. 若一个节点没有左子树,那么判断该节点和其父节点的关系
2.1 若该节点是其父节点的右孩子,那么该节点的前驱节点即为其父节点。
2.2 若该节点是其父节点的左孩子,那么需要沿着其父亲节点一直向树的顶端寻找,
直到找到一个节点P,P节点是其父节点Q的右孩子,那么Q就是该节点的后继节点 */ static stree_t stree_get_right(stree_t root) {
if (root) {
while (root->right)
root = root->right;
}
return root;
} static stree_t stree_get_right_p(stree_t root, stree_key_t key, stree_t * pq, stree_t * pr) {
while (root) {
if (key == root->key)
return root; *pq = root;
if (key < root->key)
root = root->left;
else {
*pr = root; // 出现右拐点, 父节点 Q 的右孩子
root = root->right;
}
}
return root;
} //
// stree_pred - 查找 root 前驱节点, 比 key 小的最大的数
// root : tree 对象
// key : 查找 node key
// return : 返回查询到节点
//
stree_t
stree_pred(stree_t root, stree_key_t key) {
if (root) {
stree_t q = NULL, r = NULL;
stree_t p = stree_get_right_p(root, key, &q, &r);
if (!p)
return NULL;
// 有左子树
if (p->left)
return stree_get_right(p->right); // 没有前驱节点的情况
if ((!p) || (p && !r))
return NULL; // 没有左子树, 其父节点的右节点
if (p == q->right)
return q;
// 没有左子树, 是其父节点的左节点
return r;
}
return root;
} /*
后继节点
1. 若一个节点有右子树,那么该节点的后继节点是其右子树中 key 值最小的节点
2. 若一个节点没有右子树,那么判断该节点和其父节点的关系
2.1 若该节点是其父节点的左孩子,那么该节点的后继结点即为其父节点
2.2 若该节点是其父节点的右孩子,那么需要沿着其父亲节点一直向树的顶端寻找,
直到找到一个节点P,P节点是其父节点Q的左孩子,那么Q就是该节点的后继节点
*/ static stree_t stree_get_left(stree_t root) {
if (root) {
while (root->left)
root = root->left;
}
return root;
} static stree_t stree_get_left_p(stree_t root, stree_key_t key, stree_t * pq, stree_t * pr) {
while (root) {
if (root->key == key)
return root; *pq = root; // 设置父亲节点
if (root->key < key)
root = root->right;
else {
*pr = root; // 出现左拐点, 父节点 Q 的左孩子
root = root->left;
}
}
return root;
} //
// stree_succ - 查找 root 后继节点, 比 key 大的最小的数
// root : tree 对象
// key : 查找 node key
// return : 返回查询到节点
//
stree_t
stree_succ(stree_t root, stree_key_t key) {
if (root) {
stree_t q = NULL, r = NULL;
stree_t p = stree_get_left_p(root, key, &q, &r);
if (!p)
return NULL;
// 有右子树
if (p->right)
return stree_get_right(p->right); // 没有前驱节点的情况
if ((!p) || (p && !r))
return NULL; // 没有右子树, 其父节点的左节点
if (p == q->left)
return q;
// 没有右子树, 是其父节点的右节点
return r;
}
return root;
}

  这里也简单写个测试例子, 保证核心逻辑插入和查找还有销毁可以用.

main.c

#include <stdio.h>
#include <stdlib.h> #include "stree.h" //
// Size Balanced Tree
//
int main(int argc, char * argv[]) {
stree_t root = NULL; // 插入数据
for (int i = ; i < ; i++)
stree_insert(&root, i, (stree_value_t) { .i = i }); int key = ;
stree_t node = stree_find(root, key);
if (NULL == node) {
fprintf(stderr, "stree_find is error key = %d\n", key);
exit(EXIT_FAILURE);
}
printf("key = %d, node = %lld\n", key, node->value.i); stree_remove(&root, key);
node = stree_find(root, key);
if (node) {
fprintf(stderr, "stree_find is error key = %d\n", key);
exit(EXIT_FAILURE);
}
printf("stree_remove is success key = %d\n", key); stree_delete(&root);
return ;
}

论文 + C 代码互相配合, 希望能对准备尝试的人有些作用.  长舒一口气.

抛开图, 面试最难不过二叉树 ~,~

开发中 tree set hash list vector 非常常见, 其中 tree 常被 hash 和 set 抢掉运用场景.  例如 id 管理.

但二叉树是批量搜索业务的基石 (别说跳表). 不管如何你都得尝试跃迁过 ~-~

后记 - 开心就好

  错误是难免的, 欢迎交流指正, 互相提升 ~

  負け犬達のレクイエム - http://music.163.com/m/song?id=29751658&userid=16529894

: )

故事的开头,总是惊鸿一瞥,一眼千年,故事的结尾,总是潇洒转身,渐行渐远

-:

C基础 - 终结 Size Balanced Tree的更多相关文章

  1. Size Balanced Tree(SBT) 模板

    首先是从二叉搜索树开始,一棵二叉搜索树的定义是: 1.这是一棵二叉树: 2.令x为二叉树中某个结点上表示的值,那么其左子树上所有结点的值都要不大于x,其右子树上所有结点的值都要不小于x. 由二叉搜索树 ...

  2. Size Balanced Tree

    Size Balanced Tree(SBT)是目前速度最快的平衡二叉搜索树,且能够进行多种搜索操作,区间操作:和AVL.红黑树.伸展树.Treap类似,SBT也是通过对节点的旋转来维持树的平衡,而相 ...

  3. Size Balanced Tree(SBT树)整理

    不想用treap和Splay,那就用SB树把,哈哈,其实它一点也SB,厉害着呢. 先膜拜一下作者陈启峰.Orz 以下内容由我搜集整理得来. 一.BST及其局限性 二叉查找树(Binary Search ...

  4. 初学 Size Balanced Tree(bzoj3224 tyvj1728 普通平衡树)

    SBT(Size Balance Tree), 即一种通过子树大小(size)保持平衡的BST SBT的基本性质是:每个节点的size大小必须大于等于其兄弟的儿子的size大小: 当我们插入或者删除一 ...

  5. 子树大小平衡树(Size Balanced Tree,SBT)操作模板及杂谈

    基础知识(包括但不限于:二叉查找树是啥,SBT又是啥反正又不能吃,平衡树怎么旋转,等等)在这里就不(lan)予(de)赘(duo)述(xie)了. 先贴代码(数组模拟): int seed; int ...

  6. Size Balanced Tree(节点大小平衡树)

    定义 SBT也是一种自平衡二叉查找树,它的平衡原理是每棵树的大小不小于其兄弟树的子树的大小 即size(x->l)$\ge$size(x->r->l),size(x->r-&g ...

  7. ADT基础(二)—— Tree,Heap and Graph

    ADT基础(二)-- Tree,Heap and Graph 1 Tree(二叉树) 先根遍历 (若二叉树为空,则退出,否则进行下面操作) 访问根节点 先根遍历左子树 先根遍历右子树 退出 访问顺序为 ...

  8. Size Balance Tree(SBT模板整理)

    /* * tree[x].left 表示以 x 为节点的左儿子 * tree[x].right 表示以 x 为节点的右儿子 * tree[x].size 表示以 x 为根的节点的个数(大小) */ s ...

  9. 56. 2种方法判断二叉树是不是平衡二叉树[is balanced tree]

    [本文链接] http://www.cnblogs.com/hellogiser/p/is-balanced-tree.html [题目] 输入一棵二叉树的根结点,判断该树是不是平衡二叉树.如果某二叉 ...

随机推荐

  1. 洛谷 P2144 [FJOI2007]轮状病毒

    P2144 [FJOI2007]轮状病毒 题目描述 轮状病毒有很多变种.许多轮状病毒都是由一个轮状基产生.一个\(n\)轮状基由圆环上\(n\)个不同的基原子和圆心的一个核原子构成.\(2\)个原子之 ...

  2. 【bzoj3570】 Cqoi2014—通配符匹配

    http://www.lydsy.com/JudgeOnline/problem.php?id=3507 (题目链接) 题意 给出一个主串,里面有些通配符,'*'可以代替任意字符串或者消失,'?'可以 ...

  3. vim 折叠的用法

    http://www.cnblogs.com/fakis/archive/2011/04/14/2016213.html 1. 折叠方式 可用选项来设定折叠方式: 可在Vim 配置文件中设置 set ...

  4. WEB入门 四 CSS样式表深入

    学习内容 Ø        CSS选择器深入学习 Ø        CSS继承 Ø        CSS文本效果 Ø        CSS图片效果 能力目标 Ø        掌握CSS选择器的组合声 ...

  5. Gradle 命令之 --stacktrace , --info , --debug 用法

    FAQ: Android studio 出现错误Run with --stacktrace option to get the stack trace. Run with --info or --de ...

  6. 解题:POI 2008 Subdivision of Kingdom

    题面 还可以这么搜......学到了(PoPoQQQ orz) 我们最朴素的做法是枚举所有状态(当然可以剪,剪完最终实际状态量也是$C_{26}^{13}$的),然后每次$O(n)$扫一遍判断,大概会 ...

  7. Webpack 学习笔记总结

    Webpack安装 Linux系统默认已经安装了node&npm,但版本比较低,而且没法升级,可以重新下载Node然后通过软链接替换系统自带的node和npm; ln -s /path_to/ ...

  8. python的map函数的使用方法详解以及使用案例(处理每个元素的自增、自减、平方等)

    1.用我们之前学过的求一下平方(只有一个列表) #求平方 num=[1,5,6,2,7,8] a=[] for n in num: a.append(n**2) print (a) C:\python ...

  9. python的函数介绍 位置参数 关键字参数 默认参数 参数组 *args **kwargs

    1.数学意义的函数与python中的函数 数学意义的函数 y = 2*3+1 x =3 y =7 x是自变量,y是因变量 2.python中定义函数的方法 一个函数往往都是为了完成一个特定的功能而存在 ...

  10. golang 中 sync包的 WaitGroup

    golang 中的 sync 包有一个很有用的功能,就是 WaitGroup 先说说 WaitGroup 的用途:它能够一直等到所有的 goroutine 执行完成,并且阻塞主线程的执行,直到所有的 ...