1、跳跃表的定义
跳跃表(Skip List):增加了向前指针的链表叫做指针。跳表全称叫做跳跃表,简称跳表。跳表是一个随机化的数据结构,实质是一种可以进行二分查找的有序链表。跳表在原有的有序链表上增加了多级索引,通过索引来实现快速查询。跳表不仅能提高搜索性能,同时也可以提高插入和删除操作的性能。
跳表是一个随机化的数据结构,可以被看做是二叉树的一个变种,它在性能上和红黑树、AVL树不相上下。
2、跳跃表的时间复杂度
单链表的查找时间复杂度为:O(n),下面分析下跳表这种数据结构的查找时间复杂度:
我们首先考虑这样一个问题,如果链表里有n个结点,那么会有多少级索引呢?按照上面讲的,每两个结点都会抽出一个结点作为上一级索引的结点。那么第一级索引的个数大约就是n/2,第二级的索引大约就是n/4,第三级的索引就是n/8,依次类推,也就是说,第k级索引的结点个数是第k-1级索引的结点个数的1/2,那么第k级的索引结点个数为:n/2^{k}。
假设索引有h级,最高级的索引有2个结点,通过上面的公式,我们可以得到n/(2^{h}) = 2,从而可得:h = log{2}n - 1。如果包含原始链表这一层,整个跳表的高度就是log_{2}n。我们在跳表中查找某个数据的时候,如果每一层都要遍历m个结点,那么在跳表中查询一个数据的时间复杂度就为:O(m*logn)。
如上面的例子,m=2,所以跳表查找任意数据的时间复杂度为O(logn),这个查找的时间复杂度和二分查找是一样的,但是我们却是基于单链表这种数据结构实现的。不过,天下没有免费的午餐,这种查找效率的提升是建立在很多级索引之上的,即空间换时间的思想。其具体空间复杂度见下文详解。
3、跳跃表的空间复杂度
比起单纯的单链表,跳表就需要额外的存储空间去存储多级索引。假设原始链表的大小为n,那么第一级索引大约有n/2个结点,第二级索引大约有4/n个结点,依次类推,每上升一级索引结点的个数就减少一半,直到剩下最后2个结点,如下图所示,其实就是一个等比数列。
这几级索引结点总和为:n/2 + n/4 + n/8 + ... + 8 + 4 + 2 = n - 2。所以跳表的空间复杂度为O(n)。也就是说如果将包含n个结点的单链表构造成跳表,我们需要额外再用接近n个结点的存储空间。
4、跳跃表的改进
其实从上面的分析,我们利用空间换时间的思想,已经把时间压缩到了极致,因为每一级每两个索引结点就有一个会被抽到上一级的索引结点中,所以此时跳表所需要的额外内存空间最多,即空间复杂度最高。其实我们可以通过改变抽取结点的间距来降低跳表的空间复杂度,在其时间复杂度和空间复杂度方面取一个综合性能,当然也要看具体情况,如果内存空间足够,
那就可以选择最小的结点间距,即每两个索引结点抽取一个结点到上一级索引中。如果想降低跳表的空间复杂度,
则可以选择每三个或者每五个结点,抽取一个结点到上级索引中。
代码演示:
#include
<iostream>
#include
<vector>
#include
<algorithm>
#include
<ctime>
#include
<cstdlib>
#include
<cmath>
#include
<cinttypes>
using
namespace
std;
typedef
struct
Node
{
//值和当前节点的层数
int
key, level;
//跳跃表的节点可以上下右移动
struct
Node
* next, * down, * up;
}
Node
;
typedef
struct
Skiplist
{
//head指向最小节点的当前最高层, tail指向最大节点的当前最高层
Node
* head, * tail;
//当前跳跃表的最大高度
int
max_level;
}
Skiplist
;
Node
* getNewNode(
int
key
,
int
n
) {
Node
* nodes = (
Node
*)malloc(
sizeof
(
Node
) *
n
);
for
(
int
i = 0; i <
n
; i++) {
nodes[i].key =
key
;
nodes[i].level = i;
nodes[i].next =
NULL
;
//链接上下节点
nodes[i].down = (i ? nodes + i - 1 :
NULL
);
nodes[i].up = (i + 1 <
n
? nodes + i + 1 :
NULL
);
}
return
nodes +
n
- 1;
}
Skiplist
* getNewSkiplist(
int
n
) {
Skiplist
* s = (
Skiplist
*)malloc(
sizeof
(
Skiplist
));
s->head = getNewNode(
INT32_MIN
,
n
);
s->tail = getNewNode(
INT32_MAX
,
n
);
s->max_level =
n
;
//连接头尾节点
Node
* p = s->head, * q = s->tail;
while
(p) {
p->next = q;
p = p->down, q = q->down;
}
while
(s->head->level != 0) s->head = s->head->down;
return
s;
}
Node
* find(
Skiplist
*
s
,
int
x
) {
Node
* p =
s
->head;
while
(p && p->key !=
x
) {
//下一个节点值小于等于x,则右移
if
(p->next->key <=
x
)p = p->next;
//否则下移
else
p = p->down;
}
return
p;
}
//生成一个[0,1)的随机数
double
randDouble() {
#define
MAX_RAND_N
10000
double
ans = (rand() %
MAX_RAND_N
) * 1.0 /
MAX_RAND_N
;
return
ans;
#undef
MAX_RAND_N
}
int
randLevel(
Skiplist
*
s
) {
int
level = 1;
double
p = 1.0 / 2.0;
//层数越高,概率越第
while
(randDouble() < p) level += 1;
#define
min
(a, b) ((a) < (b) ? (a) : (b))
return
min
(
s
->max_level, level);
#undef
min
}
void
insert(
Skiplist
*
s
,
int
x
) {
int
level = randLevel(
s
);
printf(
"rand level = %d\n"
, level);
//到达level - 1层(从0开始),level-1为最高层
while
(
s
->head->level + 1 < level)
s
->head =
s
->head->up;
Node
* node = getNewNode(
x
, level);
Node
* p =
s
->head;
printf(
"insert begin\n"
);
fflush(
stdout
);
//到达level - 1层(从0开始)
while
(p->level != node->level) p = p->down;
while
(p) {
while
(p->next->key < node->key)p = p->next;
node->next = p->next;
p->next = node;
p = p->down;
node = node->down;
}
return
;
}
void
clearNode(
Node
*
p
) {
if
(
p
==
NULL
)
return
;
free(
p
);
return
;
}
void
clearSkiplist(
Skiplist
*
s
) {
Node
* p =
s
->head, * q;
while
(p->level != 0) p = p->down;
while
(p) {
q = p->next;
clearNode(p);
p = q;
}
free(
s
);
return
;
}
void
output(
Skiplist
*
s
) {
Node
* p =
s
->head;
int
len = 0;
for
(
int
i = 0; i <=
s
->head->level; i++) {
len += printf(
"%4d"
, i);
}
printf(
"\n"
);
for
(
int
i = 0; i < len; i++) printf(
"-"
);
printf(
"\n"
);
while
(p->level > 0) p = p->down;
while
(p) {
bool
flag = (p->key !=
INT32_MIN
&& p->key !=
INT32_MAX
);
for
(
Node
* q = p; flag && q; q = q->up) {
printf(
"%4d"
, q->key);
}
if
(flag) printf(
"\n"
);
p = p->next;
}
return
;
}
int
main() {
srand(time(0));
int
x;
#define
MAX_LEVEL
32
Skiplist
* s = getNewSkiplist(
MAX_LEVEL
);
#undef
MAX_LEVEL
// insert
while
(~scanf_s(
"%d"
, &x)) {
if
(x == -1)
break
;
insert(s, x);
output(s);
}
output(s);
// find
while
(~scanf_s(
"%d"
, &x)) {
Node
* p = find(s, x);
printf(
"find result : "
);
if
(p) {
printf(
"key = %d, level = %d\n"
, p->key, p->level);
}
else
{
printf(
"NULL\n"
);
}
}
clearSkiplist(s);
return
0;
}