跳跃表(Skip lists)(算法导论第12课笔记)

简介

跳跃表支持插入、删除、搜索。是个动态(插删)搜索数据结构。同时每个操作复杂度都是对数级别的。
“是个随机化的、简单的数据结构。”
“几乎可以保证每一步操作都是logN的可能性为(1-1/n^α),n可以取很大。因此极小概率超过logN”

其他动态搜索数据结构:树堆(treaps)、红黑树(red-black trees)、B树(B-trees)。

结构

如果只是一个双向链表,则操作的复杂度是O(n)。
下图分别是wiki和课程里两种跳表数据结构的示意图。
其中,有多层“链表”组成,链表节点是有序的,最下层链表保存了所有数据。
在这里插入图片描述

搜索

  1. 再顶层链表上向右搜索,直到搜索过头,则返回。
  2. 向下走一层继续向右搜索。继续1。
  3. 直到搜索到该节点或者搜到头,或者底层中也没有该元素(至少确定了该插入的位置)。

复杂度分析

  1. 当只有两层时,考虑L2层的链表节点在L1层上均匀分布,L1层的个数为n,则复杂度为
    ∣ L 2 ∣ + n ∣ L 2 ∣ |L2| + \frac{n}{|L2|} L2+L2n
    因 此 当 ∣ L 2 ∣ = n 时 , 最 小 值 为 结 果 为 , 2 n 因此当|L2| = \sqrt{n} 时,最小值为结果为, 2\sqrt{n} L2=n 2n
  2. 当有n个节点时,设置log(n)层,每一层的节点个数是下一层的一半。因此每一层上最多只要走两步。最终复杂度为
    2 ∗ l o g 2 n 2*log_{2}^{n} 2log2n

插入

  1. 通过搜索的步骤,找出待插入的位置。
  2. 判断该数据除了底层以外还需要插入到上层的哪些链表里。(随机,比如抛硬币),(1/2的概率往上升一层,如果往上升了,则接着1/2的概率往上升,即在每一层的概率为1,1/2,1/4,1/8…)、

注意:在一开始时要插入一个负无穷,让其升到最高(保持每个链表都有该元素),因为每次都从最上一个链表开始搜索。防止链表中第一个元素的高度小于后面的元素,则此情况无法在最高的链表上进行遍历。

删除

通过搜索找到该元素,再把每条链表上的该元素删除即可。

性质

with high probability

  1. 对于任意大于等于1的α,存在合适的常数,使事件E的概率至少为1-O(1/(n^α))
  2. 链表的最大层数的期望是log(N)。

证明复杂,略

代码实现

package com.wangqi.javase.datastruct;

import java.util.Random;

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() {
        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 int size() {
        return nodes;
    }

    /**
     * 从最高层第一个(左上角即head开始找),一直找到最下面一层,找到要插入的位置前面的那个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.key) {
            return p;
        } else {
            return null;
        }
    }

    /**
     * 向跳跃表中添加key-value
     */
    public void put(int k, T v) {
        SkipListNode<T> p = findNode(k);
        //如果key值相同,替换原来的vaule即可结束
        if (k == p.key) {
            p.value = v;
            return;
        }
        SkipListNode<T> q = new SkipListNode<T>(k, v);
        //在p后面插入q
        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++;//层数递增
    }

    /**
     * 在跳跃表中删除k
     */
    public boolean remove(int k) {
        SkipListNode<T> p = findNode(k);
        if (p.key != k) { //跳表中不存在k
            return false;
        }
        // 从底层开始往上删
        while (p != null) {
            horizontalLink(p.left, p.right);
            p = p.up;
        }
        nodes--;
        // 判断链表的最高层是否受到影响
        while (head.right == tail && listLevel != 1) {
            head = head.down;
            tail = tail.down;
            listLevel--;
        }
        return true;
    }

    //node1后面插入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;
    }

    /**
     * 打印出原始数据
     */
    @Override
    public String toString() {
        if (size() == 0) {
            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();
    }

    //节点
    private static class SkipListNode<T> {
        private int key;
        private 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 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.key == key) && (ent.value == value);
        }

        @Override
        public String toString() {
            return "key-value:" + key + "-" + value;
        }
    }
}

测试函数

package com.wangqi.javase.datastruct;

import java.util.HashMap;
import java.util.Map;

public class SkipListTest {
    public static void main(String[] args) {
        SkipList<String> skipList = new SkipList<>();
        skipList.put(1, "tom");
        //System.out.println(skipList.search(1));
        //System.out.println(skipList.search(2));
        skipList.put(2, "bob");
        //System.out.println(skipList.search(2));
        skipList.put(1, "jack");
        //System.out.println(skipList.search(1));
        System.out.println(skipList.remove(1));
        System.out.println(skipList.remove(2));
        System.out.println(skipList.size());

    }
}

参考跳跃表(Skip List)-实现(Java)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值