跳表--skipList

跳表
跳表是一种数据结构,用于存储顺序数据,基础是顺序链表,通过利用多层次的链表进行组合,以提高查询的速度(而插入、删除都依赖于查询,若能够迅速地定位到查询数据的位置,插入删除的操作也就高效了)。

图一:简单链表结构


跳表--skipList_第1张图片
图二:跳表结构

将简单链表和跳表进行对比发现,跳表是利用空间换时间,以提高查询的效率。若利用简单链表进行查询,在有序的情况下,也要一个个遍历;而利用跳表,先从高层遍历起,利用遍历结果再到下层,以达到快速收敛比较区间的目的。

跳表的操作和简单链表类似:查询,插入,删除。

基本数据结构定义:

skipListNode定义:
跳表--skipList_第2张图片

typedef struct skipListNode{
	int val;
	struct levelArray
	{
	   struct skipListNode*  forward;
	}levels[];
}skipListNode;


levelArray中存放的全部是skipListNode*的指针,将相同高度的节点链接成一个链表。
高度为0:就是普通的单项链表
高度为1:将高度大于1的节点串成一个链表
......
高度为MAXLEVEL:将所有高度为MAXLEVEL的节点串成一个单项链表


而插入删除都依赖于查询,所以先分析查询

 

查询逻辑:

输入:val: int , list: skipList
输出:pNode: skipListNode*  若查找成功,返回节点指针,否则返回NULL;

基本逻辑:

从最高层开始遍历,寻找小于val的最大节点
若找到,则下降一层,在找到的节点后面继续查找小于val的最大节点,直至0层。
找到后,判断该节点后面的节点val是否与给定val相等,若相等,就是需要查找的节点,否则该节点不存在

以上图跳表结构图为例,寻找节点10
1. 在第三层上遍历,找到val为1的节点是小于10的最大节点,下降到第2层 (遍历1个节点)
2.从val为1的节点在第二层开始查找,val为6的节点为小于10的最大节点,下降到1层 (2个)
3. 从val未6的节点在第1层上查找,val为9的节点为小于10的最大节点,下降到0层 (1次)
4. 从val为9的节点在0层上查找,找到val为10的节点 (1次)
共遍历了5个节点,若简单链表,则需遍历10个节点,才能查找成功。

伪代码:关键:找到小于val的最大节点

pNode = pHead;
FOR i = level-1 to 0
     nextNode = pNode->levels[i].forward;
     WHILE nextNode && nextNode.val < val
          pNode = nextNode;
     END WHILE
END FOR

IF !pNode RETURN NULL;
IF pNode THEN
     pNode = pNode->levels[0].forward;
     IF pNode->val == val RETURN pNode;
END IF



插入逻辑:
关键点:找到插入点,确认LEVEL高度,并更新前驱的LEVELS数组

 示例:
跳表--skipList_第3张图片
插入val=40的节点
1. 更新新节点的LEVEL数组,让其指向前驱节点中原本指向的节点
2. 更新前驱节点的LEVEL数组,指向新节点
3. 高度变高,在LEVEL3处新节点没有前驱,将让HEAD节点作为它的前驱

伪代码:

pNode = pHead;
skipListNode* updateNodes[MAX_LEVEL];

//统计各层小于val的最大节点,作为更新节点
FOR i = level-1 to 0
     nextNode = pNode->levels[i].forward;
     WHILE nextNode && nextNode.val < val
          pNode = nextNode;
     END WHILE

    updateNodes[i] = pNode;
END FOR

IF pNode THEN    //找到插入点,在pNode节点后面插入      
     newNode = createNode;
        
     更新个前驱的LEVELS数组
     FOR i = level-1 to 0
            newNode->levels[i]->forward = updateNotes[i]->levels[i]->forward;
            updateNodes[i]->levels[i]->forward = newNode;
     END FOR
END IF

 伪代码中体现了主要过程,有很多细节处理没有展现出来,在实际程序中,需要注意的是创建新节点的LEVEL高度和当前高度之间的比较以及相关处理。
若新节点的高度小于skipList目前高度,则更新前驱节点时,只需更新updateNodes[newLevel-1.....0], newLevel为
新节点的高度。
若新节点的高度已超过skipList的高度,在更新前驱节点时,新更新updateNodes[curLevel-1......0], 对于超过的部分,
直接用HEAD节点中对应的LEVELS数组指向它即可。
 

删除逻辑:

关键点:找出目的节点以及每一层上目的节点的前驱节点列表,在删除目的节点前,先更新前驱节点的LEVEL数组即可。

示例:

跳表--skipList_第4张图片

收集前驱节点,在此处要删除val为4的节点,
二级LEVEL遍历: val = 1 节点LEVELS[2]要更新, 指向目的节点LEVEL[2]指向的节点
一级LEVEL遍历: val = 3 节点LEVELS[1]要更新
,指向目的节点LEVEL[1]指向的节点 
零级LEVEL遍历: val = 3 节点LEVELS[0]要更新
, 指向目的节点LEVEL[0]指向的节点

伪代码:

updateNodes[MAX_LEVEL];    //更新节点数组
pDestNode = NULL;          //目的节点
pNode = pHead;

//查找目的节点、收集前驱节点
FOR i = level-1 to 0
     nextNode = pNode->levels[i].forward;
     WHILE nextNode && nextNode.val < val
          pNode = nextNode;
     END WHILE

    //若该节点后面紧跟目的节点,则目的节点删除后,该节点的LEVEL数组需要更新
    IF pNode->levels[i].forward && pNode->levels[i].forward.val == val THEN
        updateNodes[i] = pNode;
        IF !pDestNode THEN 
            pDestNode = pNode->levels[i].forward; 
        END IF
    END IF
END FOR

//更新待删节点前驱节点的LEVEL数组
FOR i = level-1 to 0
     skipListNode* pUpdateNode = updateNodes[i];
     IF !pUpdateNode    continue;
     pUpdateNode->levels[i].forward = pDestNode->levels[i].forward;
END FOR

//删除目的节点
delete pDestNode;

 

运行结果:

1. 构建跳表,即不停的插入节点:6-38
2. 删除节点10,删除成功

跳表--skipList_第5张图片

 

注:在希尔排序中,通过设置步长,以期望减少元素移动次数,在此处有雷同之处,就是通过设置层级,在查询元素
时,快速收敛查询区间,减少遍历此处,不同的层次之间,也有步长的概念。

参考:维基百科     http://en.wikipedia.org/wiki/Skip_list
程序代码:http://www.oschina.net/code/snippet_100374_12547

你可能感兴趣的:(跳表)