SkipList A Probabilistic Alternative to Balanced Trees

Skip Lists: A Probabilistic Alternative to Balanced Trees

写博客,顺便看了一篇论文,也是挺好的。其实这篇博客我一直想写,但是怕写不好,没什么勇气。

  跳跃表是一种可以用来替代平衡树的数据结构,因为它使用的是基于概率选择而不是严格平衡的方法,导致了在跳跃表中,插入和删除变得更加简单,速度明显变快。

  跳跃表大概长这个样子,由图可以看出,是一种空间换时间的算法。与平衡二叉树的比较而言,在平衡二叉树的插入和删除时往往很可能需要全局的调整,而跳跃表只需要做局部的调整,查询速度都一样为O(lgn)。,在高并发下,为了线程安全,你可能需要对整个二叉树进行加锁,而跳跃表只需要加锁其中的一部分。基于上述的原因,Java并发包使用SkipList来实现Map,Set即

ConcurrentHashMapConcurrentSkipListSet

和HashMap和HashSet不一样,通过跳跃表生成的数据存储是有序的。

也可以看出,本质上是同时维护多个*分层链表*。

对于每个节点,可能有这样的一“operation”

  • next(p): Return the position following p on the same level.同一层的下一个节点
  • prev(p): Return the position preceding p on the same level. 同一层的前一个节点
  • below(p): Return the position below p in the same tower.同一个柱子(tower 塔)的下一个节点
  • above(p): Return the position above p in the same tower.同一个柱子(tower 塔)的上一个节点

说归说,但是实际上并没有什么用,这里将不使用这种访问方法,因为数组的下标就能实现这样的operation。

首先定义节点SkipNode,一个节点值,一个指针数组。


随机建表


  下图是从论文上截取出来的初始化建表的过程,通过一层一层的建立跳跃表的结构。
首先是最低层,然后随机选择(概率因子p也可以自己设定)每个节点有1/p个概率被选择上,构建第二层,然后随机选择第二层的1/p个节点构建第三层,以此类推直到构建到你想要的高度(可以自己设定的)。


查找算法


  查找一个元素,比如查找上图的元素12,顺序应该是这样的:从上往下查找,6小,向前,NIL;6向下一层,25大;向下一层,9小,向前,25大;向下一层,12查找到。返回

  论文上的句子我简单翻译一下,可能不是很明白:在某一层查找一个元素,如果查找到了则直接返回,如果已经走到了最后,或者遇到的值已经超过想找的元素的大小,则向下一层继续寻找,直到找到元素或者走到了最底层不能向下但是仍然没有找到则返回false。

论文上的算法:

Search(list, searchKey)
    x := list→header
    -- loop invariant: x→key < searchKey
    for i := list→level downto 1 do
        while x→forward[i]→key < searchKey do
            x := x→forward[i]
    -- x→key < searchKey ≤ x→forward[1]→key
    x := x→forward[1]
    if x→key = searchKey then return x→value
        else return failure

可以看出,算法真的是令人惊喜,也许是我认识的算法太少了,通过两层循环就找到了想要的元素,可以自己推敲一下。首先从顶层开始,内层循环,一直向前,直到数值比想要的大,然后向下一层。真是精美啊,我特喜欢。

实现起来就是这样的,这里只实现一个Set类型的,判断是否存在即可。


插入算法


  插入算法,首先要考虑,插入的位置,这和查找算法实际上做的是同一个过程。然后找到你要插入位置的前置,生成一个节点,插入即可。

涉及到的关键问题有

  1. 查找并记录要插入位置的前一个元素
  2. 随机生成tower的lvl的大小,如果大于最大的高度level应该做调整。
  3. 查找的前一个元素和要生成元素的对接,形成链表。

此处贴出论文上的代码,对于自己的代码,将在最后全部呈现出来。

Insert(list, searchKey, newValue)
    local update[1..MaxLevel]
    x := list→header
    for i := list→level downto 1 do
        while x→forward[i]→key < searchKey do
            x := x→forward[i]
        -- x→key < searchKey ≤ x→forward[i]→key
        update[i] := x
    x := x→forward[1]
    if x→key = searchKey then x→value := newValue
    else
        lvl := randomLevel()
        if lvl > list→level then
            for i := list→level + 1 to lvl do
                update[i] := list→header
            list→level := lvl
        x := makeNode(lvl, searchKey, value)
        for i := 1 to level do
            x→forward[i] := update[i]→forward[i]
            update[i]→forward[i] := x

很重要的重点,randomLevel()怎么做的。

说出来你可能不信:

 public static int randomLevel() {
        int lvl = 1;
        while (lvl < MAX_LEVEL && Math.random() < P)
            lvl++;
        return lvl;
    }

就这么一点点,其中P定义为你想选择的概率 ,可以是1/2,一般选择

public static final double P = 1 / Math.E;

一般你可能会觉得这样产生出来的结构会不会很不均匀啊之类的,实践一下就知道了,是不会的。


删除算法


  删除算法也是一样的,首先找到要删除的节点,记录删除节点的前一个位置,然后向后拼接成完整的链表即可。直接看算法:

Delete(list, searchKey)
    local update[1..MaxLevel]
    x := list→header
    for i := list→level downto 1 do
        while x→forward[i]→key < searchKey do
            x := x→forward[i]
        update[i] := x
    x := x→forward[1]
    if x→key = searchKey then
        for i := 1 to list→level do
            if update[i]→forward[i] ≠ x then break
            update[i]→forward[i] := x→forward[i]
        free(x)
        while list→level > 1 and
            list→header→forward[list→level] = NIL do
        list→level := list→level – 1

Code 代码


package SkipNode;

/**
 * 跳表节点数据存储结构
 */
class SkipNode<E extends Comparable<? super E>> {
    public final E value; //节点存储的数据  
    public final SkipNode<E>[] forward; //节点的指针数组  

    /**
     * 根据节点的层级构造一个节点
     *
     * @param level 节点层级
     * @param value 节点存储值
     */
    @SuppressWarnings("unchecked")
    public SkipNode(int level, E value) {
        forward = new SkipNode[level + 1];//level层的元素后面带着level+1的指针数组  
        this.value = value;
    }

}

public class SkipSet<E extends Comparable<? super E>> {

    /**
     * 概率因子,1/E
     */
//  public static final double P = 0.5;  
    public static final double P = 1 / Math.E;
    /**
     * 最大层级
     */
    public static final int MAX_LEVEL = 6;

    /**
     * 开始节点,不存值,贯穿所有层
     */
    public final SkipNode<E> header = new SkipNode<E>(MAX_LEVEL, null);
    /**
     * 当前跳表的最高层级
     */
    public int level = 0;

    /**
     * 插入一个元素
     *
     * @param value 待插入值
     */
    @SuppressWarnings("unchecked")
    public void insert(E value) {
        SkipNode<E> x = header;
        SkipNode<E>[] update = new SkipNode[MAX_LEVEL + 1];
        //查找元素的位置,这里其实做了一次contain操作,注释见contain  
        for (int i = level; i >= 0; i--) {
            while (x.forward[i] != null
                    && x.forward[i].value.compareTo(value) < 0) {
                x = x.forward[i];
            }
            //update[i]是比value小的数里面最大的,是value的前置节点  
            update[i] = x;
        }
        x = x.forward[0];

        //此处不允许插入相同元素,为一个set  
        if (x == null || !x.value.equals(value)) {//跳表中不包含所要插的元素  
            //随机产生插入的层级  
            int lvl = randomLevel();
            //产生的随机层级比当前跳表的最高层级大,需要添加相应的层级,并更新最高层级  
            if (lvl > level) {
                for (int i = level + 1; i <= lvl; i++) {
                    update[i] = header;
                }
                level = lvl;
            }

            //生成新节点  
            x = new SkipNode<E>(lvl, value);
            //调整节点的指针,和指向它的指针  
            for (int i = 0; i <= lvl; i++) {
                x.forward[i] = update[i].forward[i];
                update[i].forward[i] = x;
            }

        }
    }

    /**
     * 删除一个元素
     *
     * @param value 待删除值
     */
    @SuppressWarnings("unchecked")
    public void delete(E value) {
        SkipNode<E> x = header;
        SkipNode<E>[] update = new SkipNode[MAX_LEVEL + 1];
        //查找元素的位置,这里其实做了一次contain操作,注释见contain  
        for (int i = level; i >= 0; i--) {
            while (x.forward[i] != null
                    && x.forward[i].value.compareTo(value) < 0) {
                x = x.forward[i];
            }
            update[i] = x;
        }
        x = x.forward[0];
        //删除元素,调整指针  
        if (x.value.equals(value)) {
            for (int i = 0; i <= level; i++) {
                if (update[i].forward[i] != x)
                    break;
                update[i].forward[i] = x.forward[i];
            }
            //如果元素为本层最后一个元素,则删除同时降低当前层级  
            while (level > 0 && header.forward[level] == null) {
                level--;
            }

        }
    }

    /**
     * 查找是否包含此元素
     *
     * @param searchValue 带查找值
     * @return true:包含;false:不包含
     */
    public boolean contains(E searchValue) {
        SkipNode<E> x = header;
        //从开始节点的最高层级开始查找  
        for (int i = level; i >= 0; i--) {
            //当到达本层级的NULL节点或者遇到比查找值大的节点时,转到下一层级查找  
            while (x.forward[i] != null
                    && x.forward[i].value.compareTo(searchValue) < 0) {
                x = x.forward[i];
            }
        }
        x = x.forward[0];
        //此时x有三种可能,1.x=null,2.x.value=searchValue,3.x.value>searchValue  
        return x != null && x.value.equals(searchValue);
    }

    /**
     * 这里是跳表的精髓所在,通过随机概率来判断节点的层级
     *
     * @return 节点的层级
     */
    public static int randomLevel() {
        int lvl = 1;
        while (lvl < MAX_LEVEL && Math.random() < P)
            lvl++;
        return lvl;
    }

    /**
     * 输出跳表的所有元素
     * 遍历最底层的元素即可
     */
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("{");
        SkipNode<E> x = header.forward[0];
        while (x != null) {
            sb.append(x.value);
            x = x.forward[0];
            if (x != null)
                sb.append(",");
        }
        sb.append("}");
        return sb.toString();
    }

}  

与JDK ConcurrentSkipListSet 的运行比较

package SkipNode;

    import java.util.concurrent.ConcurrentSkipListSet;

    /**
     * Created by bamboo on 2016/9/7.
     */
    public class Main {
        public static void main(String[] args) {
            SkipSet<Integer> skipSet = new SkipSet<Integer>();
            long startTime = System.currentTimeMillis();//获取当前时间
            for (int i = 0; i < 900000; i++) {
                skipSet.insert(i);
            }
            long endTime = System.currentTimeMillis();
            System.out.println("SkipList程序运行时间:" + (endTime - startTime) + "ms");


            ConcurrentSkipListSet<Integer> set = new ConcurrentSkipListSet<>();
            long start = System.currentTimeMillis();//获取当前时间
            for (int i = 0; i < 900000; i++) {
                set.add(i);
            }
            long end = System.currentTimeMillis();
            System.out.println("concurrentSkipList程序运行时间:" + (end - start) + "ms");



        }
    }

比较:

    SkipNode.Main
    SkipList程序运行时间:31448ms
    concurrentSkipList程序运行时间:652ms

    Process finished with exit code 0

不堪入目啊 我还是找个地方藏起来吧。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值