定义
跳跃表(skiplist)是一种随机化(随机提高层次)的数据结构,是一种可以于平衡树媲美的层次化链表结构——查找、删除、添加等操作都可以在对数期望时间下完成.
实现
public class SkipListNode <T> {
public int key;
public T value;
/**
* 上下左右 四个指针
*/
public SkipListNode<T> up, down, left, right;
/**
* 负无穷
*/
public static final int HEAD_KEY = Integer.MIN_VALUE;
/**
* 正无穷
*/
public static final int TAIL_KEY = Integer.MAX_VALUE;
public SkipListNode(int k,T v) {
// TODO Auto-generated constructor stub
key = k;
value = v;
}
public int getKey() {
return key;
}
public void setKey(int key) {
this.key = key;
}
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null) {
return false;
}
if (!(o instanceof SkipListNode<?>)) {
return false;
}
SkipListNode<T> ent;
try {
// 检测类型
ent = (SkipListNode<T>) o;
} catch (ClassCastException ex) {
return false;
}
return (ent.getKey() == key) && (ent.getValue() == value);
}
@Override
public String toString() {
// TODO Auto-generated method stub
return "key-value:"+key+"-"+value;
}
}
public class SkipList <T>{
private SkipListNode<T> head,tail;
/**
* 节点总数
*/
private int nodes;
/**
* 层数
*/
private int listLevel;
/**
* 用于投掷硬币
*/
private Random random;
/**
* 向上提升一个的概率
*/
private static final double PROBABILITY=0.5;
public SkipList() {
// TODO Auto-generated constructor stub
random=new Random();
clear();
}
/**
*清空跳跃表
* */
public void clear(){
head=new SkipListNode<T>(SkipListNode.HEAD_KEY, null);
tail=new SkipListNode<T>(SkipListNode.TAIL_KEY, null);
horizontalLink(head, tail);
listLevel=0;
nodes=0;
}
public boolean isEmpty(){
return nodes==0;
}
public int size() {
return nodes;
}
/**
* 在最下面一层,找到要插入的位置前面的那个key
* */
private SkipListNode<T> findNode(int key){
SkipListNode<T> p=head;
while(true){
while (p.right.key!=SkipListNode.TAIL_KEY&&p.right.key<=key) {
p=p.right;
}
if (p.down!=null) {
p=p.down;
}else {
break;
}
}
return p;
}
/**
* 查找是否存储key,存在则返回该节点,否则返回null
* */
public SkipListNode<T> search(int key){
SkipListNode<T> p=findNode(key);
if (key==p.getKey()) {
return p;
}else {
return null;
}
}
/**
* 向跳跃表中添加key-value
*
* */
public void put(int k,T v){
SkipListNode<T> p=findNode(k);
//如果key值相同,替换原来的vaule即可结束
if (k==p.getKey()) {
p.value=v;
return;
}
SkipListNode<T> q=new SkipListNode<T>(k, v);
backLink(p, q);
int currentLevel=0;//当前所在的层级是0
//抛硬币
while (random.nextDouble()<PROBABILITY) {
//如果超出了高度,需要重新建一个顶层
if (currentLevel>=listLevel) {
listLevel++;
SkipListNode<T> p1=new SkipListNode<T>(SkipListNode.HEAD_KEY, null);
SkipListNode<T> p2=new SkipListNode<T>(SkipListNode.TAIL_KEY, null);
horizontalLink(p1, p2);
vertiacallLink(p1, head);
vertiacallLink(p2, tail);
head=p1;
tail=p2;
}
//将p移动到上一层
while (p.up==null) {
p=p.left;
}
p=p.up;
SkipListNode<T> e=new SkipListNode<T>(k, null);//只保存key就ok
backLink(p, e);//将e插入到p的后面
vertiacallLink(e, q);//将e和q上下连接
q=e;
currentLevel++;
}
nodes++;//层数递增
}
/**
* node1后面插入node2
* @param node1
* @param node2
*/
private void backLink(SkipListNode<T> node1,SkipListNode<T> node2){
node2.left=node1;
node2.right=node1.right;
node1.right.left=node2;
node1.right=node2;
}
/**
* 水平双向连接
* */
private void horizontalLink(SkipListNode<T> node1,SkipListNode<T> node2){
node1.right=node2;
node2.left=node1;
}
/**
* 垂直双向连接
* */
private void vertiacallLink(SkipListNode<T> node1,SkipListNode<T> node2){
node1.down=node2;
node2.up=node1;
}
public T pop(int key){
SkipListNode<T> node = findNode(key);
if(key == node.getKey()){
T t = node.getValue();
while (node.up!=null){
node.left.right = node.right;
node.right.left = node.left;
node = node.up;
}
nodes--;
return t;
}
else {
return null;
}
}
/**
* 打印出原始数据
* */
@Override
public String toString() {
// TODO Auto-generated method stub
if (isEmpty()) {
return "跳跃表为空!";
}
StringBuilder builder=new StringBuilder();
SkipListNode<T> p=head;
while (p.down!=null) {
p=p.down;
}
while (p.left!=null) {
p=p.left;
}
if (p.right!=null) {
p=p.right;
}
while (p.right!=null) {
builder.append(p);
builder.append("\n");
p=p.right;
}
return builder.toString();
}
}
总结
根据上面代码可以总结出跳跃表的特性
- 首先key是可以通过比较来确定前后顺序的
- 每个节点可以连接上下左右方向的节点
- 可以通过key从表的头部开始查找,知道获得<=key 的节点
- 插入节点,根据节点的key找到合适位置,插入之后开始升层操作,每次概率升层,添加具备这个key的节点.
- 只有最后一层的节点中存在value
- 在跳跃表的头部和尾部都是指向最上面一层头部和尾部的节点,同样是只存在key
- 查找的时候是从上向下
- 插入和删除的时候都是从下向上