1. 程式人生 > >【資料結構】基數樹

【資料結構】基數樹

        本節研究基數樹相關的機制和實現;

基數樹

說明幾點

(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, mask, 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 key, 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;
}