学过编程的都知道,数据结构中有一种数据结构叫做链表。
它的简单实现如下:
struct Node {
T v;
Node* nxt;
};
struct List {
Node Head;
};
它的简单示意图如下:
链表有什么特点呢?
它一般是与数组比的,为什么呢,因为数组的优点就是链表的缺点,链表的优点就是数组的缺点。它们很有比较性。
链表是不连续的内存,所以新增元素不用改变原来已有元素的内存位置,很方便(跟数组比)。
但是,它访问很慢,因为单纯的链表这种结构访问元素的方式很笨重,就是一个一个访问。然而数组就很快了,由于它是连续的内存,所以能很快算出下一个元素的位置。如果我们需要某个确定索引位置的元素,数组比链表快。
想一下链表访问慢的原因。
是因为它的访问方式决定了它访问很慢,因为它一次访问唯一的一个元素(这个元素一般就是下一个元素)。因为它一个结点只能访问一个其他的结点。
那么,可不可以一次可以访问多个元素呢?
能不能一次访问两个呢,或者一次访问三个?
当然是可以的。
这就是跳表。
下图示意一下什么是跳表。
先看到,跳表的最底层其实就是链表(一个元素接着一个)。然后上面那么一层层的结点,是什么意思呢?
就是跳跃的意思,就是打破单个链表结点一次只能访问一个结点带来的慢速度。
拿a这个节点来说明一下。
(请注意把最底层的结点和它上面一层一层的结点看成一个整体。)
a这个节点可以访问到三个元素,是b,c,e。分别跳了1步,2步,4步。
b这个节点可以访问到一个元素,是c。跳了1步。
c这个节点可以访问到两个元素,是c,e。分别跳了1步,2步。
所以此时我们从头节点访问元素的步骤如下:
访问的节点 | 跳表的路径 | 跳表所需访问元素的个数 | 链表的路径 | 链表所需访问元素的个数 |
---|---|---|---|---|
a | a | 1 | a | 1 |
b | a->b | 2 | a->b | 2 |
c | a->c | 2 | a->b->c | 3 |
d | a->c->d | 3 | a->b->c->d | 4 |
e | a->e | 2 | a->b->c->d->e | 5 |
f | a->e->f | 3 | a->b->c->d->e->f | 6 |
g | a->e->g | 3 | a->b->c->d->e->f->g | 7 |
如上图,相比较而言是不是快了。
跳表的时间复杂度是O(log n),比链表O(n)快多了。
感觉跟树状数组有点像。就是一个是数组,一个是链表。
那么跳表新增一个结点的时候,需要确定它的层数,如何确定新增节点的层数?
我们容易知道,一个跳表,有n/2的结点只有一层,有n/4的结点有两层,有n/8的结点有三层。
根据理论上的统计我们确定一个结点层数的概率就是,对于一个未知结点。它有1/2的可能是一层,1/4的可能是两层,1/8的可能是三层。。。
所以对于一个新增结点,我们随机给层数。
下面是它的简单实现:
/// @file skip_list.cpp
/// @brief wait for check
/// @author zhaolu
/// @version 1.0
/// @date 2020-03-07
/// @copyright Copyright (c) 2020 zhaolu. All Rights Reserved.
#include <string>
#include <random>
#include <iostream>
template<typename T>
class node {
private:
unsigned random_level() const {
std::random_device rd;
std::mt19937 generator(rd());
unsigned level = 1;
for (int i = 1; i < 16; ++i) {
if (generator() % 2 == 1) {
++level;