【数据结构】基数树

前端之家收集整理的这篇文章主要介绍了【数据结构】基数树前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

本节研究基数树相关的机制和实现;


基数树

说明几点

(1)基数树,是一种基于二进制表示键值的二叉查找树,类似字典树;其典型应用为IP地址的查找;

(2)如果使用IPv4时,基数树只需要支持到最大深度为32就可以了,key值从最高位向最低位开始匹配,比如key为0xC0000000,将会从key的最高位1向0开始匹配;


代码分析

(本节代码选自Nginx中关于基数树的代码

基数树声明

//基数树节点
struct ngx_radix_node_s {
    ngx_radix_node_t  *right;     //右孩子
    ngx_radix_node_t  *left;      //左孩子
    ngx_radix_node_t  *parent;    //父亲
    uintptr_t          value;     //指向用户实际的数据,若还没有使用为NGX_RADIX_NO_VALUE
};

//基数树管理结构
typedef struct {
    ngx_radix_node_t  *root;      //指向根结点
    ngx_pool_t        *pool;      //内存池
    ngx_radix_node_t  *free;      //管理已经分配但暂时未使用(不在树中)的节点,实际上所有不在树中节点的单链表,使用right组成一个单链表
    char              *start;     //管理已经分配但暂时未使用内存的首地址
    size_t             size;      //已经分配内存中还未使用的内存大小
} ngx_radix_tree_t;

基数树节点分配(内存分配)

//基数树节点分配(申请一个节点内存)
static ngx_radix_node_t *
ngx_radix_alloc(ngx_radix_tree_t *tree)
{
    ngx_radix_node_t  *p;

    if (tree->free) {   //管理已经分配但暂时未使用(不在树中)的节点有节点
        p = tree->free; //直接找到
        tree->free = tree->free->right; //指向下一个节点
        return p;
    }

    if (tree->size < sizeof(ngx_radix_node_t)) {    //已有内存不够分配一个节点
        tree->start = ngx_pmemalign(tree->pool,ngx_pagesize,ngx_pagesize);  //分配一页内存
        if (tree->start == NULL) {
            return NULL;
        }

        tree->size = ngx_pagesize;    //一页大小
    }

    p = (ngx_radix_node_t *) tree->start;     //分配的节点内存地址
    tree->start += sizeof(ngx_radix_node_t);  //更新已分配内存还未使用内存的地址
    tree->size -= sizeof(ngx_radix_node_t);   //剩余的内存大小

    return p;
}

基数树创建

//基数树创建
ngx_radix_tree_t *
ngx_radix_tree_create(ngx_pool_t *pool,ngx_int_t preallocate)  
{
    //preallocate表示建树的深度;当preallocate为1时,一共有3个节点;当preallocate为2,一共有7个节点;当为n时有2^(n+1)-1个节点;默认为-1;
    
    uint32_t           key,mask,inc;
    ngx_radix_tree_t  *tree;

    tree = ngx_palloc(pool,sizeof(ngx_radix_tree_t));    //申请基数树管理结构内存
    if (tree == NULL) {
        return NULL;
    }

    tree->pool = pool;
    tree->free = NULL;    //管理已经分配但暂时未使用(不在树中)的节点,初始化为NULL
    tree->start = NULL;   //管理已经分配但暂时未使用内存的首地址,初始化为NULL
    tree->size = 0;       //已经分配内存中还未使用的内存大小,初始化为0

    tree->root = ngx_radix_alloc(tree);     //创建根节点
    if (tree->root == NULL) {
        return NULL;
    }

    tree->root->right = NULL;
    tree->root->left = NULL;
    tree->root->parent = NULL;
    tree->root->value = NGX_RADIX_NO_VALUE;   //初始值

    if (preallocate == 0) {
        return tree;
    }

    /*
     * Preallocation of first nodes : 0,1,00,01,10,11,000,001,etc.
     * increases TLB hits even if for first lookup iterations.
     * On 32-bit platforms the 7 preallocated bits takes continuous 4K,* 8 - 8K,9 - 16K,etc.  On 64-bit platforms the 6 preallocated bits
     * takes continuous 4K,7 - 8K,8 - 16K,etc.  There is no sense to
     * to preallocate more than one page,because further preallocation
     * distributes the only bit per page.  Instead,a random insertion
     * may distribute several bits per page.
     *
     * Thus,by default we preallocate maximum
     *     6 bits on amd64 (64-bit platform and 4K pages)
     *     7 bits on i386 (32-bit platform and 4K pages)
     *     7 bits on sparc64 in 64-bit mode (8K pages)
     *     8 bits on sparc64 in 32-bit mode (8K pages)
     */

    if (preallocate == -1) {
        switch (ngx_pagesize / sizeof(ngx_radix_node_t)) {    //可以有多少个树节点

        /* amd64 */
        case 128:
            preallocate = 6;    // 2^7-1为127个节点
            break;

        /* i386,sparc64 */
        case 256:
            preallocate = 7;   // 2^8-1为255个节点
            break;

        /* sparc64 in 32-bit mode */
        default:
            preallocate = 8;
        }
    }

    mask = 0;
    inc = 0x80000000;     //增加的幅度

    //预创建树
    while (preallocate--) { 

        key = 0;
        mask >>= 1;
        mask |= 0x80000000;   //初始时为0x80000000,表示第一层;然后依次右移和或,变为0xC0000000,表示第二层;其实mask表示对应的层次

        do {
            if (ngx_radix32tree_insert(tree,key,NGX_RADIX_NO_VALUE)
                != NGX_OK)
            {
                return NULL;
            }

            key += inc;     
            
            //inc为0x80000000时,mask为0x80000000(表示第一层),key表示创建的节点值,根节点为0,然后变为0x80000000这样就创建了根结点的左右孩子;
            
            //退出循环后,inc为0x40000000时,mask为0xC0000000(表示第2层)key为依次又变为0,然后到0x40000000,然后到0x80000000,
            //然后到0xC0000000,然后到0x00000000,这样又对应创建了4个孩子;

        } while (key);

        inc >>= 1;
    }

    return tree;
}

插入基数树节点

//插入基数树节点,使用mask来控制层次,避免建立额外的层次
//预创建,将会建立一颗满二叉树;如果不用预创建,只会创建对应路径的树节点,其他分支则不会创建;
ngx_int_t
ngx_radix32tree_insert(ngx_radix_tree_t *tree,uint32_t key,uint32_t mask,uintptr_t value)
{
    uint32_t           bit;
    ngx_radix_node_t  *node,*next;

    bit = 0x80000000;

    node = tree->root;    //头节点
    next = tree->root;

    while (bit & mask) {    //一位位判断,从高到低,mask控制移动的层次
        if (key & bit) {    //对应的位为1,向有查找,初始时,key为0直接break;
            next = node->right;

        } else {
            next = node->left;  //为0向左找
        }

        if (next == NULL) { //跳出,已经找到
            break;
        }

        bit >>= 1;      //向低位移动
        node = next;    //指向父节点
    }

    if (next) {   //找到指定层的节点时,若不为空时
        if (node->value != NGX_RADIX_NO_VALUE) {
            return NGX_BUSY;
        }

        node->value = value;    //可以赋值
        return NGX_OK;
    }

    while (bit & mask) {     //一位位判断,从高到低,mask控制移动的层次
        next = ngx_radix_alloc(tree);   //申请一个基数树节点
        if (next == NULL) {
            return NGX_ERROR;
        }

        next->right = NULL;
        next->left = NULL;
        next->parent = node;      //指向父节点
        next->value = NGX_RADIX_NO_VALUE;   //初始化为无效值

        if (key & bit) {
            node->right = next;

        } else {
            node->left = next;
        }

        bit >>= 1;    //bit继续右移
        node = next;
    }

    node->value = value;  //对应的叶子节点,指向对应的值value

    return NGX_OK;
}

删除基数树节点

//删除基数树节点
ngx_int_t
ngx_radix32tree_delete(ngx_radix_tree_t *tree,uint32_t mask)
{
    uint32_t           bit;
    ngx_radix_node_t  *node;

    bit = 0x80000000;
    node = tree->root;

    while (node && (bit & mask)) {    //mask控制层次
        if (key & bit) {
            node = node->right;

        } else {
            node = node->left;
        }

        bit >>= 1;
    }

    if (node == NULL) {
        return NGX_ERROR;
    }

    //不为叶子节点时,简单处理
    if (node->right || node->left) {    //找到指定的节点了,但左右孩子有任一不为空时
        if (node->value != NGX_RADIX_NO_VALUE) {
            node->value = NGX_RADIX_NO_VALUE; //直接赋值为无效
            return NGX_OK;
        }

        return NGX_ERROR;
    }

    //为叶子节点时,回收内存,回收一系列单路径节点内存
    for ( ;; ) {
        if (node->parent->right == node) {    //该节点对应的孩子指向为空
            node->parent->right = NULL;   

        } else {
            node->parent->left = NULL;
        }

        //插入到tree->free的头部
        node->right = tree->free;   
        tree->free = node;

        node = node->parent;    //指向父亲

        if (node->right || node->left) {  //父亲还有另外的孩子,直接退出
            break;
        }

        if (node->value != NGX_RADIX_NO_VALUE) {  //父亲的值还是有效的,直接退出
            break;
        }

        if (node->parent == NULL) {   //如果父亲是根结点,那么也直接退出,根节点不删除
            break;
        }
    }

    return NGX_OK;
}

最长匹配查找key

//查找对应节点key上的值,其实是得到最长的匹配,不需要mask来控制层次
uintptr_t
ngx_radix32tree_find(ngx_radix_tree_t *tree,uint32_t key)
{
    uint32_t           bit;
    uintptr_t          value;
    ngx_radix_node_t  *node;

    bit = 0x80000000;
    value = NGX_RADIX_NO_VALUE;
    node = tree->root;    //树

    while (node) {
        if (node->value != NGX_RADIX_NO_VALUE) {  //最长匹配的有效值
            value = node->value;    
        }

        if (key & bit) {  //为1时,表示向右走,到右孩子
            node = node->right;

        } else {          //为0时,表示向左走,到左孩子
            node = node->left;
        }

        bit >>= 1;
    }

    return value;
}
原文链接:https://www.f2er.com/datastructure/382662.html

猜你在找的数据结构相关文章