跳跃表实现

跳跃表:


跳跃表与链表结构相似,只是引入“分层”的概念,从上到下的每一层都是一个链表。

借个图:

从图中可观察到跳跃表有以下的性质:

1.每个节点有多个层,每层都有一个指向同层的下一节点的指针

2.每层的链表都是一个有序链表,根据给定的key排序

3.最底层也就是第一层,的链表包含所有节点

4.存在于 k 层的节点,同样也存在于 <k 层的链表中

引入多层的链表的概念是为了加快查找效率,使其从最高层向下查找过程接近于二分查找

想到平衡树的结构,我们期待第 k 层链表的节点个数为 n ,第 k + 1 层的节点个数为 n / 2,且能够均匀分布

像这样:

保证上层节点的数量是下层节点数量的1/2的方法就是,让节点的层数增加的概率为1/2。

int rand_level(){
    int level = 1;
    while(rand() & 1)    //每次节点层数的增加的概率都是 1/2
        ++level;
    return level;
}

但是若想让节点分布均匀,就要支付向平衡树旋转调整的代价,所以只是通过上面的层数计算方法,近似的使节点分布均匀

跳跃表结构


struct skip_node
{
    skip_node*       level;    //层数 随机生成
    int              score;    //分值 就是排序用的key
    void*             data;    //保存的数据
};

struct skip_list
{
    skip_node*          head;        //头结点,
    int                 maxLevel;    //整个跳跃表,最大的层数
    int                 length;      //跳跃表元素的个数  
};

redis中使用跳跃表来保存有序集合。但是redis中的跳跃表结构有所不同,其目的也是针对特殊的需求,而增加了几个字段。

下面是我实现的跳跃表 c语言版:

其中在skip_node中增加了levelNum和backward字段是为了方便操作,方便范围查找和删除。

并且在skip_list中增加了tail节点指针,指向最后一个节点。


#ifndef _SKIP_LIST_H
#define _SKIP_LIST_H
#include <iostream>
#include <string>
#include <ctime>
#include <cstdio>
#include <cstdlib>
using namespace std;

#define SKIP_LIST_MAX_LEVEL 32

struct skip_node;
struct skip_level
{
	skip_node*	forward;		//前进指针
};

struct skip_node
{
	skip_level* level;			//保存多个层
	skip_node*	backward;		//后退指针后退步数为1
	int			levelnum;		//层数
	double		score;			//分值,作为key对跳跃表排序
	void*		data;			//数据对象
};

struct skip_list
{
	skip_node*	head;			//表头拥有最大层数,不保存数据
	skip_node*	tail;			//指向尾节点	
	int			level;			//除表头之外的最大层数
	int			length;			//跳跃表的长度,也就表示节点个数
};

//函数声明
skip_list*	create_skiplist();	
int			rand_level();
skip_node*	find_skipnode(skip_list* skiplist, double score);
void		find_skipnode_inrange(skip_list* skiplist, double startscore, double endscore, skip_node** startnode, skip_node** endnode);
void		insert_skipnode(skip_list* skiplist, double score, void* data);
void		remove_skipnode(skip_list* skiplist, skip_node* del);
void		remove_inrange(skip_list* skiplist, double startscore, double endscore);
skip_node*	find_inrange_first(skip_list* skiplist, double startscore, double endscore);
skip_node*	find_inrange_last(skip_list* skiplist, double startscore, double endscore);
void		print_skiplist(skip_list* skiplist);
void		destroy_skiplist(skip_list* skiplist);

//函数实现
skip_list*	create_skiplist() {
	skip_list* skiplist = (skip_list*)malloc(sizeof(skip_list));
	if (skiplist == NULL)
		return NULL;
	if ((skiplist->head = (skip_node*)malloc(sizeof(skip_node))) == NULL) {
		free(skiplist);
		return NULL;
	}
	skiplist->length = 0;
	skiplist->level = 0;

	skiplist->head->level = (skip_level*)malloc(sizeof(skip_level) * SKIP_LIST_MAX_LEVEL);
	if (skiplist->head->level == NULL) {
		free(skiplist->head);
		free(skiplist);
		return NULL;
	}
	memset(skiplist->head->level, 0, sizeof(skip_level) * SKIP_LIST_MAX_LEVEL);
	skiplist->head->backward = NULL;
	skiplist->head->score = 0;
	skiplist->head->data = NULL;
	skiplist->head->levelnum = SKIP_LIST_MAX_LEVEL;

	skiplist->tail = NULL;

	srand(time(NULL));	//定义随机数种子,后面会使用rand随机生成节点的层数

	return skiplist;
}

int			rand_level() {
	int level = 1;
	while (rand() % 2) {
		++level;
	}
	return level;
}

void		insert_skipnode(skip_list* skiplist, double score, void* data) {
	skip_node* node = (skip_node*)malloc(sizeof(skip_node));
	int level = rand_level();
	node->level = (skip_level*)malloc(sizeof(skip_level) * level);
	memset(node->level, 0, sizeof(skip_level) * level);
	node->score = score;
	node->data = data;
	node->levelnum = level;

	skip_node* update[SKIP_LIST_MAX_LEVEL];		//保存要插入节点的前面的各层指针
	memset(update, 0, sizeof(skip_node*) * SKIP_LIST_MAX_LEVEL);
	skip_node* start = skiplist->head;
	int maxlevel = skiplist->level;
	for (int k = maxlevel - 1; k >= 0; k--) {	//从高层向下查找
		skip_node* end = start;
		skip_node* ptmp = NULL;
		//找到比score大的节点
		while ((ptmp = end->level[k].forward) && ptmp->score < score) {
			end = ptmp;
		}
		update[k] = end;
	}

	if (level > maxlevel) {	//如果新节点的层数大于当前最大层,则它多出来的几层的前指针为head的各层指针
		for (int k = level - 1; k >= maxlevel; k--) {
			update[k] = skiplist->head;
		}
		skiplist->level = level;
	}

	for (int i = 0; i < level; i++) {
		node->level[i].forward = update[i]->level[i].forward;
		update[i]->level[i].forward = node;
		if (i == 0) {
			node->backward = update[i];
		}
	}

	if (skiplist->tail == NULL || node->level[0].forward == NULL) {
		skiplist->tail = node;
	}

	++skiplist->length;
}

skip_node*	find_skipnode(skip_list* skiplist, double score) {
	skip_node* head = skiplist->head;
	int maxlevel = skiplist->level;
	skip_node* ptmp = NULL;
	for (int k = maxlevel - 1; k >= 0; k--) {
		while ((ptmp = head->level[k].forward) && ptmp->score < score) {
			head = ptmp;
		}
		if (ptmp && ptmp->score == score) {
			return ptmp;
		}
	}
	return NULL;
}

void		find_skipnode_inrange(skip_list* skiplist,double startscore, double endscore, skip_node** startnode, skip_node** endnode) {
	*startnode = NULL;
	*endnode = NULL;
	if (startscore > endscore)
		return;
	int maxlevel = skiplist->level;
	skip_node* start = skiplist->head;
	skip_node* end = start;
	skip_node* ptmp = NULL;
	skip_node* ptmp2 = NULL;
	bool isFindLeft = false;
	bool isFindRight = false;
	for (int k = maxlevel - 1; k >= 0; k--) {
		while ((ptmp = end->level[k].forward) && !isFindRight && ptmp->score < endscore) {
			end = ptmp;
		}
		if (!isFindRight && ptmp && ptmp->backward && ptmp->backward->score <= endscore) {
			*endnode = ptmp;
			isFindRight = true;
		}
		while ((ptmp2 = start->level[k].forward)&& !isFindLeft && ptmp2->score < startscore) {
			start = ptmp2;
		}
		if (!isFindLeft && ptmp2) {
			skip_node* x = ptmp2->backward;
			if (x && ((x->score < startscore) || x == skiplist->head)) {
				*startnode = ptmp2;
				isFindLeft = true;
			}
		}

		if (isFindLeft && isFindRight)
			break;
	}

}

void		remove_skipnode(skip_list* skiplist, skip_node* node) {
	skip_node* head = skiplist->head;
	skip_node* update[SKIP_LIST_MAX_LEVEL];
	for (int k = node->levelnum - 1; k >= 0; k--) {
		skip_node* x = head;
		skip_node* y = NULL;
		while ((y = x->level[k].forward) && y != node) {
			x = y;
		}
		update[k] = x;
	}
	for (int k = node->levelnum - 1; k >= 0; k--) {
		update[k]->level[k].forward = node->level[k].forward;
	}
	if (node->level[0].forward) {
		node->level[0].forward->backward = node->backward;
	}
	free(node);
	--skiplist->length;
}

skip_node* find_inrange_first(skip_list* skiplist, double startscore, double endscore) {
	skip_node* start = NULL;
	skip_node* end = NULL;
	find_skipnode_inrange(skiplist, startscore, endscore, &start, &end);
	if (start)
		return start;
	return NULL;
}

skip_node* find_inrange_last(skip_list* skiplist, double startscore, double endscore) {
	skip_node* start = NULL;
	skip_node* end = NULL;
	find_skipnode_inrange(skiplist, startscore, endscore, &start, &end);
	if (end)
		return end->backward;
	return NULL;
}

void		remove_inrange(skip_list* skiplist, double startscore, double endscore) {
	int maxlevel = skiplist->level;
	skip_node* head = skiplist->head;
	skip_node* start = NULL;
	skip_node* end = NULL;
	//找到开始和结束位置,[start,end)
	find_skipnode_inrange(skiplist, startscore, endscore, &start, &end);
	if (start == NULL)
		return;
	skip_node* front[SKIP_LIST_MAX_LEVEL];
	skip_node* rear[SKIP_LIST_MAX_LEVEL];
	memset(front, 0, sizeof(skip_node*) * SKIP_LIST_MAX_LEVEL);
	memset(rear, 0, sizeof(skip_node*) * SKIP_LIST_MAX_LEVEL);
	//保存范围前面各层的指针
	skip_node* ptmp = start->backward;
	int ncount = 0;
	while (ncount < maxlevel) {
		for (; ncount < ptmp->levelnum;) {
			front[ncount] = ptmp;
			++ncount;
		}
		ptmp = ptmp->backward;
	}
	//保存范围后的各层指针
	if (end) {
		ptmp = end->backward;
		ncount = 0;
		while (ncount < maxlevel && ptmp != start) {
			for (; ncount < ptmp->levelnum;) {
				rear[ncount] = ptmp;
				++ncount;
			}
			ptmp = ptmp->backward;
		}
	}
	//将前后连接
	for (int i = 0; i < ncount; i++) {
		if (rear[i])
			front[i]->level[i].forward = rear[i]->level[i].forward;
		else
			front[i]->level[i].forward = NULL;
	}
	//删除节点,更改skiplist->length
	if(end)
		end->backward = start->backward;
	int len = 0;
	for (; start != end; ) {
		skip_node* del = start;
		start = start->level[0].forward;
		free(del);
		++len;
	}
	skiplist->length -= len;
	//更新skiplist->level
	for (int k = maxlevel - 1; k >= 0; k--) {
		if (head->level[k].forward) {
			skiplist->level = k + 1;
			break;
		}
	}
}

void	destroy_skiplist(skip_list* skiplist) {
	skip_node* head = skiplist->head;
	skip_node* del = NULL;
	while (head) {
		del = head;
		head = head->level[0].forward;
		free(del->level);
		free(del);
	}
	free(skiplist);
}

void		print_skiplist(skip_list* skiplist) {
	if (skiplist == NULL)
		return;
	printf("====================================\n");
	skip_node * head = skiplist->head;
	while (head->level[0].forward) {
		head = head->level[0].forward;
		printf("score:%f,level:%d,data:%d\n", head->score, head->levelnum, int(head->data));
	}
	printf("++++++++++++++++++++++++++++++++++++\n");
}

#endif

测试:

int main() {

	skip_list* skip = create_skiplist();

	for (int i = 0; i < 10; i++) {
		insert_skipnode(skip, (double)i, (void*)(i + 100));
	}

	print_skiplist(skip);

	skip_node* start = NULL;
	skip_node* end = NULL;
	find_skipnode_inrange(skip, 3, 6.3, &start, &end);
	for (; start->score < end->score; start = start->level[0].forward) {
		printf("find result score:%f,level:%d,data:%d\n", start->score, start->levelnum, int(start->data));
	}
	remove_inrange(skip, 3, 6.3);
	print_skiplist(skip);

	start = find_skipnode(skip, 9);
	printf("find result score:%f,level:%d,data:%d\n", start->score, start->levelnum, int(start->data));

	insert_skipnode(skip, -3, (void*) 789);
	print_skiplist(skip);
	insert_skipnode(skip, 54, (void*)999);
	print_skiplist(skip);

	remove_inrange(skip, 100, 300);
	print_skiplist(skip);

	remove_inrange(skip, 50, 100);
	print_skiplist(skip);
	destroy_skiplist(skip);
	system("pause");
	return 0;
}

结果:

至于跳跃表与红黑树,AVL相比的优势,在查找过程中跳跃表只是近似二分,所以在效率上恐不能取胜,但是从代码的复杂度上看,逻辑比红黑树和AVL,要简单一些,现在想一想红黑树的爷爷,叔叔,兄弟节点,条件太多。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值