跳表的实现

简介

skiplist本质也是一种查找结构,和搜索树、哈希表一样可以作为key或者key/value模型的查找结构,从命名可以看出它也是一个链表结构,链表的查找效率是O(n),作为在链表基础上优化的一种查找结构,跳表的查找的平均时间复杂度是O(logn),跳表是在有序链表的基础上发展起来的。优化的思路如下:

  1. 假如我们每相邻两个节点升高一层,增加一个指针,让指针指向下下个节点,如下图b所示。这 样所有新增加的指针连成了一个新的链表,但它包含的节点个数只有原来的一半。由于新增加的指针,我们不再需要与链表中每个节点逐个进行比较了,需要比较的节点数大概只有原来的一半。

  2. 以此类推,我们可以在第二层新产生的链表上,继续为每相邻的两个节点升高一层,增加一
    个指针,从而产生第三层链表。如下图c,这样搜索效率就进一步提高了。

  3. skiplist正是受这种多层链表的想法的启发而设计出来的。实际上,按照上面生成链表的方
    式,上面每一层链表的节点个数,是下面一层的节点个数的一半,这样查找过程就非常类似
    二分查找,使得查找的时间复杂度可以降低到O(log n)。但是这个结构在插入删除数据的时
    候有很大的问题,插入或者删除一个节点之后,就会打乱上下相邻两层链表上节点个数严格
    的2:1的对应关系。如果要维持这种对应关系,就必须把新插入的节点后面的所有节点(也
    包括新插入的节点)重新进行调整,这会让时间复杂度重新蜕化成O(n)。

在这里插入图片描述

  1. skiplist的设计为了避免这种问题,做了一个大胆的处理,不再严格要求对应比例关系,而是
    插入一个节点的时候随机出一个层数。这样每次插入和删除都不需要考虑其他节点的层数,
    这样就好处理多了。细节过程入下图:

在这里插入图片描述
插入一个节点,这个节点随机一个层数的方式受两个参数的影响,p增加一层的概率,maxlenvel最大层数限制,伪代码如下
在这里插入图片描述
C++实现如下

int randomlevel()
	{
		int level = 1;
		while (rand() <= RAND_MAX * p && level < maxlevel)  //RAND_MAX是能随机生成的最大值
			++level;

		return level;
	}

Redis的跳表实现中p是取1/4 maxlevel取32。
根据伪代码可以看出,产生越高的节点层数概率越低
节点层数等于1的概率为1-p
层数大于等于2的概率为p,恰好等于2的概率为p(1-p)
层数大于等于3的概率为p2,恰好等于3的概率为p2(1-p)
层数大于等于4的概率为p3, 恰好等于4的概率为p3(1-p)

一个节点的平均层数计算公式为

在这里插入图片描述
当p=1/2时,每个节点包含的平均指针数目为2
当p=1/4时,每个节点包含的平均指针数目为1.33

跳表的实现

struct skiplistnode
{
	int _val;
	vector<skiplistnode*>_nextV;

	skiplistnode(int val,int level)
		:_val(val)
		,_nextV(level,nullptr)
	{}
};
class Skiplist
{
	typedef skiplistnode node;
public:
	Skiplist()
	{
		_head = new node(-1, 1);
		srand((unsigned int)time(0));
	}
	bool search(int target)
	{
		node* cur = _head;
		int level = _head->_nextV.size()-1;
		while (level>=0)
		{
			if (cur->_nextV[level] && cur->_nextV[level]->_val < target)
			{
				cur = cur->_nextV[level];
			}
			else if (cur->_nextV[level] == nullptr || cur->_nextV[level]->_val > target)
			{
				--level;
			}
			else
				return true;	
		}
		return false;
	}
	vector<node*> findprevnode(int num)
	{
		node* cur = _head;
		int level = _head->_nextV.size() - 1;
		vector<node*>prev(level + 1, _head);
		while (level >= 0)
		{
			if (cur->_nextV[level] && cur->_nextV[level]->_val < num)
			{
				cur = cur->_nextV[level];
			}
			else if (cur->_nextV[level] == nullptr || cur->_nextV[level]->_val >= num)
			{
				prev[level] = cur;
				--level;
			}
		}
		return prev;
	}
	void add(int num)
	{
		vector<node*>prev = findprevnode(num);
		int n = randomlevel();
		node* newnode = new node(num, n);
		if (n > prev.size())
		{
			prev.resize(n, _head);
			_head->_nextV.resize(n);
		}
		for (int i = 0; i < n; ++i)
		{
			newnode->_nextV[i] = prev[i]->_nextV[i];
			prev[i]->_nextV[i] = newnode;
		}
	}
	bool erase(int num)
	{
		vector<node*>prev = findprevnode(num);
		if (prev[0]->_nextV[0] == nullptr || prev[0]->_nextV[0]->_val != num) //说明不存在num
			return false;
		else
		{
			node* del = prev[0]->_nextV[0];
			for (int i = 0; i < del->_nextV.size(); ++i)
			{
				prev[i]->_nextV[i] = del->_nextV[i];
			}
			delete del;
			int level = _head->_nextV.size() - 1;
			while (level>0&&_head->_nextV[level] == nullptr)
			{
				--level;
			}
			_head->_nextV.resize(level + 1); //删除一个节点后可能导致最顶上有些层数指针指向空,把这些没意义的层数减一下来
			return true;
		}
	}
	int randomlevel()
	{
		int level = 1;
		while (rand() <= RAND_MAX * p && level < maxlevel)
			++level;

		return level;
	}
	void print()
	{
		node* cur = _head;
		while (cur)
		{
			printf("%2d\n", cur->_val);
			for (auto& e : cur->_nextV)
			{
				printf("%2s", "↓");
			}
			printf("\n");
			cur = cur->_nextV[0];
		}
	}
private:

	node* _head;
	double p = 0.4;
	int maxlevel = 32;
};
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值