概述
跳表是一种由原始数据层有序单链表加多层级索引数据有序单链表的结构。
跳表最底层为原始数据有序单链表,各级索引层上节点数量从下往上的分布情况呈指数级递减。理论上log2n - 1层、且从上往下每层的节点数都是上一层的2倍的索引层级结构是能使跳表查询效率达到最高的设计,但考虑到内存空间的过多消耗问题,所以一般会限制索引的层数,以及新插入的原始数据上升到不同索引层级的概率。
特点
跳表支持O(logn)级别的时间复杂度的查询、插入、删除操作,以及支持O(nlogn)级别的时间复杂度排序数列。它汲取了链表O(1)时间复杂度的动态伸缩内存空间的优点,另外通过引入多级索引层的方式规避了链表O(n)时间复杂度的查询缺点。虽然说链表在内存动态伸缩这一块没有了数组那种数据迁移的沉重包袱,但因为链表开辟的内存空间不是连续的,对cpu缓存不友好,没办法利用cpu缓存预读数据,所以在内存数据访问上相比数组就效率没那么高了。
为什么链表开辟的内存空间不是连续的,对cpu缓存不友好,没办法利用cpu缓存预读数据,在内存数据访问上相比数组就效率没那么高了?
为了回答这个问题,先讲讲操作系统为什么要引入cpu缓存?随着硬件的发展,cpu的执行速度和内存的读取速度拉开了好几个数量级,因此高速cpu因等待低速内存读取数据而造成大量的cpu时间浪费,为了平衡这个速率,所以引入了比内存更高效的物理介质,也就是cpu缓存,于是cpu每次读取数据时,首先会到cpu缓存里读取,如果没有,就再从内存读取到cpu缓存,然后从cpu缓存里返回,每次从内存读取数据到cpu缓存时,并不是以某个数据为单位读取,而是以某个数据一定范围内的整块数据为单位读取,也就是以数据块单位读取,如果下次cpu执行中需要的数据刚好在上次cpu缓存加载进来的数据块中能找到,这时被直接从cpu缓存里读的数据也可以认为是上次cpu缓存预读的数据。因为不用去内存中读取数据了,也就间接缓和了高速cpu和低速内存的速率问题。
因为cpu缓存是以数据块单位从内存中读取数据的,如果要访问的是数组内的数据,数组的内存地址空间是连续的,所以cpu执行首次访问时首先cpu缓存内没有,就从内存中以数据块为单位读取,而这个数据块可能就涵盖了整个数组的所有数据,所以,cpu后面多次执行随机访问该数组内的数据时,就可以直接从cpu缓存里获取到数据了,整个访问速率肯定就很高效了。而链表内,因为每个节点开辟的空间都不是连续的,所以每次cpu缓存从内存里读取的数据块内可能只包含了链表数据中的某一个节点的数据,每次cpu访问链表中一个节点时,都没办法直接从cpu缓存里命中数据,而是需要从内存中读取,访问效率自然就没有数组高了。
跳表通过引入多级索引层,使数据的查询效率接近二分查找O(logn)。索引层的引入,各个索引节点需要占用额外的内存空间,空间复杂度为(n),如果单个数据过大,因为索引节点仅保存数据的指针,在数据的个数数量级很大的情况下,这些额外的内存空间消耗可以忽略不计。
因为跳表本身是有序的结构,所以在做数值范围查找时非常方便。
跳表C语言版本实现如下:
SkipList.h文件:
// SkipList.h
#define MAX_LEVEL 64
struct Node {
int value;
int maxLevel;
struct Node* forward[MAX_LEVEL];
};
struct SkipList {
int maxLevel;
int maxLevelNodeNum;
struct Node head;
};
struct SkipList* newSkipList();
void skipListInsert(struct SkipList* sl, int value);
int skipListFind(struct SkipList* sl, int value);
void skipListDelete(struct SkipList* sl, int value);
void skipListPrintSortData(struct SkipList* sl);
SkipList.c文件:
// SkipList.c
#include "SkipList.h"
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
int getLevel(){
static int isSrandTag = 0;
int i, level;
if(isSrandTag == 0){
isSrandTag = 1;
srand((int)time(0));
}
level = 0;
for(i = 1; i < MAX_LEVEL; i++){
if(rand() % 2 == 0){
level++;
}
else{
break;
}
}
return level;
}
struct SkipList* newSkipList(){
struct SkipList* sl = (struct SkipList*)malloc(sizeof(struct SkipList));
memset(&sl->head, 0, sizeof(struct Node));
sl->maxLevel = 0;
sl->maxLevelNodeNum = 0;
return sl;
}
void skipListInsert(struct SkipList* sl, int value){
int level;
int i;
struct Node* update[MAX_LEVEL], *newNode;
struct Node* tmpNode = &sl->head;
level = getLevel();
newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->maxLevel = level;
newNode->value = value;
for(i = level; i >= 0; i--){
while(tmpNode->forward[i] && tmpNode->forward[i]->value > value){
tmpNode = tmpNode->forward[i];
}
update[i] = tmpNode;
}
for(i = 0; i <= level; i++){
newNode->forward[i] = update[i]->forward[i];
update[i]->forward[i] = newNode;
}
if(level > sl->maxLevel){
sl->maxLevel = level;
sl->maxLevelNodeNum = 1;
}
else if(level == sl->maxLevel){
sl->maxLevelNodeNum++;
}
}
int skipListFind(struct SkipList* sl, int value){
int i;
struct Node* tmpNode;
tmpNode = &sl->head;
for(i = sl->maxLevel; i>= 0; i--){
while(tmpNode->forward[i] && tmpNode->forward[i]->value > value){
tmpNode = tmpNode->forward[i];
}
if(tmpNode->forward[i] && tmpNode->forward[i]->value == value){
return 1;
}
}
return 0;
}
void skipListDelete(struct SkipList* sl, int value){
int i;
struct Node* dest = NULL;
struct Node* tmpNode = &sl->head;
for(i = sl->maxLevel; i >= 0; i--){
while(tmpNode->forward[i] && tmpNode->forward[i]->value > value){
tmpNode = tmpNode->forward[i];
}
if(tmpNode->forward[i] && tmpNode->forward[i]->value == value){
dest = tmpNode->forward[i];
tmpNode->forward[i] = tmpNode->forward[i]->forward[i];
}
}
if(!dest){
return;
}
if(dest->maxLevel == sl->maxLevel){
sl->maxLevelNodeNum--;
if(sl->maxLevelNodeNum == 0){
tmpNode = &sl->head;
if(sl->maxLevel - 1 > 0){
for(i = sl->maxLevel - 1; i > 0; i--){
while(tmpNode->forward[i]){
tmpNode = tmpNode->forward[i];
sl->maxLevelNodeNum++;
}
if(sl->maxLevelNodeNum > 0){
sl->maxLevel = i;
break;
}
}
}
else{
sl->maxLevel = 0;
}
}
}
free(dest);
}
void skipListPrintSortData(struct SkipList* sl){
struct Node* tmpNode;
tmpNode = &sl->head;
printf("\n----------------------------------------\n");
while(tmpNode->forward[0]){
printf("[value:%d, maxLevel:%d],", tmpNode->forward[0]->value, tmpNode->forward[0]->maxLevel);
tmpNode = tmpNode->forward[0];
}
printf("\n----------------------------------------\n");
while(tmpNode->forward[0]){
printf("%d,", tmpNode->forward[0]->value);
tmpNode = tmpNode->forward[0];
}
printf("\n----------------------------------------\n");
printf("SkipList maxLevel:%d, maxLevelNodeNum:%d\n", sl->maxLevel, sl->maxLevelNodeNum);
}