跳表
在一个有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
来赋值,而在级的分配函数中,用cutOff
和rand()
比较,实际就是比较一个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]
就是查找元素的节点,但如果beforeNode
的next[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级链表,而beforeNode
的next[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;
}