跳表 笔记

跳表

在一个有n个元素的有序链表中,查找一个元素的时间复杂度为O(n),但如果把所有第偶数个元素取出来形成一个上级链表,那么复杂度就变成了O(n/2)。如果再在这个上级链表中,把所有第偶数个元素取出形成一个再上级链表,重复这个操作,直到最上级链表中只有一个元素,此时变相地形成了一棵二叉树,复杂度也逐级地降为了O(logn)

在实际的链表中,并不是固定地取第偶数个元素作为上级链表的元素,而是通过一个概率来分配,且一个元素的级数在其插入时动态形成。

设最底级的链表为0级链表,对于一个新插入的元素来说,从第0级开始,以一个概率P来决定它是否是上一级的元素,这个概率P同时也等于上一级链表元素个数与下一级链表元素个数的比值。具体的说,先产生一个0-1之间的随机数,那么这个随机数小于等于概率P的概率就是P。所以,如果产生的随机数<=P,那么这个元素就也属于上一级链表,即1级链表,然后继续生成一个0-1之间的随机数,如果随机数<=P,这个元素的级数继续增加,即2级链表。但如果随机数>P,那么这个元素的级数就停在该级链表上,停止增加,此时这个元素的级也就被成功分配。

需要注意的是,虽然概率很小,但是在极其幸运的时候,一个元素的级数可能很大,这并不利于跳表的性能。所以,在实现跳表时,要设置一个最大级数maxLevel,最大值为log1/p(n)-1。

根据课程要求,实现的是字典的跳表,所以代码实现时,元素是通过pair型构建的。


代码实现

节点

数据域+指针域+构造函数

需要注意的是,因为一个元素会出现在多级链表中,所以这里的指针域是一个指针数组,注意指针数组的空间大小要大于元素所属的级数,因为每个元素都包含在0级链表中。指针数组里记录的是每一级的指向下一个节点的指针,但是在创建节点的时候只需要分配空间就好,实际的指针存储在插入时实现。

template <class K,class E>
struct skipNode
{
	typedef pair<const K,E> pairType;
	
	pairType element;	//存储数对 
	skipNode<K,E> **next;	//指针数组 
	
	skipNode(const pairType& thePair,int size)
		:element(thePair){next=new skipNode<K,E>*[size];} 
};
构造函数

在生成跳表时,要确定的参数:
概率P即prob
跳表的最大关键字largeKey,最大关键字存放在头、尾节点用来简化查找;
跳表允许的最大元素个数maxPairs,因为程序中使用最大元素个数来代替计算最大级数的公式中的n,所以跳表中的元素个数不超过最大元素个数可以保持较好性能;

最大级数maxLevel通过公式log1/p-1来获得。
头节点headerNode需要能够存放每级链表的指针大小的空间,所以其指针数组大小为maxLevel+1;但是尾节点tailNode不需要指针域,所以尾节点的数组大小为0。last数组是用来存储查找元素时的每一级链表的最后位置,所以大小也是最大级数+1。
cutOff是用来辅助确定元素的级数的。这里用prob*RAND_MAX来赋值,而在级的分配函数中,用cutOffrand()比较,实际就是比较一个0-1的随机数和概率prob

初始时,跳表为空,所以头节点的每一级指针都指向尾节点。

template<class K,class E>
skipList<K,E>::skipList(K largeKey,int maxPairs,float prob)
{//构造函数,关键字小于largeKey且数对个数size最多为maxPairs,0<prob<1 
	cutOff=prob*RAND_MAX;	//产生随机小数  RAND_MAX <stdlib.h> 
	maxLevel=(int)ceil(logf((float)maxPairs)/logf(1/prob))-1;
	levels=0;	//初始化级数 
	dSize=0;	//跳表中已有的元素个数
	tailKey=largeKey;
	
	//生成头节点、尾节点、数组last
	pair<K,E> tailPair;
	tailPair.first=tailKey;
	
	headerNode=new skipNode<K,E>(tailPair,maxLevel+1);
	tailNode=new skipNode<K,E>(tailPair,0); 
	last=new skipNode<K,E>*[maxLevel+1];
	
	//链表为空时,任意级链表中的头节点都指向尾节点 
	for(int i=0;i<=maxLevel;i++)
		headerNode->next[i]=tailNode; 
} 
析构函数
template<class K,class E>
skipList<K,E>::~skipList()
{
	delete headerNode;
	delete tailNode;
	delete []last;
}
查找函数find

实现查找元素的操作。先与跳表的最大关键字比较,只有小于最大关键字的元素才能在跳表中被找到。然后从头节点开始,从最上级链表(levels,当前最大的非空链表)开始往下搜。在每一级链表找最大的小于查找元素的节点,那么当跳出while循环时已经遍历到了0级链表,如果元素存在,那么beforeNode->next[0]就是查找元素的节点,但如果beforeNodenext[0]不等于查找元素,说明该元素不存在。

template <class K,class E>
void skipList<K,E>::find(const K& b)	//b为查找树对的关键字 
{
	if(b>=tailKey)	//不存在
	{
		cout<<"-1\n"; 
		return;	
	} 
	//beforeNode是关键字为b的节点的前一个
	skipNode<K,E>* beforeNode= headerNode;
	for(int i=levels;i>=0;i--)	//从上级链表到下级链表  levels 当前最大非空链表 
	{//跟踪i级链表指针
		while(beforeNode->next[i]->element.first<b)
		{
			//cout<<beforeNode->next[i]->element.first<<endl;
		 	beforeNode=beforeNode->next[i];			
		}

	} 	
	//检查下一个节点的关键字是否为b 
	if(beforeNode->next[0]->element.first==b)
		cout<<beforeNode->next[0]->element.second<<"\n";		
	else
	{
		cout<<"-1\n";	//无匹配数对 
		return;
	}
}
查找函数search

插入和删除之前,都要先找到元素的位置,所以都需要使用到查找函数。

从头节点开始,从最上级链表(levels,当前最大的非空链表)开始往下搜。在每一级链表找最大的小于查找元素的节点,在last数组中记录这个节点指针,那么当跳出while循环时已经遍历到了0级链表,而beforeNodenext[0]就是查找的元素。

template <class K,class E>
skipNode<K,E>* skipList<K,E>::search(const K& theKey) const
{//搜索theKey,并保存每一级链表最后查看的节点位置保存在数组last中
	//返回可能含有theKey的节点位置
	//指针beforeNode,指向可能与theKey匹配节点的前一个节点
	skipNode<K,E>* beforeNode=headerNode;
	for(int i=levels;i>=0;i--)
	{
		while(beforeNode->next[i]->element.first<theKey)
		 	beforeNode=beforeNode->next[i];
		last[i]=beforeNode;
	}
	return beforeNode->next[0];
	
} 
级的分配

在插入元素时,要在插入前,先实现对新元素的级的分配操作。

这里的rand()返回的是一个0-RAND_MAX之间的随机数,而这个随机数小于等于cutOff的概率就是prob

template <class K,class E>
int skipList<K,E>::level() const
{//产生一个随机级号,该级号<=maxLevel
	int lev=0;
	while(rand()<=cutOff)
		lev++;
	return(lev<=maxLevel)?lev:maxLevel; 	
}
插入函数

先通过查找函数search看插入元素是否已经存在在跳表中了,如果存在,只做更新操作,如果不存在,再做插入操作。

首先通过level()函数分配新元素的级theLevel,如果新元素的级大于当前最大的非空链表级数,那么当前最大的非空链表级数要被更新,还需要维护last数组,如果不对last[theLevel]赋值,可能会指向非法内存。最后,进行节点的插入操作,即newNode->next[i]=last[i]->next[i]last[i]->next[i]=newNode,不要忘了跳表中的元素数量要加一。

template <class K,class E>
void skipList<K,E>::insert(const pair<const K,E>& thePair)
{
	if(thePair.first>=tailKey)
	{
		cout<<"关键值太大 ,异常处理"<<endl; 
	} 
	//查看theKey是否存在 
	skipNode<K,E>* theNode=search(thePair.first);
	//theKey已存在 
	if (theNode->element.first==thePair.first)
	{//更新数对 
    	theNode->element.second=thePair.second;
    	return;
	} 
	//不存在,确定新节点的级 
	int theLevel=level();		//新节点的级
	if (theLevel>levels)	//使theLevel<=levels+1  levels当前最大非空链表 
	{
    	theLevel=++levels;		//theLevel=levels+1 
    	last[theLevel]=headerNode;	//最新的一级只有headerNode有指针 
	}
	//产生新节点,并将新节点插入在theNode后面 
	skipNode<K,E>* newNode=new skipNode<K,E>(thePair,theLevel+1);
	for (int i=0;i<=theLevel;i++)
	{//插入到第i级链 
    	newNode->next[i]=last[i]->next[i];
    	last[i]->next[i]=newNode;	//维护last[i] 
	}
	dSize++;
	return;
}
删除函数

同样的,先判断元素的合法性,再通过search函数寻找删除函数的位置,因为search函数并没有比较元素的值,它返回的只是一个如果该元素存在,那么这个元素理论上的位置,所以在删除函数中,还需要判断返回的节点是不是要删除的节点。

如果是,那么与插入函数类似的,对每一级做删除节点的操作,即last[i]->next[i]=theNode->next[i],但前提是,theNode是要删除的节点。

最后,判断删除节点后最上级链表是否为空,如果为空,那么levels需要被修改。别忘了修改dsize

template <class K,class E>
void skipList<K,E>::erase(const K& theKey)
{//删除关键字为theKey的数对
	if(theKey>=tailKey)	//关键字太大
	{
		cout<<"-1\n"; 
		return;	
	} 
	//查看是否有匹配的数对
	skipNode<K,E>* theNode=search(theKey);
	if(theNode->element.first!=theKey)		//不匹配 
	{
		cout<<"-1\n"; 
		return;	
	} 
	E ele=theNode->element.second;
	//维护last数组 
	for(int i=0;i<=levels&&last[i]->next[i]==theNode;i++)
		last[i]->next[i]=theNode->next[i];
	//更新链表级
	while(levels>0&&headerNode->next[levels]==tailNode)
		levels--;
	delete theNode;
	dSize--; 
	cout<<theKey<<" "<<ele<<"\n";
	return;
}
删除最值函数

先从0级链表获取最大值/最小值,然后类似于删除函数的做法,删除最值即可。

//删除最小值
template <class K,class E>
void skipList<K,E>::eraseMin()	//headerNode的下一个 
{
	skipNode<K,E>* theNode=headerNode->next[0];	//要删除的数对
	K theKey=theNode->element.first; 
	
	if(theNode==tailNode)		//不匹配 
	{
		cout<<"-1\n"; 
		return;	
	} 
	E ele=theNode->element.second;
	//维护last数组,指向后一个 
	for(int i=0;i<=levels&&last[i]->next[i]==theNode;i++)
		last[i]->next[i]=theNode->next[i];
	//更新链表级
	while(levels>0&&headerNode->next[levels]==tailNode)
		levels--;
	delete theNode;
	dSize--; 
	cout<<theKey<<" "<<ele<<"\n";
	return;
}
//删除最大值
template <class K,class E>
void skipList<K,E>::eraseMax()
{
	skipNode<K,E>* theNode=headerNode;
	for(int i=levels;i>=0;i--)
	{
		while(theNode->next[i]->element.first<tailKey)
		 	theNode=theNode->next[i];
		last[i]=theNode;
	}
	K theKey=theNode->element.first;
	E ele=theNode->element.second;
	//维护last数组 
	for(int i=0;i<=levels&&last[i]->next[i]==theNode;i++)
		last[i]->next[i]=theNode->next[i];
	//更新链表级
	while(levels>0&&headerNode->next[levels]==tailNode)
		levels--;
	delete theNode;
	dSize--; 
	cout<<theKey<<" "<<ele<<"\n";
	return;
}

复杂度分析

跳表的优势就在于查找、插入、删除的复杂度都是O(logn),最坏性能为O(n),媲美二叉树,但其最坏性能只与概率有关系,且其概率很低。


完整代码

#include <iostream>
#include <algorithm>	//pair头文件 
#include <stdlib.h>		//RAND_MAX头文件 
#include <string>
#include <math.h>
using namespace std;

template <class K,class E>
struct skipNode
{
	typedef pair<const K,E> pairType;
	
	pairType element;	//存储数对 
	skipNode<K,E> **next;	//指针数组 
	
	skipNode(const pairType& thePair,int size)
		:element(thePair){next=new skipNode<K,E>*[size];} 
};

template <class K,class E>
class skipList
{
	public:
		skipList(K, int maxPairs=10000, float prob=0.5);
		~skipList();
		void find(const K& b);	//b查找元素关键字,输出元素值,不存在输出-1 
		void insert(const pair<const K,E>& thePair);	//插入<b,c>,不输出 
		void erase(const K& theKey);	//b删除元素关键字,不存在输出-1 
		void eraseMin();	//输出最小元素关键字和值,并删除 
		void eraseMax();	//输出最大元素关键字和值,并删除 		
	private:
		float cutOff;	//确定层数
/**/	int level() const;	//产生级数 
		int levels;	//当前最大的非空链表 
		int dSize;	//字典的数对个数 
		int maxLevel;	//语序的最大链表层数 
		skipNode<K,E>* search(const K&) const; 
		K tailKey;	//最大关键字 
		skipNode<K,E>* headerNode;	//头节点指针 
		skipNode<K,E>* tailNode;	//尾节点指针 
		skipNode<K,E>** last; 	//last[i]表示i层的最后节点 
};
template<class K,class E>
skipList<K,E>::skipList(K largeKey,int maxPairs,float prob)
{//构造函数,关键字小于largeKey且数对个数size最多为maxPairs,0<prob<1 
	cutOff=prob*RAND_MAX;	//产生随机小数  RAND_MAX <stdlib.h> 
	maxLevel=(int)ceil(logf((float)maxPairs)/logf(1/prob))-1;
	levels=0;	//初始化级数 
	dSize=0;
	tailKey=largeKey;
	
	//生成头节点、尾节点、数组last
	pair<K,E> tailPair;
	tailPair.first=tailKey;
	
	headerNode=new skipNode<K,E>(tailPair,maxLevel+1);
	tailNode=new skipNode<K,E>(tailPair,0); 
	last=new skipNode<K,E>*[maxLevel+1];
	
	//链表为空时,任意级链表中的头节点都指向尾节点 
	for(int i=0;i<=maxLevel;i++)
		headerNode->next[i]=tailNode; 
} 
template<class K,class E>
skipList<K,E>::~skipList()
{
	delete headerNode;
	delete tailNode;
	delete []last;
}
template <class K,class E>
void skipList<K,E>::find(const K& b)	//b为查找树对的关键字 
{
	if(b>=tailKey)	//不存在
	{
		cout<<"-1\n"; 
		return;	
	} 
	//beforeNode是关键字为b的节点的前一个
	skipNode<K,E>* beforeNode= headerNode;
	for(int i=levels;i>=0;i--)	//从上级链表到下级链表  levels 当前最大非空链表 
	{//跟踪i级链表指针
		while(beforeNode->next[i]->element.first<b)
		{
			//cout<<beforeNode->next[i]->element.first<<endl;
		 	beforeNode=beforeNode->next[i];			
		}

	} 	
	//检查下一个节点的关键字是否为b 
	if(beforeNode->next[0]->element.first==b)
		cout<<beforeNode->next[0]->element.second<<"\n";		
	else
	{
		cout<<"-1\n";	//无匹配数对 
		return;
	}
}
template <class K,class E>
skipNode<K,E>* skipList<K,E>::search(const K& theKey) const
{//搜索theKey,并保存每一级链表最后查看的节点位置保存在数组last中
	//返回可能含有theKey的节点位置
	//指针beforeNode,指向可能与theKey匹配节点的前一个节点
	skipNode<K,E>* beforeNode=headerNode;
	for(int i=levels;i>=0;i--)
	{
		while(beforeNode->next[i]->element.first<theKey)
		 	beforeNode=beforeNode->next[i];
		last[i]=beforeNode;
	}
	return beforeNode->next[0];
	
} 
template <class K,class E>
int skipList<K,E>::level() const
{//产生一个随机级号,该级号<=maxLevel
	int lev=0;
	while(rand()<=cutOff)
		lev++;
	return(lev<=maxLevel)?lev:maxLevel; 	
}
template <class K,class E>
void skipList<K,E>::insert(const pair<const K,E>& thePair)
{
	if(thePair.first>=tailKey)
	{
		cout<<"关键值太大 ,异常处理"<<endl; 
	} 
	//查看theKey是否存在 
	skipNode<K,E>* theNode=search(thePair.first);
	//theKey已存在 
	if (theNode->element.first==thePair.first)
	{//更新数对 
    	theNode->element.second=thePair.second;
    	return;
	} 
	//不存在,确定新节点的级 
	int theLevel=level();		//新节点的级
	if (theLevel>levels)	//使theLevel<=levels+1  levels当前最大非空链表 
	{
    	theLevel=++levels;		//theLevel=levels+1 
    	last[theLevel]=headerNode;	//最新的一级只有headerNode有指针 
	}
	//产生新节点,并将新节点插入在theNode后面 
	skipNode<K,E>* newNode=new skipNode<K,E>(thePair,theLevel+1);
	for (int i=0;i<=theLevel;i++)
	{//插入到第i级链 
    	newNode->next[i]=last[i]->next[i];
    	last[i]->next[i]=newNode;	//维护last[i] 
	}
	dSize++;
	return;
}
template <class K,class E>
void skipList<K,E>::erase(const K& theKey)
{//删除关键字为theKey的数对
	if(theKey>=tailKey)	//关键字太大
	{
		cout<<"-1\n"; 
		return;	
	} 
	//查看是否有匹配的数对
	skipNode<K,E>* theNode=search(theKey);
	if(theNode->element.first!=theKey)		//不匹配 
	{
		cout<<"-1\n"; 
		return;	
	} 
	E ele=theNode->element.second;
	//维护last数组 
	for(int i=0;i<=levels&&last[i]->next[i]==theNode;i++)
		last[i]->next[i]=theNode->next[i];
	//更新链表级
	while(levels>0&&headerNode->next[levels]==tailNode)
		levels--;
	delete theNode;
	dSize--; 
	cout<<theKey<<" "<<ele<<"\n";
	return;
}
template <class K,class E>
void skipList<K,E>::eraseMin()	//headerNode的下一个 
{
	skipNode<K,E>* theNode=headerNode->next[0];	//要删除的数对
	K theKey=theNode->element.first; 
	
	if(theNode==tailNode)		//不匹配 
	{
		cout<<"-1\n"; 
		return;	
	} 
	E ele=theNode->element.second;
	//维护last数组,指向后一个 
	for(int i=0;i<=levels&&last[i]->next[i]==theNode;i++)
		last[i]->next[i]=theNode->next[i];
	//更新链表级
	while(levels>0&&headerNode->next[levels]==tailNode)
		levels--;
	delete theNode;
	dSize--; 
	cout<<theKey<<" "<<ele<<"\n";
	return;
}
template <class K,class E>
void skipList<K,E>::eraseMax()
{
	skipNode<K,E>* theNode=headerNode;
	for(int i=levels;i>=0;i--)
	{
		while(theNode->next[i]->element.first<tailKey)
		 	theNode=theNode->next[i];
		last[i]=theNode;
	}
	K theKey=theNode->element.first;
	E ele=theNode->element.second;
	//维护last数组 
	for(int i=0;i<=levels&&last[i]->next[i]==theNode;i++)
		last[i]->next[i]=theNode->next[i];
	//更新链表级
	while(levels>0&&headerNode->next[levels]==tailNode)
		levels--;
	delete theNode;
	dSize--; 
	cout<<theKey<<" "<<ele<<"\n";
	return;
}

int main()
{
	int n;
	long m;
	cin>>n>>m;
	skipList<long,string> sl(1000000001);
	for(int i=0;i<n;i++)
	{
		int p;
		string s;
		cin>>p>>s;
		pair<int,string> t={p,s};
		sl.insert(t);
	}
	for(long i=0;i<m;i++)
	{
		int a,b;
		string c;
		pair<int,string> tmp;
		cin>>a;
		switch(a)
		{
			case 1:
				//查找 
				cin>>b;
				sl.find(b);
				break;
			case 2:
				//插入 
				cin>>b>>c;
				tmp.first=b;
				tmp.second=c;
				sl.insert(tmp);
				break;
			case 3:
				//删除 
				cin>>b;
				sl.erase(b);
				break;
			case 4:
				//删除最小元素 
				sl.eraseMin();
				break;
			case 5:
				//删除最大元素 
				sl.eraseMax();
				break;
		}
	}
	return 0;
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在 PostgreSQL 数据库中,并没有内置的跳表(Skip List)索引实现。PostgreSQL 提供了多种索引类型,如B树索引、哈希索引、GiST索引和GIN索引等,但没有直接支持跳表的索引类型。 B树索引是 PostgreSQL 中最常用的索引类型之一。它适用于范围查询和等值查询,并且可以保持数据有序性。B树索引在处理数据块的平衡性和查询效率方面具有很好的性能。 除了B树索引之外,PostgreSQL 还提供了其他类型的索引用于特定的场景。例如,哈希索引适用于等值查询,可以提供快速的哈希查找;GiST 索引(通用搜索树)和 GIN 索引(通用倒排索引)适用于全文搜索和复杂的匹配查询。 虽然 PostgreSQL 不提供内置的跳表索引实现,但是你可以使用扩展或自定义索引实现跳表的功能。通过编写自定义插件或使用第三方扩展,你可以在 PostgreSQL 中实现跳表索引。这需要一定的开发工作,并且需要充分测试和评估性能。 需要注意的是,自定义实现的跳表索引可能会受到 PostgreSQL 内核版本更新的影响,并且可能无法享受到 PostgreSQL 内置索引的一些优化和支持。 总之,PostgreSQL 并没有内置的跳表索引实现,但提供了其他类型的索引,如B树索引、哈希索引、GiST索引和GIN索引等,用于满足不同的查询需求。如果需要使用跳表索引,你可以考虑自定义实现或使用第三方扩展。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值