这道题目是Redis,LevelDB中使用到的经典数据结构。通过这道题目可以理解跳表的插入,删除,查找过程。这里提供的代码是标准的算法实现版本,通过使用常规链表指针和一个向下的指针来实现。该实现比较直观简单,便于理解。
跳表的直观理解图示,最底层是完整链表,上层在底层链表的基础上,随机将一些节点向上复制,连接起来, 节点的结构如下:
class Node {
public:
Node *right;
Node *down;
int val;
Node(Node *right, Node *down, int val): right(right), down(down), val(val) {}
};
查找过程,从上到下查找,每次如果能向左,就向左走,如果不能向左,那么就向下。
bool search(int target) {
auto p = head;
while(p != nullptr){
while(p->right != nullptr && p->right->val < target){
p = p->right;
}
if(p->right != nullptr && p->right->val == target){
return true;
}
p = p->down;
}
return false;
}
删除操作和查找操作类似,因为我们在查找的时候,总是通过前一个节点来找右边的满足要求的节点,所以删除也一样,每一层都要删除。
bool erase(int num) {
auto p = head;
bool found = false;
while(p != nullptr){
while(p->right != nullptr && p->right->val < num){
p = p->right;
}
if(p->right != nullptr && p->right->val == num){
found = true;
p->right = p->right->right;
}
p = p->down;
}
return found;
}
然后最难是插入操作
我们需要首先记录下来,每一层第一个小于要插入元素的节点。然后从最下层开始插入,最下面一层一定要插入,依次向上以每一层0.5^(i)的概率插入。
我们要注意,如果最上面多出了一层,那么此时要创建一个新的头节点
void add(int num) {
auto p = head;
pathList.clear();
while(p != nullptr){
while(p->right != nullptr && p->right->val < num){
p = p->right;
}
pathList.push_back(p);
p = p->down;
}
bool isInsert = true;
Node *down = nullptr;
while(isInsert && !pathList.empty()){
auto node = pathList.back();
pathList.pop_back();
node->right = new Node(node->right, down, num);
down = node->right;
isInsert = (rand() & 1) == 0; // %50概率向上插入
}
if(isInsert){
head = new Node(new Node(NULL, down, num), head, -1);
}
}