概述
跳跃表(SkipList)是链表加多级索引组成的数据结构。链表的数据结构的查询复条度是 O(N)。为了提高查询效率,可以在链表上加多级索引来实现快速查询。跳跃表不仅能提高搜索性能。也能提高插入和删除操作的性能。索引的层数也叫作跳跃表的高度
查找
在跳跃表的结构中会首先从顶层开始查找,当顶层不存在时向下一层查找,重复此查找过程直到跳跃到原始链表。如图所示,在 [1,3,4,10,1,20] 的链表中查找 10,首先从二级索引中查找,由于 1 和 4 都比 10 小,因此接着在一级索引查找,由于 10 大于 4 小于 11,因此接着向下查找,原始链表中 4 的下一个节点 10 便是需要查找的数据
插入
首先按照查找流程找到待插入元素的前驱和后继,然后按照随机算法生成一个高度值 height,最后将待插入的节点按照高度值生成一个垂直节点(这个节点的层数正好等于高度值),并将其插人跳跃表的多条链表中。如果高度值 heigh 大于插入前跳跃表的高度,那么跳跃表的高度被提升为 height,同时需要更新头节点和尾节点的指针指向。如果 height 小于或等于跳跃表的高度,那么需要更新待插入元素的前驱和后继的指针指向。在 [1,3,4.10,11,20] 的跳跃表中插入 18 的过程如图所示
删除
在删除节点时首先需要找到待删除的节点在每一层的前驱和后继,接着将其前驱节点的后继替换为待删除的节点的后继,删除 4 的过程如图所示
代码实现
import java.util.Random;
import java.util.Stack;class SkipNode<T> {int key;T value;SkipNode right,down;//左右上下四个方向的指针public SkipNode (int key,T value) {this.key=key;this.value=value;}}public class SkipList <T> {SkipNode headNode;//头节点,入口int highLevel;//层数Random random;// 用于投掷硬币final int MAX_LEVEL = 32;//最大的层SkipList(){random=new Random();headNode=new SkipNode(Integer.MIN_VALUE,null);highLevel=0;}public SkipNode search(int key) {SkipNode team=headNode;while (team!=null) {if(team.key==key){return team;}else if(team.right==null) { //右侧没有了,只能下降team=team.down;}else if(team.right.key>key) { //需要下降去寻找team=team.down;}else { //右侧比较小向右team=team.right;}}return null;}public void delete(int key) { //删除不需要考虑层数SkipNode team=headNode;while (team!=null) {if (team.right == null) { //右侧没有了,说明这一层找到,没有只能下降team=team.down;}else if(team.right.key==key) { //找到节点,右侧即为待删除节点team.right=team.right.right;//删除右侧节点team=team.down;//向下继续查找删除}else if(team.right.key>key) { //右侧已经不可能了,向下team=team.down;}else { //节点还在右侧team=team.right;}}}public void add(SkipNode node) {int key=node.key;SkipNode findNode=search(key);if(findNode!=null) { //如果存在这个key的节点findNode.value=node.value;return;}Stack<SkipNode>stack=new Stack<SkipNode>();//存储向下的节点,这些节点可能在右侧插入节点SkipNode team=headNode;//查找待插入的节点 找到最底层的哪个节点。while (team!=null) {//进行查找操作if(team.right==null) { //右侧没有了,只能下降stack.add(team);//将曾经向下的节点记录一下team=team.down;}else if(team.right.key>key) { //需要下降去寻找stack.add(team);//将曾经向下的节点记录一下team=team.down;}else { //向右team=team.right;}}int level=1;//当前层数,从第一层添加(第一层必须添加,先添加再判断)SkipNode downNode=null;//保持前驱节点(即down的指向,初始为null)while (!stack.isEmpty()) {//在该层插入nodeteam=stack.pop();//抛出待插入的左侧节点SkipNode nodeTeam=new SkipNode(node.key, node.value);//节点需要重新创建nodeTeam.down=downNode;//处理竖方向downNode=nodeTeam;//标记新的节点下次使用if(team.right==null) {//右侧为null 说明插入在末尾team.right=nodeTeam;}//水平方向处理else {//右侧还有节点,插入在两者之间nodeTeam.right=team.right;team.right=nodeTeam;}//考虑是否需要向上if(level>MAX_LEVEL)//已经到达最高级的节点啦break;double num=random.nextDouble();//[0-1]随机数if(num>0.5)//运气不好结束break;level++;if(level>highLevel) { //比当前最大高度要高但是依然在允许范围内 需要改变head节点highLevel=level;//需要创建一个新的节点SkipNode highHeadNode=new SkipNode(Integer.MIN_VALUE, null);highHeadNode.down=headNode;headNode=highHeadNode;//改变headstack.add(headNode);//下次抛出head}}}public void printList() {SkipNode teamNode=headNode;int index=1;SkipNode last=teamNode;while (last.down!=null){last=last.down;}while (teamNode!=null) {SkipNode enumNode=teamNode.right;SkipNode enumLast=last.right;System.out.printf("%-8s","head->");while (enumLast!=null&&enumNode!=null) {if(enumLast.key==enumNode.key) {System.out.printf("%-5s",enumLast.key+"->");enumLast=enumLast.right;enumNode=enumNode.right;}else {enumLast=enumLast.right;System.out.printf("%-5s","");}}teamNode=teamNode.down;index++;System.out.println();}}
}