跳表(SkipList)的Java实现

2 篇文章 0 订阅

跳表(SkipList)的Java实现

什么是跳表

简单说跳表(SkipList)是一种可以替代平衡树的数据结构。跳跃表让已排序的数据分布在多层次的链表结构中,默认是将Key值升序排列的,以 0-1 的随机值决定一个数据是否能够攀升到高层次的链表中。它通过容许一定的数据冗余,达到 “以空间换时间” 的目的。

跳跃表的效率和AVL相媲美,查找/添加/插入/删除操作都能够在O(LogN)的复杂度内完成。

Java实现

Java API里已经内置了ConcurrentSkipListSet和ConcurrentSkipListMap,有兴趣的可以去看看源码,这里只是单纯的用链表来实现一个SkipList。

基本Node结构
public class SkipListNode<T> {
    public int key;
    public T value;
    public SkipListNode<T> pre,next,up,down; //上下左右四个节点,pre和up存在的意义在于 "升层"的时候需要查找相邻节点

    public static final int HEAD_KEY = Integer.MIN_VALUE; // 负无穷
    public static final int  TAIL_KEY = Integer.MAX_VALUE; // 正无穷
    public SkipListNode(int k,T v) {
        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;
    }
    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 size; //链表大小
    private int listLevel; //链表层数
    private Random random; // 用于投掷硬币
    private static final double PROBABILITY=0.5; //向上提升一个的概率

    public SkipList() {
        head = new SkipListNode<>(SkipListNode.HEAD_KEY,null);
        tail = new SkipListNode<>(SkipListNode.TAIL_KEY,null);

        head.next = tail;
        tail.pre = head;

        size = 0;
        listLevel = 0;
        random = new Random();

    }

    public SkipListNode<T> get(int key){
        SkipListNode<T> p = findNode(key);
        if (p.key == key){
            return p;
        }
        return null;
    }

    /**
     * 首先查找到包含key值的节点,将节点从链表中移除,接着如果有更高level的节点,则repeat这个操作即可。
     * @param k
     * @return
     */
    public T remove(int k){
        SkipListNode<T> p = get(k);
        if (p == null){
            return null;
        }

        T oldV = p.value;
        SkipListNode<T> q;

        while (p != null){
            q = p.next;
            q.pre = p.pre;
            p.pre.next = q;

            p = p.up;
        }


        return oldV;
    }

    /**
     * put方法有一些需要注意的步骤:
     * 1.如果put的key值在跳跃表中存在,则进行修改操作;
     * 2.如果put的key值在跳跃表中不存在,则需要进行新增节点的操作,并且需要由random随机数决定新加入的节点的高度(最大level);
     * 3.当新添加的节点高度达到跳跃表的最大level,需要添加一个空白层(除了-oo和+oo没有别的节点)
     * @param k
     * @param v
     */
    public void put(int k,T v){
        //step 1
        System.out.println("添加key:"+k);
        SkipListNode<T> p = findNode(k); //这里不用get是因为下面可能用到这个节点
//        System.out.println("找到p:"+p);
        if (p.key == k){
            p.value = v;
            return;
        }

        //step 2
        SkipListNode<T> q=new SkipListNode<T>(k, v);
        insertNode(p, q);

        int currentLevel = 0;
        while (random.nextDouble() > PROBABILITY){
            if (currentLevel>=listLevel){
                addEmptyLevel();
                System.out.println("升层");
            }
            //找到左边第一个有上层节点的数据;
            while (p.up == null){
//                System.out.println(p);
                p = p.pre;
//                System.out.println("找到第一个有上层的节点"+p);
            }
            p = p.up;
            //创建q的镜像变量(只存储k,不存储v,因为查找的时候会自动找最底层数据),
            SkipListNode<T> z=new SkipListNode<T>(k, null);
            insertNode(p,z);
            z.down = q;
            q.up=z;

            //别忘了把指针转移到上一层!!
            q=z;
            currentLevel++;
        }
        size++;
//        System.out.println("添加后:"+this);
    }

    private void addEmptyLevel() {
        SkipListNode<T> p1=new SkipListNode<T>(SkipListNode.HEAD_KEY, null);
        SkipListNode<T> p2=new SkipListNode<T>(SkipListNode.TAIL_KEY, null);
        p1.next = p2;
        p1.down = head;

        p2.pre = p1;
        p2.down = tail;

        head.up = p1;
        tail.up = p2;

        head=p1;
        tail=p2;

        listLevel++;

    }

    private void insertNode(SkipListNode<T> p, SkipListNode<T> q) {
        q.next = p.next;
        q.pre = p;
        p.next.pre = q;
        p.next = q;
    }

    /**
     * 如果传入的key值在跳跃表中不存在,则findNode返回跳跃表中key值小于key,并且key值相差最小的底层节点;
     * 所以不能用此方法来代替get
     * @param key
     * @return
     */
    private SkipListNode<T> findNode(int key){
        SkipListNode<T> p = head;
        while (true){
//            System.out.println("p.next.key:"+p.next.key);
            if (p.next!=null && p.next.key<=key){
                p = p.next;
            }
//            System.out.println("找到node:"+p);
            //找到最底层数据
            if (p.down != null){
//                System.out.println("node.down:"+p);
                p = p.down;
            }else if (p.next!=null && p.next.key > key){
                break;
            }
        }

        return p;
    }

    @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.pre!=null) {
            p=p.pre;
        }
        if (p.next!=null) {
            p=p.next;
        }
        while (p.next!=null) {
            builder.append(p);
            builder.append("\n");
            p=p.next;
        }

        return builder.toString();
    }

    public boolean isEmpty() {
        return size == 0;
    }

    public int size() {
        return size;
    }

    public int getLevel(){
        return listLevel;
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        SkipList<String> list=new SkipList<String>();
        list.put(2, "yan");
        list.put(1, "co");
        list.put(3, "feng");
        list.put(1, "cao");//测试同一个key值
        list.put(4, "曹");
        list.put(6, "丰");
        list.put(5, "艳");
        list.put(11, "陈");
        list.put(8, "李");
        list.put(100, "边");
        System.out.println("列表元素:\n"+list);
        System.out.println("删除100:"+list.remove(100));
        System.out.println("列表元素:\n"+list);
        System.out.println("5对于的value:\n"+list.get(5).value);
        System.out.println("链表大小:"+list.size()+",深度:"+list.getLevel());
    }

}

参考文献:
[1]: https://blog.csdn.net/BrilliantEagle/article/details/52206261
[2]: https://blog.csdn.net/DERRANTCM/article/details/79063312

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值