基數樹的簡單實現

基數樹是一種比較節省空間的樹結構,下圖展示了基數樹的結構,其中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有點浪費空間。刪除操作還可以優化,多刪一些沒用的節點,可以節省空間,雖然這樣會增加刪除操作佔用的時間,等等。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章