基數樹是一種比較節省空間的樹結構,下圖展示了基數樹的結構,其中key是樹的構建方式,在這裏,key是一個32位的整數,爲了避免層數過深,所以使用兩位代表子節點的索引,基數樹就是依據二進制串來生成樹結構。值value被儲存在葉節點。
假設key=0X12345678,下圖依據key來建立一棵樹:
由與key是32位的,這裏使用2個位用於建樹,因此如果要查找一個值,最多隻需要跳轉16次就一定可以得出結果,是一種可以進行快速插入和查找的樹。
下面是基數樹的簡單實現,參考了nginx裏面基數樹的實現。
#pragma once
#include<stdlib.h>
#include<stdio.h>
#define MEMPAGE 4096//內存頁的大小,一般爲4kb
#define INIT_POOL_SIZE (MEMPAGE*32) //初始內存池大小
#define INIT_FREE_SIZE (INIT_POOL_SIZE/2) //初始備用節點長度
#define INIT_NODE_NUM (16*32)
#define RADIX_INSERT_VALUE_OCCUPY -1 //該節點已被佔用
#define RADIX_INSERT_VALUE_SAME -2 //插入了相同的值
#define RADIX_DELETE_ERROR -3 //刪除錯誤
typedef unsigned int ptr_t;
typedef unsigned int uint32;
#define BITS 2
const int radix_tree_height = sizeof(ptr_t) * 8 / BITS;//樹的高度
//返回key中由pos指定的位的值,位數由BITS指定
#define CHECK_BITS(key,pos) ((((unsigned int)(key))<<sizeof(int)*8-((pos)+1)*BITS)>>(sizeof(int)*8-BITS))
//基數樹節點
typedef struct radix_node_t radix_node_t;
struct radix_node_t {
radix_node_t* child[4];
radix_node_t* parent;
ptr_t value;//節點儲存的值
};
//使用內存池是爲減少建立節點時重新申請內存的時間
//內存池描述結構,放在內存池的前段
typedef struct radix_pool {
struct radix_pool* next;//內存池是雙向循環鏈表的一個節點
struct radix_pool* prev;
//已分配內存中還未使用的內存首地址
char* start;
//已分配內存中還未使用的內存長度
size_t size;
}radix_pool, * pool_t;
//基數樹管理結構
typedef struct radix_tree_t {
//根節點
radix_node_t* root;
//內存池指針
pool_t pool;
//儲存已分配但不在樹中的節點(雙向鏈表,這裏儲存其中的一個節點)
radix_node_t* free;
}radix_tree_t;
//內存池擴大函數,num:新內存池的大小,=-1使用默認值,單位:頁
pool_t get_new_pool(radix_tree_t* t, size_t num)
{
if (num == -1)num = INIT_POOL_SIZE;
pool_t pool = (pool_t)malloc(num * MEMPAGE);
if (pool == NULL)return NULL;
pool->start = (char*)pool + sizeof(radix_pool);
pool->size = num * MEMPAGE - sizeof(radix_pool);
pool->next = t->pool->next;
pool->prev = t->pool;
t->pool->next->prev = pool;
t->pool->next = pool;
t->pool = pool;
return pool;
}
//創建一個節點,從內存池中取出可以使用的節點
radix_node_t* radix_node_alloc(radix_tree_t* t)
{
radix_node_t* node;
if (t->free != NULL) {//從free中提取節點
node = t->free;
t->free = node->parent;
}
else {//在內存池中尋找可以使用的內存
if (t->pool->size < sizeof(radix_node_t)) {//如果剩餘空間不夠分配,則重新分配
get_new_pool(t, -1);
}
node = (radix_node_t*)t->pool->start;
t->pool->start += sizeof(radix_node_t);
t->pool->size -= sizeof(radix_node_t);
}
node->child[0] = NULL;
node->child[1] = NULL;
node->child[2] = NULL;
node->child[3] = NULL;
node->parent = NULL;
node->value = NULL;
return node;
}
//創建管理結構
radix_tree_t* radix_tree_create()
{
int i;
radix_tree_t* tree = (radix_tree_t*)malloc(sizeof(radix_tree_t));
if (tree == NULL)return NULL;
char* p = (char*)malloc(INIT_POOL_SIZE);
radix_node_t* ns;
if (!p) {
free(tree); return NULL;
}
//爲內存池結構分配空間
((pool_t)p)->next = (pool_t)p;
((pool_t)p)->prev = (pool_t)p;
ns = (radix_node_t*)((char*)p + sizeof(radix_pool));
//在內存中創建鏈表
for (i = 1; i < INIT_NODE_NUM - 2; ++i) {
ns[i].parent = &ns[i + 1];
}
ns[i].parent = NULL;
ns[0].child[0] = NULL;
ns[0].child[1] = NULL;
ns[0].child[2] = NULL;
ns[0].child[3] = NULL;
ns[0].parent = NULL;
ns[0].value = NULL;
tree->pool = (pool_t)p;
tree->root = ns;
tree->free = &ns[1];
((pool_t)p)->start = (char*)ns + sizeof(radix_node_t) * INIT_NODE_NUM;
((pool_t)p)->size = INIT_POOL_SIZE - sizeof(radix_pool) - sizeof(radix_node_t) * INIT_NODE_NUM;
return tree;
}
//插入
int radix_tree_insert(radix_tree_t* t, uint32 key, ptr_t value)
{
int i, temp;
radix_node_t* node, * child;
node = t->root;
for (i = 0; i < radix_tree_height; i++) {
temp = CHECK_BITS(key, i);
if (!node->child[temp]) {
child = radix_node_alloc(t);
if (!child)return NULL;
child->parent = node;
node->child[temp] = child;
node = node->child[temp];
}
else {
node = node->child[temp];
}
}
if (node->value == value)return RADIX_INSERT_VALUE_SAME;
if (node->value != NULL)return RADIX_INSERT_VALUE_OCCUPY;
node->value = value;
return 0;
}
//由於插入時會創建很多節點,爲了提高刪除速度這裏只會刪除最底層的指定節點
int radix_tree_delete(radix_tree_t* t, uint32 key)
{
radix_node_t* node = t->root, * par;
int i = 0, temp = 0;
if (node == NULL)return RADIX_DELETE_ERROR;
do {
temp = CHECK_BITS(key, i++);
node = node->child[temp];
} while (node && i < radix_tree_height);
//node爲儲存value的節點,在父節點中將此節點的鏈接置空,
//然後清空value後將此節點加入free中
if (node == NULL)return RADIX_DELETE_ERROR;
par = node->parent;
par->child[temp] = NULL;
node->value = NULL;
node->child[0] = NULL;
node->child[1] = NULL;
node->child[2] = NULL;
node->child[3] = NULL;
node->parent = t->free->parent;
t->free->parent = node;
return 0;
}
//打印函數,會打印出所有葉節點儲存的值
void radix_print(radix_node_t* node)
{
if (node == NULL)return;
if (node->value != NULL)
printf("%x\n", node->value);
radix_print(node->child[0]);
radix_print(node->child[1]);
radix_print(node->child[2]);
radix_print(node->child[3]);
}
//節點查找函數
//key爲索引,返回葉節點被查找到的值
ptr_t radix_tree_find(radix_tree_t* t, uint32 key)
{
int i = 0, temp;
radix_node_t* node;
node = t->root;
while (node && i < radix_tree_height) {
temp = CHECK_BITS(key, i++);
node = node->child[temp];
}
if (node == NULL)return NULL;
return node->value;
}
上面仍有值得改進的地方,比如節點結構,每一個節點不論是內部節點還是葉節點都有儲存數據的value有點浪費空間。刪除操作還可以優化,多刪一些沒用的節點,可以節省空間,雖然這樣會增加刪除操作佔用的時間,等等。