1、背景
有序链表是一种常用的数据结构,具备链表的优点,不会占用较大的连续内存并且可以实现快速插入,适合频繁插入、更新的应用场景。但是随着节点数目的增多,O(n)的查询复杂度使得性能下降。为了优化查询性能,在原有的数据链表基础上,增加一层至多层逻辑链表,这样的数据结构就成为跳表,可以大大加速有序链表的查询性能。

2、代码实现
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
struct LinkNode
{
uint64_t key;
uint64_t value;
LinkNode* next = nullptr;
};
struct SkipLinkNode
{
uint64_t key;
int count;
LinkNode* pointer = nullptr;
SkipLinkNode* next = nullptr;
};
class SkipLinkList
{
public:
SkipLinkList(int skipStep = 128)
: _skipStep(skipStep)
{
}
~SkipLinkList()
{
LinkNode* node = _listHeader;
while (node != nullptr)
{
LinkNode* toDelNode = node;
node = node->next;
delete toDelNode;
}
SkipLinkNode* skipNode = _skipListHeader;
while (skipNode != nullptr)
{
SkipLinkNode* toDelNode = skipNode;
skipNode = skipNode->next;
delete toDelNode;
}
}
public:
void insert(uint64_t key, uint64_t value)
{
// fisrt insert ?
if (_listHeader == nullptr)
{
_listHeader = new LinkNode;
_listHeader->key = key;
_listHeader->value = value;
_listHeader->next = nullptr;
_skipListHeader = new SkipLinkNode;
_skipListHeader->count = 1;
_skipListHeader->key = key;
_skipListHeader->next = nullptr;
_skipListHeader->pointer = _listHeader;
return;
}
// exist key?
LinkNode *pFindNode = find_i(key);
if (pFindNode)
{
pFindNode->value = value;
return ;
}
// key not exist, insert
LinkNode *pToIstNode = new LinkNode;
pToIstNode->key = key;
pToIstNode->value = value;
pToIstNode->next = NULL;
// find lower skipNode
SkipLinkNode* pSkipNode = findLowerSkipNode(key);
// cannot find, must insert into head;
if (pSkipNode == nullptr)
{
pToIstNode->next = _listHeader;
_listHeader = pToIstNode;
_skipListHeader->pointer = _listHeader;
_skipListHeader->key = _listHeader->key;
_skipListHeader->count += 1;
pSkipNode = _skipListHeader;
}
else
{
LinkNode* pLowerLinkNode = findLowerLinkNode(pSkipNode->pointer, key);
pToIstNode->next = pLowerLinkNode->next;
pLowerLinkNode->next = pToIstNode;
pSkipNode->count += 1;
}
adjustSkipNode(pSkipNode);
return;
}
bool find(uint64_t key, uint64_t &value)
{
LinkNode *pFindNode = find_i(key);
if (pFindNode == nullptr)
return false;
value = pFindNode->value;
return true;
}
void dump()
{
int iNum = 0;
cout << "SkipNode:" << endl;
SkipLinkNode *pSkipNode = _skipListHeader;
while (pSkipNode)
{
cout << iNum++ << ": " << pSkipNode->key << ", " << pSkipNode->count << endl;
pSkipNode = pSkipNode->next;
}
cout << endl;
iNum = 0;
cout << "LinkNode:" << endl;
LinkNode *pLinkNode = _listHeader;
while (pLinkNode)
{
cout << iNum++ << ": " << pLinkNode->key << ", " << pLinkNode->value << "; ";
pLinkNode = pLinkNode->next;
}
cout << endl << endl;
}
private:
LinkNode* find_i(uint64_t key)
{
SkipLinkNode *pSkipNode = findLowerSkipNode(key);
if (pSkipNode == nullptr)
return nullptr;
LinkNode *pLinkNode = pSkipNode->pointer;
while (pLinkNode && pLinkNode->key < key)
{
pLinkNode = pLinkNode->next;
}
return (pLinkNode && pLinkNode->key == key) ? pLinkNode : nullptr;
}
SkipLinkNode* findLowerSkipNode(uint64_t key)
{
if (_skipListHeader == nullptr)
return nullptr;
if (key < _skipListHeader->key)
return nullptr;
else if (key == _skipListHeader->key)
return _skipListHeader;
SkipLinkNode* pPrev = _skipListHeader;
SkipLinkNode* pCur = pPrev->next;
while (pCur && pCur->key <= key)
{
pPrev = pCur;
pCur = pCur->next;
}
return pPrev;
}
LinkNode* findLowerLinkNode(LinkNode* pNodeHeader, uint64_t key)
{
//assert(pNodeHeader && pNodeHeader->key < key);
LinkNode *pCur = pNodeHeader->next;
while (pCur && pCur->key < key)
{
pNodeHeader = pCur;
pCur = pNodeHeader->next;
}
return pNodeHeader;
}
void adjustSkipNode(SkipLinkNode *pSkipNode)
{
// no need adjust, return
if (pSkipNode == nullptr || pSkipNode->count <= _skipStep)
return;
LinkNode* pLinkNode = pSkipNode->pointer;
int iCount = 1;
while (iCount <= _skipStep)
{
pLinkNode = pLinkNode->next;
++iCount;
}
int iExtraCount = pSkipNode->count - _skipStep;
pSkipNode->count = _skipStep;
SkipLinkNode *pChgSkipNode = nullptr;
if (pSkipNode->next == nullptr)
{
pChgSkipNode = new SkipLinkNode;
pChgSkipNode->key = pLinkNode->key;
pChgSkipNode->count = iExtraCount;
pChgSkipNode->next = nullptr;
pChgSkipNode->pointer = pLinkNode;
pSkipNode->next = pChgSkipNode;
return ;
}
else
{
pChgSkipNode = pSkipNode->next;
pChgSkipNode->key = pLinkNode->key;
pChgSkipNode->count += iExtraCount;
pChgSkipNode->pointer = pLinkNode;
adjustSkipNode(pChgSkipNode);
}
return;
}
int length()
{
int iLen = 0;
SkipLinkNode *pSkipHeader = _skipListHeader;
while (pSkipHeader)
{
iLen += pSkipHeader->count;
pSkipHeader = pSkipHeader->next;
}
return iLen;
}
LinkNode* _listHeader = nullptr;
SkipLinkNode* _skipListHeader = nullptr;
int _skipStep;
};
int main()
{
SkipLinkList list;
vector<uint64_t> keys;
for (uint64_t i = 0; i < 512; i++)
{
keys.push_back(i);
}
random_shuffle(keys.begin(), keys.end());
// insert
for (auto key : keys)
{
list.insert(key, key + 1);
}
list.dump();
for (auto key: keys)
{
uint64_t value;
bool ret = list.find(key, value);
if (!ret || value != (key + 1))
{
cout << "error!" << endl;
return -1;
}
}
// replace
list.insert(0, 100);
uint64_t value;
bool ret = list.find(0, value);
if (!ret || value != 100)
{
cout << "find replace key error!" << endl;
return -1;
}
// not exist key
ret = list.find(1000, value);
if (ret)
{
cout << "find not exist error!" << endl;
return -1;
}
list.dump();
return 0;
}
3、代码分析
上述代码主要实现了拥有一层逻辑跳表的插入、查询与更新的升序列表功能,其中skipStep表示跳表节点可覆盖数据节点的最大个数。
一、查询算法
1、遍历逻辑跳表,找到最后一个skipNode,满足skipNode->key <= key
2、如果找到满足条件的skipNode,进入步骤三;否则返回未找到
3、从skipNode指向的linkNode开始向后遍历,直到linkNode==NULL 或者 linkNode->key >= key为止
4、判断linkNode->key是否等于key,如果等于,查找到数据节点,返回值;否则指定的key不存在此列表中。
二、插入算法
1、遍历逻辑跳表,找到最后一个skipNode,满足skipNode->key <= key
2、如果找到满足条件的skipNode,进入步骤三;否则说明列表中不存在插入元素key小的数据节点,直接在数据链表的头部插入,同时修改跳表头节点的覆盖数据节点个数加1,进入步骤5
3、从skipNode指向的linkNode开始向后遍历,直到linkNode==NULL 或者 linkNode->key > key为止,记录linkNode的前驱节点为pPrev
4、如果pPrev的key等于插入元素的key,替换值, 返回;否则,在pPrev节点后插入元素,将skipNode节点的覆盖数据节点个数加1,进入步骤5
5、判断当前skipNode所覆盖的数据节点个数是否大于skipStep,如果不大于,直接返回;否则,进入步骤6
6、从skipNode指向的数据节点linkNode开始,向后遍历skipStep个节点nextLinkNode,修改skipNode覆盖数据节点的个数为skipStep
7、判断skipNode节点的next是否为空,如果为空,在skipNode后插入一个新的跳表节点,设置key为nextLinkNode的key,覆盖数据节点为1,返回;否则进入步骤8
8、修改skipNode节点的后继节点,将其数据节点指针指向nextLinkNode,其覆盖的数据节点加1,然后将其作为当前skipNode,进入步骤5
本文介绍了一种通过在有序链表上构建逻辑跳表的数据结构,以提高查询效率。代码实现了跳表的插入、查找和调整操作,适用于频繁插入和更新的应用场景。

969

被折叠的 条评论
为什么被折叠?



