【資料結構】基數樹
阿新 • • 發佈:2019-01-07
本節研究基數樹相關的機制和實現;
基數樹
說明幾點
(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;
}