跳表全称叫做跳跃表,简称跳表。跳表是一个随机化的数据结构,实质就是一种可以进行二分查找的有序链表。跳表在原有的有序链表上面增加了多级索引,通过索引来实现快速查找。跳表不仅能提高搜索性能,同时也可以提高插入和删除操作的性能。
跳表在性能上和红黑树,AVL树不相上下,但是跳表的原理非常简单,目前在Redis和LeveIDB中都有用到。
跳表采用随机技术决定元素应该在哪几层,其中的搜索、插入、删除操作的时间均为O(logn),然而,最坏情况下时间复杂性却变成O(n)。相比之下,在一个有序数组或链表中进行插入/删除操作的时间为O(n),最坏情况下为O(n)。
这个是跳表的模型
跳表在原有的有序链表上面增加了多级索引,通过索引来实现快速查找。首先在最高级索引上查找最后一个小于当前查找元素的位置,然后再跳到次高级索引继续查找,直到跳到最底层为止,这时候以及十分接近要查找的元素的位置了(如果查找元素存在的话)。由于根据索引可以一次跳过多个元素,所以跳查找的查找速度也就变快了。
(以上截自百度关于跳表的一些介绍)
不过其中的-1也可以换成任意的负无穷的数,其中最下层是最原始的链表,依次往上是一层二层索引,关于跳表的一些操作,接下来详细介绍。
首先介绍跳表的基本组成单位:跳表节点。
跳表节点包含:
元素值
下一个节点
下一层的对应节点
/**
* 定义跳表节点
*/
public class SkipNode {
//元素值
public int element;
//下一个节点
public SkipNode next = null;
//下一层对应节点
public SkipNode down = null;
public SkipNode( int element ) {
this.element = element;
}
public SkipNode(SkipNode node) {
this.element = node.element;
}
}
其次介绍关于跳表的查找操作
1、从最上层的跳表索引的开头开始
2、假设当前位置为p,将p的下一个节点的值与key比较
1) p下一个节点的值 <= key ,p移动到下一个节点的位置
2) p下一个节点的值 > key 或者 p的下一个值为null,p移动到下一层相应的节点位置
3.到达底层后,p的右边可能还有元素,则移动直到p节点的值=key或者p的下一个节点为空
1) 找到p节点
2) 找不到p节点
/**
* 查找节点
* @param key(节点的值)
* @return 存在时返回最底层对应节点,不存在时返回null
*/
public SkipNode find(int key) {
SkipNode p = head;
while (p != null && p.down != null) {
if (p.next != null && key >= p.next.element) {
// 比右边大或相等时向右
p = p.next;
} else if (p.next == null || key < p.next.element) {
// 否则向下
p = p.down;
}
}
// 到达底层后右边可能还有元素
while (p.next != null && key >= p.next.element ) {
p = p.next;
}
if( p.element != -1 && key == p.element) {
return p;
}
return null;
}
然后是跳表的插入操作
1.确认节点未插入
2.获取随机层数
3.随机层数大于使用层数时:添加新的索引层
4.假设当前节点为p,将p移动到节点的随机层数的头节点
5.依次在随机层数以下的节点插入元素并与上一层关联:
1)向右移动使得p的下一个节点的元素的值>key或者p的下一个值为null
2)将key相关的node插入p的下一个位置
3)与上一层的节点oldnode想关联(如果有的话)
4)p进入下一层,将oldnode的引用指向node
6.跳表大小+1
/**
* 插入元素节点
* @param key(元素的值)
* @return 插入的节点
*/
public SkipNode insert( int key ) {
// 节点已存在不插入
if( find(key) != null ) {
return null;
}
SkipNode node = new SkipNode( key );
//获取元素随机层数
int level = getLevel();
//当随机层数大于使用层数时候添加层数
if ( level > nodelevel ) {
addlevel(level - nodelevel);
}
SkipNode p = head;
while (nodelevel - level > 0) {
p = p.down;
level++;
} // 获得最高更新索引层的头结点
SkipNode oldnode = null;
while (p != null) {
while (p.next != null && p.next.element < key) {
p = p.next;
}
node.next = p.next;
p.next = node;
if (oldnode != null) {
oldnode.down = node;
}
oldnode = node;
if (p.down != null) {
node = new SkipNode(node);
}
p = p.down;
}
size++;
return node;
}
最后是节点的删除操作
1.首先判断跳表是否为空
2.找到待删除节点的最高层索引的节点的前置节点
3.记录待删除元素
4.p为2中找到的前置节点
1)将p的下一个元素删除
2)找到下一层的待删除节点的前置节点
5.删除仅剩头节点的索引层
6.减少跳表的节点数量
/**
* 删除元素节点
* @param key(要删除元素的值)
* @return value(删除元素的值)
*/
public int remove(int key) {
// 链表为空,返回-1
if( head.next == null ) {
return -1;
}
//找到待删除元素的最上层的前一个节点
SkipNode p = findPreNode(key);
if (p == null) {
// 结点不存在
return -1;
}
// 记录待删除元素
int value = p.next.element;
while (p != null && p.next != null ) {
p.next = p.next.next;
//往下遍历,直到最底下一层
p = p.down;
//找到每层待删除元素的前一个节点
while (p != null && p.next.element != key) {
p = p.next;
}
}
// 删除仅剩头结点的索引(下面称为空索引)
p = head;
// 计算索引层数减少量
int newlevel = nodelevel;
// 删除连续空索引
while (p.down != null && p.next == null) {
p = p.down;
newlevel--;
}
head = p;
nodelevel = newlevel;
//减少跳表元素数量
size--;
return value;
}
测试代码
public static void main(String[] args) {
SkipList l = new SkipList();
l.insert(1);
l.show();
l.insert(2);
l.show();
l.insert(4);
l.show();
l.insert(6);
l.show();
l.insert(5);
l.show();
l.insert(5);
l.show();
l.insert(0);
l.show();
l.remove(5);
l.show();
l.remove(1);
l.show();
l.remove(4);
l.show();
}
测试结果
-1->1
-1->1
----------------------------
-1->1
-1->1->2
----------------------------
-1->1->4
-1->1->2->4
----------------------------
-1->1->4
-1->1->2->4->6
----------------------------
-1->1->4
-1->1->2->4->5->6
----------------------------
-1->1->4
-1->1->2->4->5->6
----------------------------
-1->1->4
-1->0->1->2->4->5->6
----------------------------
-1->1->4
-1->0->1->2->4->6
----------------------------
-1->4
-1->0->2->4->6
----------------------------
-1->0->2->6
----------------------------
源代码
import java.util.Random;
public class SkipList {
/**
* 定义跳表节点
*/
public class SkipNode {
//元素值
public int element;
//下一个节点
public SkipNode next = null;
//下一层对应节点
public SkipNode down = null;
public SkipNode( int element ) {
this.element = element;
}
public SkipNode(SkipNode node) {
this.element = node.element;
}
}
/**
* 初始跳表的空节点
*/
public SkipNode head = new SkipNode( -1 );
/**
* 跳表的索引层数
*/
int nodelevel = 0;
/**
* 跳表的元素数量
*/
int size = 0;
/**
* 随机数
*/
Random random = new Random();
/**
* 查找节点
* @param key(节点的值)
* @return 存在时返回最底层对应节点,不存在时返回null
*/
public SkipNode find(int key) {
SkipNode p = head;
while (p != null && p.down != null) {
if (p.next != null && key >= p.next.element) {
// 比右边大或相等时向右
p = p.next;
} else if (p.next == null || key < p.next.element) {
// 否则向下
p = p.down;
}
}
// 到达底层后右边可能还有元素
while (p.next != null && key >= p.next.element ) {
p = p.next;
}
if( p.element != -1 && key == p.element) {
return p;
}
return null;
}
/**
* 获得随机索引层数
* @return 元素的索引层数
*/
int getLevel() {
int level = 0;
for (int i = 0; i <= nodelevel; i++) {
if (random.nextInt(2) == 1) {
//每层都有一半概率
level++;
} else {
break;
}
}
return level;
}
/**
* 增加索引层数
* @param x(要增加的索引层数)
*/
public void addlevel(int x) {
for (int i = 0; i < x; i++) {
// 每层建立一个头结点
SkipNode newfirst = new SkipNode(-1);
newfirst.next = null;
newfirst.down = head;
head = newfirst;
}
nodelevel = x + nodelevel;
}
/**
* 插入元素节点
* @param key(元素的值)
* @return 插入的节点
*/
public SkipNode insert( int key ) {
// 节点已存在不插入
if( find(key) != null ) {
return null;
}
SkipNode node = new SkipNode( key );
//获取元素随机层数
int level = getLevel();
//当随机层数大于使用层数时候添加层数
if ( level > nodelevel ) {
addlevel(level - nodelevel);
}
SkipNode p = head;
while (nodelevel - level > 0) {
p = p.down;
level++;
} // 获得最高更新索引层的头结点
SkipNode oldnode = null;
while (p != null) {
while (p.next != null && p.next.element < key) {
p = p.next;
}
node.next = p.next;
p.next = node;
if (oldnode != null) {
oldnode.down = node;
}
oldnode = node;
if (p.down != null) {
node = new SkipNode(node);
}
p = p.down;
}
size++;
return node;
}
/**
* 寻找前置节点
* @param key(节点的值)
* @return 指定节点在最高层索引的前置节点
*/
private SkipNode findPreNode(int key) {
SkipNode p = head;
while (p != null && p.down != null) {
if (p.next != null && key > p.next.element) {
// 比右边大时向右
p = p.next;
} else if (p.next != null && key == p.next.element) {
// 与右边相等时返回自身
return p;
} else if (p.next == null || key < p.next.element) {
// 否则向下
p = p.down;
}
}
// 到达底层后右边可能还有元素
while (p.next != null) {
if (key == p.next.element) {
//System.out.println(p.element.value);
return p;
}
p = p.next;
}
// 无前置结点
return null;
}
/**
* 删除元素节点
* @param key(要删除元素的值)
* @return value(删除元素的值)
*/
public int remove(int key) {
// 链表为空,返回-1
if( head.next == null ) {
return -1;
}
//找到待删除元素的最上层的前一个节点
SkipNode p = findPreNode(key);
if (p == null) {
// 结点不存在
return -1;
}
// 记录待删除元素
int value = p.next.element;
while (p != null && p.next != null ) {
p.next = p.next.next;
//往下遍历,直到最底下一层
p = p.down;
//找到每层待删除元素的前一个节点
while (p != null && p.next.element != key) {
p = p.next;
}
}
// 删除仅剩头结点的索引(下面称为空索引)
p = head;
// 计算索引层数减少量
int newlevel = nodelevel;
// 删除连续空索引
while (p.down != null && p.next == null) {
p = p.down;
newlevel--;
}
head = p;
nodelevel = newlevel;
//减少跳表元素数量
size--;
return value;
}
/**
* 展示跳表的所有元素
*/
public void show( ) {
SkipNode p = head;
while( p != null ) {
SkipNode p1 = p;
while( p1 != null ) {
System.out.print( p1.element );
p1 = p1.next;
if (p1 == null) {
continue;
}
System.out.print( "->" );
}
p = p.down;
System.out.println();
}
System.out.println("----------------------------");
}
public static void main(String[] args) {
SkipList l = new SkipList();
l.insert(1);
l.show();
l.insert(2);
l.show();
l.insert(4);
l.show();
l.insert(6);
l.show();
l.insert(5);
l.show();
l.insert(5);
l.show();
l.insert(0);
l.show();
l.remove(5);
l.show();
l.remove(1);
l.show();
l.remove(4);
l.show();
}
}
跳表用于redis内部的sorted set排序功能。