图一:简单链表结构
将简单链表和跳表进行对比发现,跳表是利用空间换时间,以提高查询的效率。若利用简单链表进行查询,在有序的情况下,也要一个个遍历;而利用跳表,先从高层遍历起,利用遍历结果再到下层,以达到快速收敛比较区间的目的。
跳表的操作和简单链表类似:查询,插入,删除。
typedef struct skipListNode{ int val; struct levelArray { struct skipListNode* forward; }levels[]; }skipListNode;
而插入删除都依赖于查询,所以先分析查询
从最高层开始遍历,寻找小于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
示例:
插入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数组即可。
示例:
收集前驱节点,在此处要删除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;
注:在希尔排序中,通过设置步长,以期望减少元素移动次数,在此处有雷同之处,就是通过设置层级,在查询元素
时,快速收敛查询区间,减少遍历此处,不同的层次之间,也有步长的概念。
参考:维基百科 http://en.wikipedia.org/wiki/Skip_list
程序代码:http://www.oschina.net/code/snippet_100374_12547