欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

跳跃表(skip list)基本原理及C/C++实现

程序员文章站 2024-02-29 12:23:10
...

1、基本原理

不想好好写字走的freestyle......其实可以直接看代码,我的注释还是很详细的,后面也有说明性的插图。

跳跃表(skip list)基本原理及C/C++实现

跳跃表(skip list)基本原理及C/C++实现

后面关于时间复杂度的分析证明没有贴(总之我们都知道它性能棒棒哒就行了)。

2、C/C++实现

比较符合MIT公开课的实现是这篇博文:跳跃表实现

而其他多数人都是抄袭这个实现:另一种实现

下面是自己的实现,因为最难的操作就是insert,所以目前只写了insert:

#include<stdlib.h>
#include<time.h>
#include<iostream>
#include<vector>

using namespace std;

#define Tail false // 表示抛硬币得到反面
#define Face true// 表示抛硬币得到正面
typedef int ElementType;// 定义存储元素类型

// 定义链表节点
typedef struct Node
{
	ElementType value;// 节点中存储的数据
	// 三个方向的指针,横向是单链表,纵向是双链表
	Node* next;	
	Node* up;
	Node* down;
}Node;

// 跳跃表结构定义
typedef struct SkipList
{
	int level;// 记录当前的跳跃表有几层
	vector<Node*> heads; // 管理头节点,便于以后从最高层往最底层查找
}SkipList;

// 建立一个只含头节点的表
SkipList* createSkipList()
{
	SkipList* ptrSkipList=new SkipList;
	ptrSkipList->level=1;
	// 创建第一个节点
	Node* head=new Node;
	head->value=INT_MIN;// 头节点的数据统一设置为-∞
	// 初始化三个指针为空
	head->next=nullptr;
	head->up=nullptr;
	head->down=nullptr;
	ptrSkipList->heads.emplace_back(head);// 将节点指针加入vector
	srand(time(0));// 播撒随机种子
	return ptrSkipList;
}

// 产生随机数,决定插入的元素要不要往上走
bool flipCoin()
{
	if(rand()%2==1)
		return Face;
	else
		return Tail;
}

// 跳跃表的插入
void insert(SkipList* ptrSkipList,ElementType value)
{
	// 首先插入最底层的链表,注意插入时要保证有序性
	Node* ptrNewNode=new Node;
	Node* p,*q;// 辅助指针
	ptrNewNode->value=value;
	ptrNewNode->next=nullptr;
	ptrNewNode->up=nullptr;
	ptrNewNode->down=nullptr;
	// 寻找插入的合适位置
	for(p=ptrSkipList->heads[0]->next,q=ptrSkipList->heads[0];(p!=nullptr)&&(value>p->value);p=p->next,q=q->next)
	{
		// 注意不要把p=p->next;写成p++;,会出事的
	}
	// 应该插在q后面
	q->next=ptrNewNode;
	ptrNewNode->next=p;
	// 抛硬币,如果是反面,结束,否则向上走
	int currentLevel=0;// 记录当前的层数,方便后面的连接,0在这里是一个没有意义的数,最小的level是1
	Node* move=ptrNewNode;// 辅助指针move,每次promote指向相同的节点
	// 开始抛硬币,如果是正面就上升(promote)
	while(flipCoin()!=Tail)
	{	
		cout<<"插入"<<value<<"时得到正面"<<endl;
		currentLevel++;
		// 新建一个节点包含这个元素
		Node* promote=new Node;
		promote->value=value;
		// 和下面含有相同元素的节点互连
		move->up=promote;
		promote->down=move;
		promote->next=nullptr;
		move=promote;// 更新move
		// 如果没有头节点,新建一个头节点,每次头节点也要promote
		if(ptrSkipList->heads.size()<currentLevel+1)
		{
			Node* head=new Node;
			head->value=INT_MIN;
			head->next=nullptr;
			// 连接两个头节点
			ptrSkipList->heads[currentLevel-1]->up=head;
			head->down=ptrSkipList->heads[currentLevel-1];		
			ptrSkipList->heads.emplace_back(head);// 加入新的头节点
			ptrSkipList->level++;// 更新层数
		}
		// 最后连接相同level的节点
		Node* r,*s;// 辅助指针
		for(r=ptrSkipList->heads[currentLevel]->next,s=ptrSkipList->heads[currentLevel];(r!=nullptr)&&(value>r->value);r=r->next,s=s->next)
		{
			// promote的时候也要保证元素之间的顺序,从小到大排列
		}
		s->next=move;
		move->next=r;
	}
	cout<<value<<"插入完毕"<<endl;
}

int main(int argc, char* argv[])
{
	SkipList* mylist=createSkipList();
	insert(mylist,4);
	insert(mylist,3);
	insert(mylist,0);
	insert(mylist,13);
	insert(mylist,2);
	insert(mylist,50);
	insert(mylist,7);
	cout<<"跳跃表层数:"<<mylist->level<<endl;
	for(int i=mylist->level-1;i>=0;i--)
	{
		cout<<"第"<<i+1<<"层元素是:";
		for(Node* p=mylist->heads[i];p!=nullptr;p=p->next){
			cout<<p->value<<" ";
		}
		cout<<endl;
	}
}

写完以后才发现横向上其实也需要两个指针。。。这样查找算法就比较贴近公开课里讲的了,看起来更优雅,不过一个指针其实也是可以的,反正万变不离其宗,将就吧。。

这是测试结果:

跳跃表(skip list)基本原理及C/C++实现

做了一个可视化:

跳跃表(skip list)基本原理及C/C++实现

真美啊~