SkipList ----- 跳表

什么是跳表

1.跳表,也是基于链表实现的,他其实和链表一样,也是一个数据结构中的查找结构,用于解决一些查找问题而产生。

2.跳表虽然是基于链表所实现的,但是不同于链表的是,他的查询效率比较高,也可以说他的出现是链表的一个优化,具体优化的思想如下:

  • 链表我们知道,每次都是一个节点指向一个节点,中间没有跳过任何节点,一条路指到头的,所以他的查找的时间复杂度都是O(N)。如下图,就是一个链表的形式。
    在这里插入图片描述
  • 因为链表的查找时间很慢,所以制造跳表的人就想了想,如果说每次相邻的两个节点升高一层,增加一个指针,让指针指向下下个节点,这样新增加的节点又构成了一个链表,并且长度是原来链表长度的一般,这样我们查找一个数的时候,查找效率会提高一倍。如下图,就是优化后的链表:
    在这里插入图片描述
  • 这时他又想提高效率,在每隔两个双层的节点上再增加一层,这样提高的效率就很高了,如下图:
    在这里插入图片描述
  • 跳表其实就是这样被创造出来的,按照上面的情况分析,每一层都是下一层节点数的一半,这样的查找会根据节点所处层的高低将查找的时间复杂度降低为O(log n),但是虽然这样查找的效率很高,但是对于跳表的操作就会显得很复杂,如果插入或者删除一个节点的话,那么就会打乱每两个节点升高一层这样的结构,所以这样的结构还是出现问题了,所以也有了下一步的优化。
  • 跳表的设计为了避免这样的问题出现,做了一个大胆的处理,不严格要求对应的比例关系,直接每次插入节点的时候随机一个高度插入,这样插入和删除某个节点的时候,就不用考虑周围节点的高度变化了。
    在这里插入图片描述

跳表的性能分析

因为跳表在插入节点的时候,是随机一个节点的高度进行插入的,所以会指定一个最高的高度,并且会设计一个概率函数去制定每次插入的节点的高度是多少,并且计算高度的这段伪函数如下:
O(log n)
在Redis中跳表的实现中两个值的参数分别为:

p = 1/4;            //概率
maxLevel = 32;      //最高高度

通过上面的伪代码,我们可以看出来,高度越高的节点,出现的概率越小,因为random()这个区间的分布为0~1,而p = 1/4,只有当random()<p的时候,才能让高度增加,而每次都将是1/4的n次方(n代表的是出现到几),概率是非常小的。

所以也有了下面这个概率的公式,为取到固定高度的公式:在这里插入图片描述

跳表的实现

其中删除节点和插入节点的时候比较复杂.
首先,我们先看操作的节点信息:

template<class T>
struct SkipNode
{
	T data;
	vector<SkipNode*> _vNext;

	SkipNode(int val, int level)
		:_vNext(level, nullptr)
		, data(val)
	{}
};

1.因为是跳表操作,所以每次增删的时候,都必须知道增加节点的前一个节点和后一个节点,此时要用数组保存起来,为了方便后面进行修改:

//寻找插入或删除节点时要修改的节点
	vector<Node*> _Findpre(const T& val)
	{
		vector<Node*> ret(_head->_vNext.size(),_head);  //提前开好空间,然后赋值

		Node*  p= _head;
		int level = _head->_vNext.size() - 1;
		while (level >= 0)
		{
			if (p->_vNext[level] == nullptr || p->_vNext[level]->data >= val)  //往下跳
			{
				ret[level] = p;   //保存插入节点直接相连的前面个节点
				level--;          //这块注意一定要赋值,不能给push_back,因为push_back后的结果
			}                     //会让原来水平的方向颠倒,因为我这个是level是--。
			else
			{
				p = p->_vNext[level];                 //往后跳
			}

		}
		return ret;
    }

2.插入节点:插入节点是有些没法,但是只要在插入节点的找到该节点插入的位置,并且插入节点的高度,并且将需要修改的节点保存起来,然后进行按层插入即可,如下:

/插入节点
	bool Insert(T val)
	{
		if (_head == nullptr) //如果调表为空,直接插入
		{
			Node* p = new Node(val, 1);
			_head = p;
			return true;
		}

		size_t h = Gethight();
		Node* node = new Node(val, h);
		vector<Node*> pre = _Findpre(val); //寻找插入节点时需要修改的值

		if (h > _head->_vNext.size()) //如果新产生的节点高度大于根节点高度
		{
			_head->_vNext.resize(h,nullptr);  //修改根节点高度,那么赋值应该还是根节点的data
			pre.resize(h,_head);
		}
		for (size_t i = 0; i < h; ++i)     //插入节点
		{
			node->_vNext[i] = pre[i]->_vNext[i];
			pre[i]->_vNext[i] = node;
		}

		return true;
	}

3.删除操作也一样,只需要找到删除节点的前一个节点,就可以进行操作了:

//删除节点
	bool erase(T val)
	{
		if (_head == nullptr || FindVal(val) == false)  //节点不存在表中
		{
			return false;
		}

		//找到节点删除
		vector<Node*> pre = _Findpre(val);
		Node* del = pre[0]->_vNext[0];

		for (int i = 0; i < del->_vNext.size(); ++i)
		{
			pre[i]->_vNext[i] = del->_vNext[i];
		}
		delete del;

		//如果说删除的节点是存在的最高节点,那么头节点的高度要降
		int i = _head->_vNext.size()-1;
		while (i > 0)
		{
			if (_head->_vNext[i] == nullptr)
			{
				--i;
			}
			else
			{
				break;
			}
		}
		_head->_vNext.resize(i+1);
		return true;
	}

4.整体代码如下:

#include<iostream>
#include<vector>
using namespace std;
template<class T>
struct SkipNode
{
	T data;
	vector<SkipNode*> _vNext;

	SkipNode(int val, int level)
		:_vNext(level, nullptr)
		, data(val)
	{}
};

template<class T>
class SkipTable
{
	typedef struct SkipNode<T> Node;
public:
	SkipTable()
		:_head(nullptr)
		,_maxLevel(32)
		, _p(0.25)
	{}
public:
	//获取该新节点的高度
	size_t Gethight()
	{
		size_t level = 1;
		// rand() ->[0, RAND_MAX]之间                        //经典取随机数算法
		while (rand() <= RAND_MAX * _p && level < _maxLevel) //获取多个随机数,手机随机数的大小小于
		{                                                    //_maxLevel的数量
			++level;
		}
		return level;
	}
	//查找节点是否存在跳表中
	bool FindVal(const T& val)        
	{
		if (_head == nullptr)
		{
			return false;
		}
		Node* p = _head;
		
		int level = p->_vNext.size()-1;  //头节点的高度(也代表者跳表中存在节点的最高高度)
		while (level >= 0)              
		{
			if (p->_vNext[level] == nullptr || p->_vNext[level]->data > val)  //往下跳
			{
				level--;
			}
			else if (p->_vNext[level]->data == val)
			{
				return true;
			}
			else
			{
				p = p->_vNext[level];                 //往后跳
			}
		}

		return false;
	}
	//寻找插入或删除节点时要修改的节点
	vector<Node*> _Findpre(const T& val)
	{
		vector<Node*> ret(_head->_vNext.size(),_head);  //提前开好空间,然后赋值

		Node*  p= _head;
		int level = _head->_vNext.size() - 1;
		while (level >= 0)
		{
			if (p->_vNext[level] == nullptr || p->_vNext[level]->data >= val)  //往下跳
			{
				ret[level] = p;   //保存插入节点直接相连的前面个节点
				level--;          //这块注意一定要赋值,不能给push_back,因为push_back后的结果
			}                     //会让原来水平的方向颠倒,因为我这个是level是--。
			else
			{
				p = p->_vNext[level];                 //往后跳
			}

		}
		return ret;
	}
	//插入节点
	bool Insert(T val)
	{
		if (_head == nullptr) //如果调表为空,直接插入
		{
			Node* p = new Node(val, 1);
			_head = p;
			return true;
		}

		size_t h = Gethight();
		Node* node = new Node(val, h);
		vector<Node*> pre = _Findpre(val);

		if (h > _head->_vNext.size()) //如果新产生的节点高度大于根节点高度
		{
			_head->_vNext.resize(h,nullptr);  //修改根节点高度,那么赋值应该还是根节点的data
			pre.resize(h,_head);
		}
		for (size_t i = 0; i < h; ++i)     //插入节点
		{
			node->_vNext[i] = pre[i]->_vNext[i];
			pre[i]->_vNext[i] = node;
		}

		return true;
	}
	//删除节点
	bool erase(T val)
	{
		if (_head == nullptr || FindVal(val) == false)  //节点不存在表中
		{
			return false;
		}

		//找到节点删除
		vector<Node*> pre = _Findpre(val);
		Node* del = pre[0]->_vNext[0];

		for (int i = 0; i < del->_vNext.size(); ++i)
		{
			pre[i]->_vNext[i] = del->_vNext[i];
		}
		delete del;

		//如果说删除的节点是存在的最高节点,那么头节点的高度要降
		int i = _head->_vNext.size()-1;
		while (i > 0)
		{
			if (_head->_vNext[i] == nullptr)
			{
				--i;
			}
			else
			{
				break;
			}
		}
		_head->_vNext.resize(i+1);
		return true;
	}
	void Print()
	{
		for (int i = _head->_vNext.size()-1; i >= 0; --i)
		{
			Node* p = _head;
			while (p)
			{
				cout << p->data << "->";
				p = p->_vNext[i];
			}
			cout << endl;
		}
	}
private:
	Node* _head;
	size_t _maxLevel;
	float _p;
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值