skiplist

数据量较大(百万级), 且要求部分有序(类似堆)或者全有序, 支持插入和删除。

list 查找慢, 导致插入和删除慢

vector查找快, 但是插入和删除消耗大,会移动近半元素

set/map,与 skiplist性能同级别。 但是实现复杂。


skiplist 在redis中作为set的底层容器。 插入、查找、删除级别都是log(n)


//author: lovelychen_1005@163.com
/*改动:
0 改为c++类。
1 支持模板。 只是用简单类型测试过。 复杂类型未测试, 需要提供std::less<Key>
2 支持遍历。 方式是Peek取得头部; 如果需要删掉,Delete掉Peek的返回值即可
*/

#pragma once

#include <stdlib.h>
#include <stdio.h>

//http://www.cnblogs.com/liuhao/archive/2012/07/26/2610218.html
//http://in.sdo.com/?p=711

#define SKIPLIST_MAXLEVEL 25 

template <typename Key> class SkipListNode;
template <typename Key> class SkipListLevel;

template <typename Key> class SkipListLevel {
public:
	SkipListNode<Key> *forward;
};

template <typename Key> class SkipListNode {
	SkipListNode();
	~SkipListNode();
public:
	Key score;
	SkipListNode *backward;
	SkipListLevel<Key> level[];

	void Free()
	{
		::free(this);
	}

	static SkipListNode *CreateNode(int level, const Key & score) {
		SkipListNode * sn = (SkipListNode *)(malloc(sizeof(*sn) + level*sizeof(SkipListLevel<Key>)));
		sn->score = score;
		return sn;
	}
};



template <typename Key> class SkipList {
	SkipList();
	~SkipList();
public:
	SkipListNode<Key> *header, *tail;
	unsigned long length;
	int level;

	static SkipList *CreateList(void) {
		int j;
		SkipList *sl;

		sl = (SkipList *)(malloc(sizeof(*sl)));
		sl->level = 1;
		sl->length = 0;
		sl->header = SkipListNode<Key>::CreateNode(SKIPLIST_MAXLEVEL, 0);
		for(j = 0; j < SKIPLIST_MAXLEVEL; j++) {
			sl->header->level[j].forward = NULL;
		}
		sl->header->backward = NULL;
		sl->tail = NULL;
		return sl;
	}

	void Free() {
		SkipListNode<Key> *node = this->header->level[0].forward, *next;

		free(this->header);
		while(node) {
			next = node->level[0].forward;
			node->Free();
			node = next;
		}
		free(this);
	}

	const Key &Peek()
	{
		const static Key nullKey(0);

		if(length <= 0) return nullKey;

		SkipListNode<Key>  *node = NULL;
		for (int i = this->level - 1; i >= 0; i--) {
			if (this->header->level[i].forward) {
				node = this->header->level[i].forward; break;
			}
		}

		return header->level[0].forward->score;
	}

	static int RandomLevel(void) {
		int level = 1;
		while((rand()&0xFFFF) < (0.5 * 0xFFFF)) 
			level += 1;
		return (level < SKIPLIST_MAXLEVEL) ? level : SKIPLIST_MAXLEVEL;
	}

	SkipListNode<Key> *Insert(const Key & score) {
		SkipListNode<Key> *update[SKIPLIST_MAXLEVEL];
		SkipListNode<Key> *node;

		node = this->header;
		int i, level;
		for ( i = this->level-1; i >= 0; i--) {
			while(node->level[i].forward && node->level[i].forward->score < score) {
				node = node->level[i].forward;
			}
			update[i] = node;
		}
		level = RandomLevel();
		if (level > this->level) {
			for (i = this->level; i< level ;i++) {
				update[i] = this->header;
			}
			this->level = level;
		}
		node = SkipListNode<Key>::CreateNode(level, score);
		for (i = 0; i < level; i++) {
			node->level[i].forward = update[i]->level[i].forward;
			update[i]->level[i].forward = node;
		}

		node->backward = (update[0] == this->header? NULL : update[0]);
		if (node->level[0].forward)
			node->level[0].forward->backward = node;
		else
			this->tail = node;
		this->length++;
		return node;
	}

	
	int Delete(const Key &score) {
		SkipListNode<Key> *update[SKIPLIST_MAXLEVEL], *node;
		int i;

		node = this->header;
		for(i = this->level-1; i >= 0; i--) {
			while (node->level[i].forward && node->level[i].forward->score < score) {
				node = node->level[i].forward;
			}
			update[i] = node;
		}
		node = node->level[0].forward;
		if (node && score == node->score) {
			DeleteNode(node, update);
			node->Free();
			return 1;
		} else {
			return 0;
		}
		return 0;
	}

	int Search(const Key & score) {
		SkipListNode<Key> *node;
		int i;

		node = this->header;
		for (i = this->level-1; i >= 0 ;i--) {
			while(node->level[i].forward && node->level[i].forward->score < score) {
				node = node->level[i].forward;
			}
		}
		node = node->level[0].forward;
		if (node && score == node->score) {
			printf("Found %d\n",(int)node->score);
			return 1;
		} else {
			printf("Not found %d\n", (int)score);
			return 0;
		}
	}

	void Print() {
		SkipListNode<Key> *node;
		int i;
		for (i = 0; i < SKIPLIST_MAXLEVEL; i++) {
			printf("LEVEL[%d]: ", i);
			node = this->header->level[i].forward;
			while(node) {
				printf("%d -> ", (int)(node->score));
				node = node->level[i].forward;
			}
			printf("NULL\n");
		}
	}

private:
	void DeleteNode(SkipListNode<Key> *x, SkipListNode<Key> **update){
		int i;
		for (i = 0; i < this->level; i++) {
			if (update[i]->level[i].forward == x) {
				update[i]->level[i].forward = x->level[i].forward;
			}
		}
		if (x->level[0].forward) {
			x->level[0].forward->backward = x->backward;
		} else {
			this->tail = x->backward;
		}
		while (this->level > 1 && this->header->level[this->level-1].forward == NULL) 
			this->level--;
		this->length--;
	}
};


### Skiplist 数据结构概述 Skiplist 是一种基于概率的数据结构,用于高效地实现有序集合的操作。它通过构建多层链表的方式,在保持数据有序的同时支持快速的插入、删除和查找操作。每层链表中的节点数量逐级减少,从而形成类似于二分查找的效果。 在 Redis 中,Skiplist 被用来实现 `zset`(sorted set),即有序集合对象的一部分[^1]。具体来说,一个 `zset` 同时包含一个字典和一个跳跃表,其中字典负责映射成员与其分数的关系,而跳跃表则维护按分数排序的成员序列。 以下是关于如何实现或使用 Skiplist 的详细介绍: --- ### Skiplist 的基本组成 #### 1. **节点定义** 每个节点通常包含以下几个字段: - 成员值(member) - 分数值(score),表示成员的权重 - 下一层指向相同位置的指针数组(level) ```c typedef struct skiplistNode { char *member; // 成员名称 double score; // 成员对应的分数 struct skiplistLevel { struct skiplistNode *forward; // 指向下一层的指针 unsigned int span; // 当前跨度(可选优化项) } level[]; } skiplistNode; ``` #### 2. **头结点与层数控制** 为了方便管理整个跳跃表,还需要定义一个头部节点以及一些元信息来记录当前的最大层数和其他属性。 ```c typedef struct skiplist { struct skiplistNode *header; // 头部节点 unsigned long length; // 总节点数 int level; // 最大层数 } skiplist; ``` --- ### Skiplist 的核心算法 #### 1. **随机化层数** 每次创建新节点时,都需要为其分配合适的层数。这一步骤决定了性能的关键特性——时间复杂度接近于 O(log n)。Redis 使用如下方法计算随机层数: ```c #include <stdlib.h> #define MAX_LEVEL 32 // 假设最大可能层数为32 int randomLevel(void) { int level = 1; while ((rand() & 0xFFFF) < (REDIS_SKIPLIST_P << 16)) { // REDIS_SKIPLIST_P 表示提升的概率,默认为0.25 level += 1; } return (level < MAX_LEVEL) ? level : MAX_LEVEL; } ``` #### 2. **插入操作** 插入过程分为两步:先定位合适的位置并更新路径上的所有前置节点;再根据随机化的层数调整各级链接关系。 ```c void insert(skiplist *sl, double score, char *member) { skiplistNode *update[MAX_LEVEL]; // 记录各层需修改的前驱节点 skiplistNode *x; x = sl->header; for (int i = sl->level - 1; i >= 0; i--) { while (x->level[i].forward && compare(x->level[i].forward, member, score) < 0) { x = x->level[i].forward; } update[i] = x; // 更新第i层的前驱节点 } int new_level = randomLevel(); // 随机决定新增节点的高度 if (new_level > sl->level) { for (int i = sl->level; i < new_level; i++) { update[i] = sl->header; } sl->level = new_level; } x = createNode(new_level, score, member); // 创建新的节点 for (int i = 0; i < new_level; i++) { x->level[i].forward = update[i]->level[i].forward; update[i]->level[i].forward = x; } sl->length++; } ``` #### 3. **查找操作** 利用跳跃表的特点,从最高层开始逐步缩小范围直至找到目标元素为止。 ```c skiplistNode* find(skiplist *sl, double score, char *member) { skiplistNode *x = sl->header; for (int i = sl->level - 1; i >= 0; i--) { while (x->level[i].forward && compare(x->level[i].forward, member, score) < 0) { x = x->level[i].forward; } } x = x->level[0].forward; if (x != NULL && equal(x, member, score)) { return x; } return NULL; } ``` #### 4. **删除操作** 删除逻辑较为简单,只需沿着之前保存的路径逐一断开对应连接即可。 ```c void remove(skiplist *sl, double score, char *member) { skiplistNode *update[MAX_LEVEL]; skiplistNode *x = sl->header; for (int i = sl->level - 1; i >= 0; i--) { while (x->level[i].forward && compare(x->level[i].forward, member, score) < 0) { x = x->level[i].forward; } update[i] = x; } x = x->level[0].forward; if (x != NULL && equal(x, member, score)) { for (int i = 0; i < sl->level; i++) { if (update[i]->level[i].forward == x) { update[i]->level[i].forward = x->level[i].forward; } } freeNode(x); sl->length--; while (sl->level > 1 && sl->header->level[sl->level - 1].forward == NULL) { sl->level--; } } } ``` --- ### Skiplist 的实际应用 除了 Redis 中的 `zset` 实现外,Skiplist 还可以广泛应用于其他场景,例如分布式系统的路由表设计、日志文件的时间戳索引等。其主要优势在于能够以较低的空间代价换取较高的访问速度。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值