最近在看《信息检索导论》,在查找2个链表相同元素时文中使用了一种以前从没见过的数据结构——跳表(Skip List),Skip List是在有序List(链表)数据结构的基础上的扩展,解决了有序链表结构查找特定值困难的问题。
我们知道在普通链表中查找一个元素的话,需要将整个链表遍历一次,如果链表是有序的话,因为链表不像数组那样可以进行二分查找,所以我们可以在节点中存储了指向前面N个节点的指针,这样在查找一个节点时,跳表指针能够提供捷径来跳过那些不可能出现在检索结果中的记录项。
图1. 当跳表指针目标项仍然小于另一个表的当前比较项时可以采用跳表指针直接跳转
不过上面是跳表的简化版,一般性的跳表中每一个元素都有N层(N为随机数,并且N>=1),上图中的层数为1或2,而且步数是固定,一般性跳表每个节点的层数是在插入过程中随机产生的。
图2. 一个完整的跳表示例
下面来定义跳表的数据结构(基于C),首先是每个节点的数据结构
typedef struct nodeStructure { int key; int value; struct nodeStructure *forward[1]; }nodeStructure;
跳表的结构如下
typedef struct skiplist { int level; nodeStructure *header; }skiplist;
下面是跳表的基本操作
首先是节点的创建
nodeStructure* createNode(int level,int key,int value) { nodeStructure *ns=(nodeStructure *)malloc(sizeof(nodeStructure)+level*sizeof(nodeStructure*)); ns->key=key; ns->value=value; return ns; }
列表的初始化需要初始化头部,并使头部每层(根据事先定义的MAX_LEVEL)指向末尾(NULL)。
skiplist* createSkiplist() { skiplist *sl=(skiplist *)malloc(sizeof(skiplist)); sl->level=0; sl->header=createNode(MAX_LEVEL-1,0,0); for(int i=0;i<MAX_LEVEL;i++) { sl->header->forward[i]=NULL; } return sl; }
插入元素的时候,元素所占有的层数完全是随机的,通过随机算法产生
int randomLevel() { int k=1; while (rand()%2) k++; k=(k<MAX_LEVEL)?k:MAX_LEVEL; return k; }
跳表的插入需要三个步骤,第一步需要查找到在每层待插入位置,然后需要随机产生一个层数,最后就是从高层至下插入,插入时算法和普通链表的插入完全相同。
图3. 红色区域为辅助数组update[]的内容
bool insert(skiplist *sl,int key,int value) { nodeStructure *update[MAX_LEVEL]; nodeStructure *p, *q = NULL; p=sl->header; int k=sl->level; //从最高层往下查找需要插入的位置 //填充update for(int i=k-1; i >= 0; i--){ while((q=p->forward[i])&&(q->key<key)) { p=q; } update[i]=p; } //不能插入相同的key if(q&&q->key==key) { return false; } //产生一个随机层数K //新建一个待插入节点q //一层一层插入 k=randomLevel(); //更新跳表的level if(k>(sl->level)) { for(int i=sl->level; i < k; i++){ update[i] = sl->header; } sl->level=k; } q=createNode(k,key,value); //逐层更新节点的指针,和普通列表插入一样 for(int i=0;i<k;i++) { q->forward[i]=update[i]->forward[i]; update[i]->forward[i]=q; } return true; }
删除节点操作和插入差不多,找到每层需要删除的位置,删除时和操作普通链表完全一样。不过需要注意的是,如果该节点的level是最大的,则需要更新跳表的level。
bool deleteSL(skiplist *sl,int key) { nodeStructure *update[MAX_LEVEL]; nodeStructure *p,*q=NULL; p=sl->header; //从最高层开始搜 int k=sl->level; for(int i=k-1; i >= 0; i--){ while((q=p->forward[i])&&(q->key<key)) { p=q; } update[i]=p; } if(q&&q->key==key) { //逐层删除,和普通列表删除一样 for(int i=0; i<sl->level; i++){ if(update[i]->forward[i]==q){ update[i]->forward[i]=q->forward[i]; } } free(q); //如果删除的是最大层的节点,那么需要重新维护跳表的 for(int i=sl->level-1; i >= 0; i--){ if(sl->header->forward[i]==NULL){ sl->level--; } } return true; } else return false; }
跳表的优点就是查找比普通链表快,当然查找操作已经包含在在插入和删除过程,实现起来比较简单。
图4. 搜索key=14的示意图
int search(skiplist *sl,int key) { nodeStructure *p,*q=NULL; p=sl->header; //从最高层开始搜 int k=sl->level; for(int i=k-1; i >= 0; i--){ while((q=p->forward[i])&&(q->key<=key)) { if(q->key==key) { return q->value; } p=q; } } return NULL; }
完整的跳表C语言代码:
#include<stdio.h> #include<stdlib.h> #define MAX_LEVEL 10 //最大层数 //节点 typedef struct nodeStructure { int key; int value; struct nodeStructure *forward[1]; }nodeStructure; //跳表 typedef struct skiplist { int level; nodeStructure *header; }skiplist; //创建节点 nodeStructure* createNode(int level,int key,int value) { nodeStructure *ns=(nodeStructure *)malloc(sizeof(nodeStructure)+level*sizeof(nodeStructure*)); ns->key=key; ns->value=value; return ns; } //初始化跳表 skiplist* createSkiplist() { skiplist *sl=(skiplist *)malloc(sizeof(skiplist)); sl->level=0; sl->header=createNode(MAX_LEVEL-1,0,0); for(int i=0;i<MAX_LEVEL;i++) { sl->header->forward[i]=NULL; } return sl; } //随机产生层数 int randomLevel() { int k=1; while (rand()%2) k++; k=(k<MAX_LEVEL)?k:MAX_LEVEL; return k; } //插入节点 bool insert(skiplist *sl,int key,int value) { nodeStructure *update[MAX_LEVEL]; nodeStructure *p, *q = NULL; p=sl->header; int k=sl->level; //从最高层往下查找需要插入的位置 //填充update for(int i=k-1; i >= 0; i--){ while((q=p->forward[i])&&(q->key<key)) { p=q; } update[i]=p; } //不能插入相同的key if(q&&q->key==key) { return false; } //产生一个随机层数K //新建一个待插入节点q //一层一层插入 k=randomLevel(); //更新跳表的level if(k>(sl->level)) { for(int i=sl->level; i < k; i++){ update[i] = sl->header; } sl->level=k; } q=createNode(k,key,value); //逐层更新节点的指针,和普通列表插入一样 for(int i=0;i<k;i++) { q->forward[i]=update[i]->forward[i]; update[i]->forward[i]=q; } return true; } //搜索指定key的value int search(skiplist *sl,int key) { nodeStructure *p,*q=NULL; p=sl->header; //从最高层开始搜 int k=sl->level; for(int i=k-1; i >= 0; i--){ while((q=p->forward[i])&&(q->key<=key)) { if(q->key == key) { return q->value; } p=q; } } return NULL; } //删除指定的key bool deleteSL(skiplist *sl,int key) { nodeStructure *update[MAX_LEVEL]; nodeStructure *p,*q=NULL; p=sl->header; //从最高层开始搜 int k=sl->level; for(int i=k-1; i >= 0; i--){ while((q=p->forward[i])&&(q->key<key)) { p=q; } update[i]=p; } if(q&&q->key==key) { //逐层删除,和普通列表删除一样 for(int i=0; i<sl->level; i++){ if(update[i]->forward[i]==q){ update[i]->forward[i]=q->forward[i]; } } free(q); //如果删除的是最大层的节点,那么需要重新维护跳表的 for(int i=sl->level - 1; i >= 0; i--){ if(sl->header->forward[i]==NULL){ sl->level--; } } return true; } else return false; } void printSL(skiplist *sl) { //从最高层开始打印 nodeStructure *p,*q=NULL; //从最高层开始搜 int k=sl->level; for(int i=k-1; i >= 0; i--) { p=sl->header; while(q=p->forward[i]) { printf("%d -> ",p->value); p=q; } printf("\n"); } printf("\n"); } int main() { skiplist *sl=createSkiplist(); for(int i=1;i<=19;i++) { insert(sl,i,i*2); } printSL(sl); //搜索 int i=search(sl,4); printf("i=%d\n",i); //删除 bool b=deleteSL(sl,4); if(b) printf("删除成功\n"); printSL(sl); system("pause"); return 0; }
我记得面试的时候还考过。
好难的面试…