遍历一遍链表时间复杂度为O(N)
如果每相邻两个节点上高一层,增加一个节点,让指针指向下一个节点。这样所有的新增指针又会组成一个新的链表,但包含的节点的个数只有原来的一半,遍历链表的速度也会提高一半。以此类推后分成多层
eg:查找节点17过程如下:
这种思想类似于二分查找,每次查找都会排除一般的节点。时间复杂度为O(logN)
为了保证这种跳表结构,插入与删除需要进行特殊操作。这里不在要求严格的格式匹配,在插入节点时候随机出一个层数,这样每次插入和删除都不需要考虑其他节点的层数,方便处理。(每个节点有几层个数不确定)
同时,为了保证效率
设多一层的概率为p
节点层数至少为1。而大于1的节点层数,满足一个概率分布。
节点层数恰好等于1的概率为1-p。
节点层数大于等于2的概率为p,而节点层数恰好等于2的概率为p(1-p)。
节点层数大于等于3的概率为p ^ 2,而节点层数恰好等于3的概率为p ^ 2×(1-p)。节点层数大于等于4的概率为p ^ 3,而节点层数恰好等于4的概率为p^3×(1-p)。
层数越高,概率越低。
一个节点的平均层数的公式为:
跳表的平均时间复杂度为O(logN)
具体时间复杂度的分析,相关参考文章:
#pragma once
#include
#include
#include
#include
struct SkiplistNode {
int val;
std::vector<SkiplistNode*>_nextV;//节点有多少层
SkiplistNode(int _val, int leve)
:val(_val), _nextV(leve, nullptr)
{}
};
class Skiplist {
typedef SkiplistNode Node;
private:
Node* _head;//头节点
size_t _maxleve = 32;//每个节点最大的层数
double _p = 0.25;//设每个节点多一层的概率为p
int _randomLeve() {
//产生节点的随机层数
int leve = 1;
while (rand() < (RAND_MAX * _p) && leve < _maxleve) {
//rand()<(RAND_MAX*_p的概率为0.25
leve += 1;
}
return leve;
}
//查找某个节点的所有前一个指针
std::vector<Node*>_findPrevNode(int num) {
//首先要查找插入的位置,前一个节点指针
Node* cur = _head;
int curleve = cur->_nextV.size() - 1;
std::vector<Node*>prev(curleve + 1, _head);//保存前一个节点的指针
while (curleve >= 0) {//如果还没有找到节点的最后一层时都要继续循环
//节点向下移动时,更新前一个节点指针数组
if (cur->_nextV[curleve] && cur->_nextV[curleve]->val < num) {
//跳表向右走,跳到下一个节点
//特殊情况,下一个节点为空时,要向跳表也要向下移动
cur = cur->_nextV[curleve];
}
else if (cur->_nextV[curleve] == nullptr || cur->_nextV[curleve]->val >= num) {
//更新prev数组,跳表向下走
prev[curleve] = cur;
curleve -= 1;
}
}
return prev;
}
public:
Skiplist() {
//头节点值为-1,开始为第一层
_head = new Node(-1, 1);
srand(time(0));//随机数种子
}
bool search(int target) {
Node* cur = _head;
int curleve = cur->_nextV.size() - 1;
while (curleve >= 0) {//如果还没有找到节点的最后一层时都要继续循环
if (cur->_nextV[curleve] && cur->_nextV[curleve]->val < target) {
//跳表向右走,跳到下一个节点
//特殊情况,下一个节点为空时,要向跳表也要向下移动
cur = cur->_nextV[curleve];
}
else if (cur->_nextV[curleve] == nullptr || cur->_nextV[curleve]->val > target) {
//跳表向下走
curleve -= 1;
}
else {
//找到了这个节点
return true;
}
}
return false;
}
void add(int num) {
//添加数据时可以冗余
std::vector<Node*>prev = _findPrevNode(num);
int newLeve = _randomLeve();
Node* newNode = new Node(num, newLeve);
//如果newLeve超过头节点的最大层数,则选择升高head层数
if (newLeve > _head->_nextV.size()) {
_head->_nextV.resize(newLeve);//避免新增节点导致头节点层数不足
prev.resize(newLeve, _head);//多余的层数指向头节点。
}
//连接前后节点
for (size_t i = 0; i < newLeve; i++) {
newNode->_nextV[i] = prev[i]->_nextV[i];
prev[i]->_nextV[i] = newNode;
}
}
//测试打印每一层跳表
void _Print() {
int leve = _head->_nextV.size();
for (int i = leve - 1; i >= 0; i--) {
Node* cur = _head;
//打印每一层的链表
while (cur != nullptr) {
std::cout << cur->val << "->";
cur = cur->_nextV[i];
}
std::cout << "nullptr" << std::endl;
}
}
bool erase(int num) {
//删除节点,找到到节点的前一个指针,在把要删除节点的所有下一个指针连接起来
std::vector<Node*>prev = _findPrevNode(num);
if (prev[0]->_nextV[0] == nullptr || prev[0]->_nextV[0]->val != num) {
//跳表最下层都找不到这个节点,说明找不到节点
return false;
}
//找到了对应节点
Node* del = prev[0]->_nextV[0];
for (size_t i = 0; i < del->_nextV.size(); i++) {
//连接del节点的每一层前后指针
prev[i]->_nextV[i] = del->_nextV[i];
}
delete del;
//如果删除可以减少跳表的高度,提高搜索效率
int _headLeve = _head->_nextV.size() - 1;
while (_headLeve >= 0) {
if (_head->_nextV[_headLeve] == nullptr) {
_headLeve -= 1;
}
else {
break;
}
}
_head->_nextV.resize(_headLeve + 1);
return true;
}
};
/**
* Your Skiplist object will be instantiated and called as such:
* Skiplist* obj = new Skiplist();
* bool param_1 = obj->search(target);
* obj->add(num);
* bool param_3 = obj->erase(num);
*/
测试代码:
#include"StopWatch.h"
void TestAdd() {
Skiplist sp;
int arr[] = { 1,2,3,4 };
for (auto num : arr) {
sp.add(num);
}
//sp.search(0);
sp._Print();
for (auto num : arr) {
sp.erase(num);
}
}
int main() {
TestAdd();
return 0;
}
运行结果:
跳表与红黑树对比:
相同点:
不同点:
跳表与哈希表对比:
不同点: