跳表是一个随机化的数据结构,实质就是一种可以进行二分查找的有序链表。跳表是有序链表,当一个新节点加入跳表时,会按照一定的概率分布获得一个索引等级,高等级的产生几率小于低等级。每个节点有一组next指针,各个等级的节点构成各个等级的链表,每个节点不同的next指针用于不同等级的链表,高等级的节点也都会出现在低等级的链表中,即高等级链表是低等级链表的子集。
例如二分跳表,所有节点都出现在0级链表中,而只有1/2的节点出现在1级链表中,有1/8的节点出现在2级链表中……设相邻等级高等级与低等级出现率的比值为p,(0<p<1),链表的最大长度为N,则该链表所允许出现的最大等级为-1+log(1/p)N。
跳表具有一个首节点和一个尾节点,首节点也是各级链表的首节点,不存放有实际意义的元素;尾节点则也是各级链表的尾节点,存放跳表最大的关键字。有实际意义的数据则插入到首节点和尾节点之间。
搜索:从首节点出发,先在高等级链表中搜索,当在高等级链表中无法前进时,逐渐降低等级直至0级,从而找到需要搜索的位置。
插入:搜索插入位置,给新节点分配等级,然后将这个节点插入到小于等于这个等级的所有链表中。
删除:搜索删除位置,将这个节点从包含它的所有链表(小于等于节点等级的链表)中删除。
由以上插入和删除操作可以看出,每个节点的等级是完全随机产生的,而且节点等级确定下来,就再也不会发生改变,即不需要在任何与该节点无关的插入和删除操作之后对等级进行维护。所以在一番插入和删除操作之后,跳表的实际连接结构会变得十分错综复杂,跳表时间复杂度上的优越性仅体现在平均时间上,而最坏时间则与一般有序链表无异(比如在偶然情况下,所有节点的等级都一样,那么此时的跳表就是一个一般的有序链表)。
代码
#include<iostream>
using namespace std;
template<class K,class E>
struct skipNode
{
pair<K, E> element;//前者表示关键字,后者表示数据值
skipNode<K, E>** next;//存放一组next指针
skipNode(const pair<K, E>& thePair, int levelSize)
{
element = thePair;
next = new skipNode<K, E>*[levelSize];
}
};
template<class K, class E>
class skipList
{
private:
int currentLevel;//当前链表中节点的最大等级
int maxLevel;//允许出现的最大等级
int size;//节点个数
float cutOff;//在生成新节点时用于划分随机数
K maxKey;//关键字取值上限
skipNode<K, E>* headerNode;//每级链表的共同首节点
skipNode<K, E>* tailNode;//每级链表的共同尾节点
skipNode<K, E>** lastNodeArray;//临时存放搜索到某节点时每级链表最后的节点
int getLevel()const//随机生成新节点的等级
{
int lv = 0;
while (rand() <= cutOff)
lv++;
return (lv <= maxLevel) ? lv : maxLevel;//保证等级不超过链表允许的最大等级
}
skipNode<K, E>* search(const K& theKey)const//寻找某节点,将搜索到该节点位置时每级链表最后的节点存放到lastNodeArray中
{
skipNode<K, E>* beforeNode = headerNode;
for (int i = currentLevel; i >= 0; i--)//把beforeNode移动到搜索位置的前一个节点
{
while (beforeNode->next[i]->element.first < theKey)
beforeNode = beforeNode->next[i];
lastNodeArray[i] = beforeNode;
}
return beforeNode->next[0];
}
public:
skipList(K theMaxKey, int maxPairs, float prob)//最大关键字、链表最大容量(仅用于计算最大等级)、相邻等级出现概率比值
{
cutOff = prob * RAND_MAX;//cutOff是对全体rand()生成的随机数的划分
maxLevel = (int)ceil(logf((float)maxPairs) / logf(1 / prob)) - 1;//计算在最大容量和出现概率确定的情况下,链表允许出现的最大等级(从0级开始)
currentLevel = 0;
size = 0;
maxKey = theMaxKey;
pair<K, E> tailPair;
tailPair.first = maxKey;
headerNode = new skipNode<K, E>(tailPair, maxLevel + 1);//首节点仅充当各级链表的起点,其中存储的数对无意义
tailNode = new skipNode<K, E>(tailPair, 0);//尾结点存放数对关键字的最大值,用于判断是否已经遍历完整个链表
lastNodeArray = new skipNode<K, E>*[maxLevel + 1];
for (int i = 0; i <= maxLevel; i++)//初始链表为空,各级链表首节点直接指向尾结点
headerNode->next[i] = tailNode;
}
void find(const K& theKey)const//按关键字查找
{
if (theKey >= maxKey)
cout << "关键字大于链表允许的最大值" << endl;
skipNode<K, E>* theNode = search(theKey);
if (theNode->element.first == theKey)
cout << theNode->element.second << endl;
else
cout << "该节点不存在" << endl;
}
void insert(const pair<const K, E>& thePair)//插入
{
if (thePair.first >= maxKey)
{
cout << "关键字大于链表允许的最大值" << endl;
return;
}
skipNode<K, E>* theNode = search(thePair.first);//插入位置前一个节点
if (theNode->element.first == thePair.first)//如果关键字已被占用,则覆盖其数据值
{
theNode->element.second = thePair.second;
cout << "覆盖:(" << theNode->element.first << "," << theNode->element.second << ")" << endl;
return;
}
//若关键字未被占用
int newNodeLevel = getLevel();//获取新节点等级
if (newNodeLevel > currentLevel)//若新节点等级大于当前链表中所有节点的等级,则将新节点等级设置为当前链表最大等级加一,并修改当前链表最大等级
{
currentLevel++;
newNodeLevel = currentLevel;
lastNodeArray[currentLevel] = headerNode;
}
skipNode<K, E>* newNode = new skipNode<K, E>(thePair, newNodeLevel + 1);
cout << "插入:(" << newNode->element.first << "," << newNode->element.second << ") " << "等级:"<<newNodeLevel<<endl;
for (int i = 0; i <= newNodeLevel; i++)//将新节点插入到从0级到newNodeLevel级的链表中
{
newNode->next[i] = lastNodeArray[i]->next[i];
lastNodeArray[i]->next[i] = newNode;
}
size++;
}
void erase(const K& theKey)//删除
{
if (theKey >= maxKey)
{
cout << "关键字大于链表允许的最大值" << endl;
return;
}
skipNode<K, E>* theNode = search(theKey);
if (theNode->element.first != theKey)
{
cout << "需要删除的节点不存在" << endl;
return;
}
cout << "删除:(" << theNode->element.first << "," << theNode->element.second << ")" << endl;
for (int i = 0; i <= currentLevel && lastNodeArray[i]->next[i] == theNode; i++)//从需要删除的节点所在的每级链表中删除该节点
lastNodeArray[i]->next[i] = theNode->next[i];
while (currentLevel > 0 && headerNode->next[currentLevel] == tailNode)//删除之后可能导致当前链表所有节点的最大等级降低,需要维护
currentLevel--;
delete theNode;
size--;
}
};