概述
跳跃表(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()) { //在该层插入node team=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;//改变head stack.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(); } } }