../
生活在BSD的树上
===============

2022-08-17

FreeBSD、OpenBSD系统都自带了一个很好用的红黑树和splay树实现。

这个实现只有头文件,无需外部依赖,而且是泛型的。所以想在Linux下面用起来也很简单。

## 获取头文件

运行:

    curl https://raw.githubusercontent.com/openbsd/src/master/sys/sys/tree.h -o tree.h

这个头文件里面包含了`sys/_null.h`,这个文件在Linux里面是没有的。不过,
`tree.h`用这个文件只是为了定义`NULL`,所以改成`stdlib.h`就行了。

    // #include <sys/_null.h>
    #include <stdlib.h>

## 声明

头文件tree.h中的树实际上全是宏,所以用之前需要先展开。举个例子,如果希望树中的
值类型是`double`,那么新建一个头文件`double_tree.h`,在文件中这样声明:

    #ifndef DOUBLE_TREE_H_
    #define DOUBLE_TREE_H_

    #include "tree.h"

    struct double_treenode {
        RB_ENTRY(double_treenode) entry;
        double val;
    };

    int double_cmp(struct double_treenode *e1, struct double_treenode *e2);

    RB_HEAD(double_tree, double_treenode);
    RB_PROTOTYPE(double_tree, double_treenode, entry, double_cmp)

    #endif

## 定义

然后创建源文件`double_tree.h`,补充需要的函数定义:

    #include "double_tree.h"

    int double_cmp(struct double_treenode *e1, struct double_treenode *e2); {
        if (e1->val < e2->val) {
            return -1;
        } else if (e1->val > e2->val) {
            return 1;
        }
        return 0;
    }

    RB_GENERATE(double_tree, double_treenode, entry, double_cmp)

## 使用

接下来介绍如何使用这个`double_tree`。

### 初始化

创建树并初始化:

    RB_HEAD(double_tree, double_treenode) head;
    RB_INIT(&head);

初始化也可以一行完成:

    RB_HEAD(double_tree, double_treenode) head = RB_INITIALIZER(&head);

### 插入

    struct double_treenode *n;
    double data[5] = {1.0, 2.0, 3.0, 4.0, 5.0};
    for (int i = 0; i < 5; i++) {
        n = malloc(sizeof(struct double_treenode));
        n->val = data[i];
        RB_INSERT(double_tree, &head, n);
    }

### 查找和删除

    struct double_treenode find;
    find.val = 3.0

    struct double_treenode *iter;
    iter = RB_FIND(double_tree, &head, &find);

    if (iter != NULL) {
        printf("Found\n");
        RB_REMOVE(double_tree, &head, iter);
        free(iter);
    }

### 遍历

    RB_FOREACH(iter, double_tree, &head) {
        // Do something on iter->val
        ...
    }

其实,`RB_FOREACH(iter, double_tree, &head)`本质上是:

    for (iter = RB_MIN(double_tree, &head);
            iter != NULL;
            iter = RB_NEXT(double_tree, &head, iter))

用`RB_MIN`可以取得树中的最小节点;用`RB_NEXT`可以获取`iter`的下一个元素;
`RB_MAX`则是最大节点。

如果想用其他的顺序遍历树的话,可以用`RB_LEFT`和`RB_RIGHT`。比如说,用前序遍历打
印树:

    void
    print_tree(struct double_treenode *n)
    {
        struct double_treenode *left, *right;

        if (n == NULL) {
            printf("nil");
            return;
        }
        left = RB_LEFT(n, entry);
        right = RB_RIGHT(n, entry);
        if (left == NULL && right == NULL)
            printf("%d", n->val);
        else {
            printf("%d(", n->val);
            print_tree(left);
            printf(",");
            print_tree(right);
            printf(")");
        }
    }

## 其他

有个很怪的事情,Arch Linux里面有`tree.h`的man文档,执行`man 3 tree`就能看到,但
是这个头文件本体却找不到。

这篇文章里面只写了红黑树,但是splay树其实也大同小异,就不再赘述了,可以直接去翻
文档。

## 勘误

感谢@w3cing提醒。
FreeBSD所提供的`tree.h`中的`RB_TREE`实际上并非红黑树,RB指的是rank-balanced
tree,而具体的实现是weak AVL。FreeBSD在2020年8月对tree.h进行过一次修改。


--------------------------------------------------------------------
Email: i (at) mistivia (dot) com