为有序链表增加额外的向前指针的链表叫作跳表(skip list)。它采用随机技术来决定链表的哪些节点应增加向前指针以及增加多少个指针。基于这种随机技术,跳表的查找、插入、删除的平均时间复杂度为O(logN)。然而在最坏情况下,时间复杂度却渐近等于N。
关于跳表级数的确定
在规则的跳表结构中,第i级链表的数对个数与第i-1级链表的数对个数之比是一个分数p,因此属于i-1级链表的数对同时属于第i级链表的概率就为p(形如二叉树之类的结构其p则为0.5)。我们采用一个随机数生成器不断生成0-1的实数来模拟得到它的级数,假如生成的数≤p,则级数加一,直到生成的数大于p。
这种随机方法的缺点是,可能某些新插入的节点被分配的级数特别大,远远超过log1/pN,其中N为字典数目的最大预期数目。为避免这种情况,我们设定一个级数的上线maxLevel,最大值为ceil(log1/pN)-1。另外一个缺点是新插入的节点被分配的级数即使小于maxLevel,但是也远大于当前跳表拥有的链表层数,因此如果发生这种情况我们将其调整为当前跳表拥有的链表层数+1。
复杂度
当字典有n个数对时,查找、插入、删除的操作的时间复杂度均O(n+maxLevel)。在最坏的情况下,也就是只有一个maxLevel级数对,余下所有数对均在0级链表上。i>0时,在i级链表上花费的时间为O(maxLevel)。尽管最坏情况下的性能较差,但跳表不失作为一种有价值的数据描述,因为查找、插入、删除的平均复杂度均为O(logN)。如Redis数据库就采用了跳表来实现存储。相较于红黑树,跳表的性能差不多而且实现更为简单。至于空间复杂度,最坏情况下每个记录都是maxLevel级,都需要maxLevel+1个指针。因此除了需要存储n个数对的空间,还需要存储O(nmaxLevel)个指针的空间。不过一般情况下(规则的跳表),一级链表有np个指针,二级链表有np2个指针,i级链表有npi个链表。当p=0.5时,平均空间需求(加上n个数对的指针)大约是2n个指针空间。虽然最坏情况下的空间需求比较大,但是平均的空间需求并不大。
具体的C++实现
字典的数据结构
#pragma once
//字典结构的抽象描述
template <typename K,typename V>
class dictionary {
public:
virtual ~dictionary(){ }
virtual bool empty() const = 0;
//返回字典中数对的数目
virtual size_t size() const = 0;
//返回匹配数对的指针
virtual std::pair<const K, V>* find(const K& _key) const = 0;
virtual void erase(const K& _key) = 0;
virtual void insert(const std::pair<const K, V>& _pair) = 0;
};
指针节点
#pragma once
template <typename K,typename V>
struct skipNode
{
typedef std::pair<const K, V> pairType;
pairType element;
skipNode<K, V>** next; //指针数组 next[i]表示第i级链表指针
//对一个lev级链表数对,其size值为lev+1
skipNode(const pairType& _element,size_t size)
:element(_element),next(new skipNode<K,V>* [size]){ }
//使用RAII来管理每个节点的next指针域资源
~skipNode()
{
delete[] next;
cout << "delete next[] array!" <<
endl;
}
};
跳表实现
#pragma once
#ifndef skipList_H
#define skipList_H
#include <string>
using std::string;
#include <iostream>
using std::cout; using std::cin; using std::endl; using std::ends;
#include <cmath>
#include <random>
#include <sstream>
#include <ctime>
#include "dictionaryADT.h"
#include "skipNode.h"
#include "..//..//..//ch06/ChainList/ChainList/illegalParameterValue.h"
template <typename K,typename V>
class skipList :public dictionary<K, V> {
public:
skipList(const K& _MAX_KEY,int _MAX_PAIRS =100,float _prob=0.5); //2^7=128 初始化为6-1层链表
~skipList();
bool empty() const { return dict_size == 0; }
size_t size() const { return dict_size; }
std::pair<const K, V>* find(const K& _Key)const;
void insert(const std::pair<const K,V>& _pair);
void erase(const K& _Key);
void output(std::ostream& os)const;
private:
skipNode<K, V>* headerNode; //头节点
skipNode<K, V>* tailNode; //尾节点
skipNode<K, V>** last; //在搜寻指定Key的过程中,保存每一层level的最后一个节点的位置
//用在删除和插入节点时,对涉及到的节点的指针更新
size_t dict_size; //字典中数对的个数
int levels; //当前字典中最大的节点含有的链表层数
int MAX_LEVEL; //字典允许的最大链表层数
float probability; //概率 用于模拟判断链表的级数
//即i级链表的数对个数比上i-1级链表的数对个数 形如二叉树则probability=0.5
K MAX_KEY; //最大关键字
private:
skipNode<K, V>* search(const K& _Key) const; //搜索给定Key 并将每层链表遍历过的节点存入last数组中
int get_level()const; //为新插入的节点模拟级数的分配
};
template <typename K,typename V>
skipList<K, V>::skipList(const K& _MAX_KEY, int _MAX_PAIRS, float _prob) {
dict_size = 0;
levels = 0;
probability = _prob;
//虽然代码中没有对容器中的数对上限做出要求,但是小于这个值性能会更好
//因为我们的MAX_LEVEL是根据这个值计算出来的
MAX_LEVEL = std::ceil(std::logf(static_cast<float>(_MAX_PAIRS)) / std::logf(1 / probability)) - 1;
cout << "initial MAX_LEVEL= " << MAX_LEVEL << endl;
MAX_KEY = _MAX_KEY;
//初始化头尾节点,头节点的数据域可以为任意值
std::pair<K, V> initialPair;
initialPair.first = MAX_KEY;
headerNode = new skipNode<K, V>(initialPair, MAX_LEVEL + 1);
tailNode = new skipNode<K, V>(initialPair, 0);
last = new skipNode<K, V>* [MAX_LEVEL + 1];
//初始化将头节点的每层链表都指向尾节点
for (int i = 0; i <= MAX_LEVEL;++i) {
headerNode->next[i] = tailNode;
}
}
template <typename K,typename V>
skipList<K, V>::~skipList() {
skipNode<K, V>* nextNode;
while (headerNode!=tailNode) {
nextNode = headerNode->next[0];
cout << "delete node " <<headerNode->element.first<< endl;
delete headerNode;
headerNode = nextNode;
}
delete tailNode;
delete[] last;
}
template <typename K,typename V>
std::pair<const K, V>* skipList<K, V>::find(const K& _Key)const {
//查找关键字为_Key的数对
if (_Key >= MAX_KEY)
return nullptr;//大于等于了给定的Key上限 无可匹配的数对
//指针beforeNode在查找完后指向_Key的上一个位置
skipNode<K, V>* beforeNode = headerNode;
//从上级链表到下级链表依次寻找
for (int i = levels; i >=0 ; --i) {
while (beforeNode->next[i]->element.first < _Key)
beforeNode = beforeNode->next[i];
}
//虽然可以在找到关键字==_Key的时候停止 但是这种检验是否相等的额外操作一般来说是不必要的
//因为大部分相等的数对都是出现在0级链表中 所以我们直接在循环完所有层的链表后进行判断
if (beforeNode->next[0]->element.first == _Key)
return &beforeNode->next[0]->element;
//否则没找到
return nullptr;
}
template <typename K,typename V>
int skipList<K, V>::get_level()const {
//级的分配方法
static std::default_random_engine e(std::time(0));//使用time(0)作为种子获取随机数引擎
std::uniform_real_distribution<double> u(0, 1);//限定在0-1之间取double值
int currentLev=0;
while (u(e) < probability)
++currentLev;
//如果取得的级数过大 限制为max_level
return (currentLev <= MAX_LEVEL) ? currentLev:MAX_LEVEL;
}
template <typename K,typename V>
skipNode<K, V>* skipList<K, V>::search(const K& _Key) const{
//搜索关键字_Key,把每一级链表中要查看的最后一个节点都存在last中
//返回包含关键字_key的节点
skipNode<K, V>* beforeNode = headerNode;
for (int i = levels; i >= 0;--i) {
while (beforeNode->next[i]->element.first < _Key)
beforeNode = beforeNode->next[i];
last[i] = beforeNode;
}
return beforeNode->next[0];
}
template <typename K,typename V>
void skipList<K, V>::insert(const std::pair<const K,V>& _pair) {
//将数对插入字典中 如果存在就覆盖它的值
//要插入的数对Key值超过上限
if (_pair.first >= MAX_KEY) {
std::ostringstream oss;
oss << "Key = " << _pair.first << " of inserted pair must be < " << MAX_KEY;
throw illegalParameterValue(oss.str());
}
//查找_pair数对是否存在
skipNode<K, V>* theNode = search(_pair.first);
if (theNode->element.first == _pair.first) {//如果存在 则更新值
theNode->element.second = _pair.second;
return;
}
//若不存在 确定新节点所在的链表级数
int theLevel = get_level();
cout << "get level of node " << _pair.first << " = " << theLevel;
if (theLevel > levels) {//如果级数大于了当前字典中最高的级数 则让它的级数为levels+1
theLevel = ++levels;
last[theLevel] = headerNode;
cout << " , but make it = " << theLevel;
}
cout << endl;
//在节点theNode之后插入新节点 并更新节点指针
skipNode<K, V>* newNode = new skipNode<K, V>(_pair, theLevel + 1);
for (int i = 0; i <= theLevel;++i) {//逐级更新
newNode->next[i] = last[i]->next[i];
last[i]->next[i] = newNode;
}
++dict_size;
return;
}
template<typename K,typename V>
void skipList<K, V>::erase(const K& _Key) {
//删除关键字为_Key的数对
if (_Key >= MAX_KEY) {
cout << "Key= " << _Key << " ,while must be < " << MAX_KEY << endl;
return;
}
//检查是否有匹配的数对
skipNode<K, V>* theNode = search(_Key);
if (theNode->element.first != _Key) {
cout << "Key= " << _Key << " is not in the dictionary" << endl;
return;
}
//删除该节点
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] == theNode)
--levels;
delete theNode;
--dict_size;
}
template <typename K,typename V>
void skipList<K, V>::output(std::ostream& os) const{
skipNode<K, V>* theNode;
//输出链表层数情况
for (int i = levels; i >= 0;--i) {
theNode = headerNode;
os << "level " << i << " : ";
while (theNode!=tailNode) {
os << "(" << theNode->element.first << "," << theNode->element.second << ")-->";
theNode = theNode->next[i];
}
//输出尾节点
os << "(" << theNode->element.first << "," << theNode->element.second << ")" << endl;
}
os << endl;
}
template <typename K,typename V>
std::ostream& operator<<(std::ostream& os,const skipList<K,V>& list) {
list.output(os);
return os;
}
#endif