跳表 | 基本概念 | 代码实现

1.跳表的基本概念

跳表的本质是一种查找结构,一般查找问题的解法分为两个大类:一个是基于各种平衡树,一个是基于哈希表,跳表比较的特殊,它独成一派。跳表是基于有序链表的基础上发展而来的,普通链表查找只能一个一个往下跳,而跳表能一次跳过好几个结点,这就是它查找效率高的原因。 例如:
如果只有一层那么查找就是逐个遍历,效率就是O(n)
在这里插入图片描述

如果为两个相邻结点添加第二层指向
在这里插入图片描述
甚至是添加第三层的指向
在这里插入图片描述
每层的结点个数呈现2:1的对应关系,查找就是从最高层开始,依次往下查找,这个过程类型二分查找,效率可以来到O(log n)。但是如果严格维持这种2:1的对应关系,插入删除节点都需要重新调整,插入删除效率直接就降到O(n)。所以跳表采用随机化结点的层数,来控制查找插入删除的时间复杂度近似为O(log n)。怎么计算可以参考博客

2.跳表的结构

跳表的结点有多层,通过vector<SkiplistNode*> 来存储下一个结点的指针。初始化跳表时是带头结点的,它的层数要求是最高的,它开始只有它自己,默认给一层,后面插入的时候如果有结点的层数高于它,需要调整。

struct SkiplistNode
{	
	int _data;
	vector<SkiplistNode*> _nextV;

	SkiplistNode(int data,int level)
		:_data(data)
		,_nextV(level,nullptr)
	{}
};

class Skiplist
{
	typedef SkiplistNode Node;
private:
	Node* _head;
	int _maxLevel = 32;
	double _p = 0.25;

public:
	Skiplist()
	{
		_head = new Node(-1,1);
	}
};

int main()
{
	Skiplist list;	
	return 0;
}
3.跳表的增删改查

看看自己的代码有没有问题可以在leetcode上提交代码:题目链接
1)跳表的查找: 跳表中的元素是有序的,在理解跳表查找前先来看一下一个有序的单链表是如何查找元素的。
在这里插入图片描述
查找有序单链表,cur表示当前结点,如果cur的值data等于待查找的值target说明找到了。可如果cur 的下一个结点的值data大于target,说明target目标值不在链表中,或者是链表遍历到结尾还是找不到也说明target目标值不在链表中,这就是有序单链表的查找过程。
有了有序单链表的查找的基础,来看跳表的查找就简单了。
在这里插入图片描述
1、cur表示当前结点、next表示下一个结点、data表示结点的数据。
2、要从最高层找起,直到遍历到最低的那一层。
3、如果target 等于 cur的 data说明找到了。
4、如果target 大于 next 的 data,向右找。但是要保证next 不能为NULL。如找19而当前cur 的data是7,它的next 是NULL。不代表找不到,而是要到下一层找。
5、如果next的date 大于target 说明target可能在cur 和 next之间,往下一层找。

bool search(int target)
{
	Node* cur = _head;
	int level = cur->_nextV.size() -1;

	while(level >= 0)
	{
		if(cur->_nextV[level] && cur->_nextV[level]->_data < target)
		{
			cur = cur->_nextV[level];
		}
		else if(cur->_nextV[level] == nullptr || cur->_nextV[level]->_data > target)
		{
			level--;
		}
		else {return true;}
	}
	return false;
}

2)跳表的插入: 类比单链表的从中间的某个位置插入。需要找到前一个位置prev。跳表要找的是prevV,前结点指针列表。
在这里插入图片描述
1、找到prevV
2、创建一个结点newNode,先让newNode->_nextV[i] 逐个指向 prevV[i]->_nextV[i]指向的结点。再让prevV[i]->_nextV[i]指向newNode。
3、这里创建newNode层数是随机的,需要根据概率计算。这里参照radis中给定的概率和最大层数。
4、如果插入节点的层数高于头结点,需要调整头结点的层数。

vector<Node*> findPervV(int num)
{
	Node* cur = _head;
	int level = _head->_nextV.size() -1;
	vector<Node*> prevV(level + 1,nullptr);

	while(level >= 0)
	{			
		if(cur->_nextV[level] != nullptr && cur->_nextV[level]->_data < num)
		{
			cur = cur->_nextV[level];
		}
		else if(cur->_nextV[level] == nullptr || cur->_nextV[level]->_data >= num)
		{
			prevV[level] = cur;
			level--;
		}
	}
	return prevV;	
}

void add(int num)
{
	int n = randomLevel();

	int level = _head->_nextV.size() -1;
	vector<Node*> prevV = findPervV(num);
	
	// 调整头结点的层数
	if(n > _head->_nextV.size())
	{
		_head->_nextV.resize(n,nullptr);
		prevV.resize(n,_head);
	}

	Node* newNode = new Node(num,n);	
	for(int i = 0;i < n;i++)
	{			
		newNode->_nextV[i] = prevV[i]->_nextV[i];
		prevV[i]->_nextV[i] = newNode;
	}
}

int randomLevel()
{
	Node* cur = _head;
	srand(time(NULL));

	int level= 1;
	// rand的区间范围为[0,RAND_MAX]
	while(rand() <= RAND_MAX*_p && level < _maxLevel) { level++;}

	return level;
}

3)跳表的删除:
1、要到前结点列表prevV
2、根据当前结点的层数,循环指向prevV[i]->_nextV[i] = del->_nextV[i]

bool erase(int num)
{
	vector<Node*> prevV = findPervV(num);

	if(prevV[0]->_nextV[0] == nullptr || prevV[0]->_nextV[0]->_data != num)
	{
		return false;
	}
	else
	{
		Node* del = prevV[0]->_nextV[0];
		
		// 当前节点的层数
		for(int i = 0;i < del->_nextV.size();i++)
		{
			prevV[i]->_nextV[i] = del->_nextV[i];
		}
		delete del;

		int index = _head->_nextV.size() -1;
		
		// _head->_nextV[index] == nullptr说明最高结点删除了,调整头结点
		while (index >= 0) 
		{
			if(_head->_nextV[index] == nullptr) {index--;}
			else {break;}	
		}

		return true;
	}
}

4)跳表的打印: 这里按照单链表的思路打印,如果只看第一层那么跳表就像是有序的单链表。注意跳表是带头指针的,所以直接从_head->nextV[0]开始。

void print()
{
	Node* cur = _head->_nextV[0];

	while(cur != nullptr)
	{			
		cout << cur->_data << "->";	
		cur = cur->_nextV[0];
	}
	cout << "NULL" << endl;
}
4.完整代码
#include <vector>
#include <iostream>
#include <time.h>
#include <stdlib.h>


using namespace std;

struct SkiplistNode
{	
	int _data;
	vector<SkiplistNode*> _nextV;

	SkiplistNode(int data,int level)
		:_data(data)
		,_nextV(level,nullptr)
	{}
};


class Skiplist
{
	typedef SkiplistNode Node;
private:
	Node* _head;
	int _maxLevel = 32;
	double _p = 0.25;

public:
	Skiplist()
	{
		_head = new Node(-1,1);
	}

	bool search(int target)
	{
		Node* cur = _head;
		int level = cur->_nextV.size() -1;

		while(level >= 0)
		{
			if(cur->_nextV[level] && cur->_nextV[level]->_data < target)
			{
				cur = cur->_nextV[level];
			}
			else if(cur->_nextV[level] == nullptr || cur->_nextV[level]->_data > target)
			{
				level--;
			}
			else {return true;}
		}
		return false;
	}

	vector<Node*> findPervV(int num)
	{
		Node* cur = _head;
		int level = _head->_nextV.size() -1;
		vector<Node*> prevV(level + 1,nullptr);

		while(level >= 0)
		{			
			if(cur->_nextV[level] != nullptr && cur->_nextV[level]->_data < num)
			{
				cur = cur->_nextV[level];
			}
			else if(cur->_nextV[level] == nullptr || cur->_nextV[level]->_data >= num)
			{
				prevV[level] = cur;
				level--;
			}
		}
		return prevV;	
	}

	void add(int num)
	{
		int n = randomLevel();

		int level = _head->_nextV.size() -1;
		vector<Node*> prevV = findPervV(num);

		if(n > _head->_nextV.size())
		{
			_head->_nextV.resize(n,nullptr);
			prevV.resize(n,_head);
		}

		Node* newNode = new Node(num,n);	
		for(int i = 0;i < n;i++)
		{			
			newNode->_nextV[i] = prevV[i]->_nextV[i];
			prevV[i]->_nextV[i] = newNode;
		}
	}

	int randomLevel()
	{
		Node* cur = _head;
		srand(time(NULL));

		int level= 1;
		while(rand() <= RAND_MAX*_p && level < _maxLevel) { level++;}

		return level;
	}

	bool erase(int num)
	{
		vector<Node*> prevV = findPervV(num);

		if(prevV[0]->_nextV[0] == nullptr || prevV[0]->_nextV[0]->_data != num)
		{
			return false;
		}
		else
		{
			Node* del = prevV[0]->_nextV[0];

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

			int index = _head->_nextV.size() -1;

			while (index >= 0) 
			{
				if(_head->_nextV[index] == nullptr) {index--;}
				else {break;}	
			}

			return true;
		}
	}


	void print()
	{
		Node* cur = _head->_nextV[0];

		while(cur != nullptr)
		{			
			cout << cur->_data << "->";	
			cur = cur->_nextV[0];
		}
		cout << "NULL" << endl;
	}

};

int main()
{
	Skiplist list;	
	list.add(1);
	list.add(3);
	list.add(6);

	cout << list.search(6) << endl;

	list.erase(9);
	list.erase(6);

	cout << list.search(6) << endl;

	list.print();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值