每日一省之 ———— 一个简单的跳跃表(Skip List)的Java实现

第一次知道跳表这种数据结构的时间大概是在一年前(说这句话时候可能就被无数同胞鄙视了),但自己却不知道如何实现。当时印象最深的就是这篇文章跳跃表(Skip List)-实现(Java),因为这篇文章中的Skip List的实现方式最让人容易理解,我最初也是通过这篇文章对跳表有了更进一步的认识,所以,真的要在这里感谢这篇文章的主人。一年之后,我发现自己对跳表的认识又模糊不清了,所以最先想到的也是这篇文章。今天再次拜读此文,同时实现了其中未给出的删除方法。并增加了泛型,但泛型也只是对value采用了泛型,对Key依然采用原文中的String类型。所以依然比较简单和局限。之所以贴出来,是为了增进自己对Skip List的理解和作为备忘。原文的链接如之前所述,原文具体作者其实我也不知道是谁,只想在此默默的说声感谢。当然了,若原文作者觉得我有什么冒犯或侵权的行为,我会立马删帖。

关于跳表的定义和介绍,读者可以参考原文。这里就直接给出原码了,这里的原码与原文的唯一的一点区别就是,我这里给出了原文没给出的删除方法(原文其实参考的是一篇英文文章,英文文章给出了删除方法的伪代码,我直到后来才发现,不过自己的实现和和伪代码中的思路是一样的,代码略显多余,此处贴出来的是我自己实现的删除方法)。可能实现上比较糟糕,所以也恳请大家批评指正!!!

1 对跳表中各个元素(键值对)的封装类SkipListEntry.java


public class SkipListEntry<V>
{
    public String key;
    public V value;

    public int pos; // 主要为了打印 链表用

    public SkipListEntry<V> up, down, left, right; // 上下左右 四个指针

    public static String negInf = new String("-oo"); // 负无穷
    public static String posInf = new String("+oo"); // 正无穷

    public SkipListEntry(String k, V v)
    {
        key = k;
        value = v;
        up = down = left = right = null;
    }

    public V getValue()
    {
        return value;
    }

    public String getKey()
    {
        return key;
    }

    public V setValue(V val)
    {
        V oldValue = value;
        value = val;
        return oldValue;
    }

    @SuppressWarnings("unchecked")
    public boolean equals(Object o)
    {
        SkipListEntry<V> entry;
        try
        {
            entry = (SkipListEntry<V>) o; // 检测类型
        } catch (ClassCastException ex)
        {
            return false;
        }
        return (entry.getKey() == key) && (entry.getValue().equals(value));
    }

    public String toString()
    {
        return "(" + key + "," + value + ")";
    }
}

2 Skip List的具体实现(包含增、删、改、查 )


import java.util.Random;


/**
 * 跳表的一种简单实现。key只能为字符串类型,value可以为任意对象类型
 * @param <V>
 * @author xxx 2017年2月14日 下午9:42:06
 * @version v1.0
 */
public class SkipList<V>
{
    public SkipListEntry<V> head; // 顶层的第一个元素
    public SkipListEntry<V> tail; // 顶层的最后一个元素

    public int size; // 跳跃表中的元素个数

    public int height; // 跳跃表的高度
    public Random flag; // 投掷硬币

    /**
     * 默认构造函数
     * @author xxx 2017年2月14日 下午9:32:22
     * @since v1.0
     */
    public SkipList() 
    {
        head = new SkipListEntry<V>(SkipListEntry.negInf, null);
        tail = new SkipListEntry<V>(SkipListEntry.posInf, null);

        head.right = tail;
        tail.left = head;

        size = 0;
        height = 0;
        flag = new Random();
    }

    /**
     * 返回元素的个数
     * @return
     * @author xxx 2017年2月14日 下午9:35:22
     * @since v1.0
     */
    public int size()
    {
        return size;
    }

     /**
      * 判断跳表中的元素个数是否为零
      * @return
      * @author xxx 2017年2月14日 下午9:35:52
      * @since v1.0
      */
    public boolean isEmpty()
    {
        return (size == 0);
    }


    /**
     * 从最顶层的第一个元素,也即head元素开始查找,直到找到第0层、要插入的位置前面的那个key
     * @param k
     * @return
     * @author xxx 2017年2月14日 下午9:42:12
     * @since v1.0
     */
    private SkipListEntry<V>  findEntry(String k)
    {
        SkipListEntry<V> p = head;

        while (true)
        {
            /*
             * 一直向右找,例: k=34。 10 ---> 20 ---> 30 ---> 40 ^ | p 会在30处停止
             */
            while (p.right.key != SkipListEntry.posInf && p.right.key.compareTo(k) <= 0)
            {
                p = p.right;
            }
            // 如果还有下一层,就到下一层继续查找
            if (p.down != null)
            {
                p = p.down;
            } else
            {
                break; // 到了最下面一层 就停止查找
            }
        }

        return p; // p.key <= k
    }

    /** 返回和key绑定的值 */
    public V get(String k)
    {
        SkipListEntry<V> p = findEntry(k);

        if (k.equals(p.getKey()))
        {
            return p.value;
        } else
        {
            return null;
        }
    }


    /**
     * 往跳表中插入一个键值对,如果键已经存在,则覆盖相应的值并返回旧值
     * @param k
     * @param v
     * @return
     * @author xxx 2017年2月14日 下午9:48:54
     * @since v1.0
     */
    public V put(String k, V v)
    {
        System.out.println("-----插入[" + k + "]之前的跳跃表是:-----");
        printHorizontal();

        SkipListEntry<V> p, q;

        p = findEntry(k);

        if (k.equals(p.getKey()))
        {
            V old = p.value;
            p.value = v;
            return old;
        }

        q = new SkipListEntry<V>(k, v);
        q.left = p;
        q.right = p.right;
        p.right.left = q;
        p.right = q;

        int currentLevel = 0; // 当前层 currentLevel = 0

        // 随机值小于0.5,则插入的键值对对应的键需要在上一层建立关联,同时有可能增加跳表的高度
        while (flag.nextDouble() < 0.5)
        {
            // 如果超出了高度,需要重新建一个顶层
            if (currentLevel >= height)
            {
                SkipListEntry<V> p1, p2;

                height = height + 1;
                p1 = new SkipListEntry<V>(SkipListEntry.negInf, null);
                p2 = new SkipListEntry<V>(SkipListEntry.posInf, null);

                p1.right = p2;
                p1.down = head;

                p2.left = p1;
                p2.down = tail;

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

                head = p1;
                tail = p2;
            }

            while (p.up == null)
            {
                p = p.left;
            }
            p = p.up;

            SkipListEntry<V> e;

            /*
             * 注意,本实现中只有第0层的链表持有键对应的值,1 ~ height 层中的SkipListEntry对象
             * 仅仅持有键的引用,值为空,以便节省空间。
             */
            e = new SkipListEntry<V>(k, null);
            e.left = p;
            e.right = p.right;
            e.down = q;
            p.right.left = e;
            p.right = e;
            q.up = e;

            q = e; // q 进行下一层迭代
            currentLevel = currentLevel + 1; // 当前层 +1

        }
        // 插入一个键值对后总数加1
        size = size + 1;

        System.out.println("-----插入[" + k + "]之后的跳跃表是:-----");
        printHorizontal();
        return null;
    }

    /**
     * 根据键删除键值对
     * @param key
     * @return
     * @author xxx 2017年2月14日 下午10:08:17
     * @since v1.0
     */
    public void remove(String key)
    {
        SkipListEntry<V> p = findEntry(key);

        if(!p.getKey().equals(key)) {
            return;
        }

        //删除元素后重新关联,同时使被删除的对象游离,便于垃圾回收
        p.left.right = p.right;
        p.right.left = p.left;
        p.right = null;
        p.left = null;
        //自底向上,使所有键等于key的SkipListEntry对象左右两个方向的引用置空
        while(p.up != null) {
            p = p.up;
            p.left.right = p.right;
            p.right.left = p.left;
            p.right = null;
            p.left = null;
        }

        //自顶向下,使所有键等于key的SkipListEntry对象上下两个方向的引用置空
        while(p.down != null) {
            SkipListEntry<V> temp = p.down;
            p.down = null;
            temp.up = null;
            p = temp;
        }

        /*
         * 删除元素后,如果顶层的链表只有head和tail两个元素,则删除顶层。
         * 删除顶层以后最新的顶层如果依然只有head和tail两个元素,则也要被删除,以此类推。
         */
        while(head.right.key == tail.key && height > 0) {
            SkipListEntry<V> p1, p2;
            p1 = head.down;
            p2 = tail.down;

            head.right = null;
            head.down = null;

            tail.left = null;
            tail.down = null;

            p1.up = null;
            p2.up = null;
            head = p1;
            tail = p2;
            height = height - 1;
        }
        //成功移除一个元素,大小减1
        size = size - 1;

        System.out.println("-----删除[" + key + "]后的跳跃表是:-----");
        printHorizontal();

    }

    /**
     * 打印出跳表的图示结构(水平方向)
     * @author xxx 2017年2月14日 下午10:35:36
     * @since v1.0
     */
    public void printHorizontal()
    {
        String s = "";
        int i;
        SkipListEntry<V> p;

        p = head;

        while (p.down != null)
        {
            p = p.down;
        }

        i = 0;
        while (p != null)
        {
            p.pos = i++;
            p = p.right;
        }

        p = head;
        while (p != null)
        {
            s = getOneRow(p);
            System.out.println(s);
            p = p.down;
        }
    }

    private String getOneRow(SkipListEntry<V> p)
    {
        String s;
        int a, b, i;

        a = 0;

        s = "" + p.key;
        p = p.right;

        while (p != null)
        {
            SkipListEntry<V> q;

            q = p;
            while (q.down != null)
                q = q.down;
            b = q.pos;

            s = s + " <-";

            for (i = a + 1; i < b; i++)
                s = s + "--------";

            s = s + "> " + p.key;

            a = b;

            p = p.right;
        }

        return s;
    }

    /**
     * 打印出跳表的图示结构(垂直方向)
     * @author xxx 2017年2月14日 下午10:35:36
     * @since v1.0
     */
    public void printVertical()
    {
        String s = "";
        SkipListEntry<V> p;
        p = head;
        while (p.down != null)
            p = p.down;

        while (p != null)
        {
            s = getOneColumn(p);
            System.out.println(s);

            p = p.right;
        }
    }

    private String getOneColumn(SkipListEntry<V> p)
    {
        String s = "";
        while (p != null)
        {
            s = s + " " + p.key;
            p = p.up;
        }
        return (s);
    }

}

3 测试


public class Test
{

    public static void main(String[] args)
    {
        SkipList<String> s = new SkipList<String>();
        s.put("ABC", "");
        s.put("DEF", "");
        s.put("KLM", "");
        s.put("HIJ", "");
        s.put("GHJ", "");
        s.put("AAA", "");

        s.remove("ABC");
        s.remove("DEF");
        s.remove("KLM");
        s.remove("HIJ");
        s.remove("GHJ");
        s.remove("AAA");


        s.put("ABC", "");
        s.put("DEF", "");
        s.put("KLM", "");
        s.put("HIJ", "");
        s.put("GHJ", "");
        s.put("AAA", "");

    }

}
//运行测试后结果示例如下(注意:由于跳表的特性,每次运行结果都不一样)

-----插入[ABC]之前的跳跃表是:-----
-oo <-> +oo
-----插入[ABC]之后的跳跃表是:-----
-oo <-> ABC <-> +oo
-oo <-> ABC <-> +oo
-----插入[DEF]之前的跳跃表是:-----
-oo <-> ABC <-> +oo
-oo <-> ABC <-> +oo
-----插入[DEF]之后的跳跃表是:-----
-oo <---------> DEF <-> +oo
-oo <-> ABC <-> DEF <-> +oo
-oo <-> ABC <-> DEF <-> +oo
-----插入[KLM]之前的跳跃表是:-----
-oo <---------> DEF <-> +oo
-oo <-> ABC <-> DEF <-> +oo
-oo <-> ABC <-> DEF <-> +oo
-----插入[KLM]之后的跳跃表是:-----
-oo <---------> DEF <-> KLM <-> +oo
-oo <-> ABC <-> DEF <-> KLM <-> +oo
-oo <-> ABC <-> DEF <-> KLM <-> +oo
-----插入[HIJ]之前的跳跃表是:-----
-oo <---------> DEF <-> KLM <-> +oo
-oo <-> ABC <-> DEF <-> KLM <-> +oo
-oo <-> ABC <-> DEF <-> KLM <-> +oo
-----插入[HIJ]之后的跳跃表是:-----
-oo <---------> DEF <---------> KLM <-> +oo
-oo <-> ABC <-> DEF <---------> KLM <-> +oo
-oo <-> ABC <-> DEF <-> HIJ <-> KLM <-> +oo
-----插入[GHJ]之前的跳跃表是:-----
-oo <---------> DEF <---------> KLM <-> +oo
-oo <-> ABC <-> DEF <---------> KLM <-> +oo
-oo <-> ABC <-> DEF <-> HIJ <-> KLM <-> +oo
-----插入[GHJ]之后的跳跃表是:-----
-oo <-----------------> GHJ <-----------------> +oo
-oo <-----------------> GHJ <-----------------> +oo
-oo <-----------------> GHJ <-----------------> +oo
-oo <-----------------> GHJ <-----------------> +oo
-oo <-----------------> GHJ <-----------------> +oo
-oo <-----------------> GHJ <-----------------> +oo
-oo <---------> DEF <-> GHJ <---------> KLM <-> +oo
-oo <-> ABC <-> DEF <-> GHJ <---------> KLM <-> +oo
-oo <-> ABC <-> DEF <-> GHJ <-> HIJ <-> KLM <-> +oo
-----插入[AAA]之前的跳跃表是:-----
-oo <-----------------> GHJ <-----------------> +oo
-oo <-----------------> GHJ <-----------------> +oo
-oo <-----------------> GHJ <-----------------> +oo
-oo <-----------------> GHJ <-----------------> +oo
-oo <-----------------> GHJ <-----------------> +oo
-oo <-----------------> GHJ <-----------------> +oo
-oo <---------> DEF <-> GHJ <---------> KLM <-> +oo
-oo <-> ABC <-> DEF <-> GHJ <---------> KLM <-> +oo
-oo <-> ABC <-> DEF <-> GHJ <-> HIJ <-> KLM <-> +oo
-----插入[AAA]之后的跳跃表是:-----
-oo <-------------------------> GHJ <-----------------> +oo
-oo <-------------------------> GHJ <-----------------> +oo
-oo <-------------------------> GHJ <-----------------> +oo
-oo <-------------------------> GHJ <-----------------> +oo
-oo <-------------------------> GHJ <-----------------> +oo
-oo <-> AAA <-----------------> GHJ <-----------------> +oo
-oo <-> AAA <---------> DEF <-> GHJ <---------> KLM <-> +oo
-oo <-> AAA <-> ABC <-> DEF <-> GHJ <---------> KLM <-> +oo
-oo <-> AAA <-> ABC <-> DEF <-> GHJ <-> HIJ <-> KLM <-> +oo
-----删除[ABC]后的跳跃表是:-----
-oo <-----------------> GHJ <-----------------> +oo
-oo <-----------------> GHJ <-----------------> +oo
-oo <-----------------> GHJ <-----------------> +oo
-oo <-----------------> GHJ <-----------------> +oo
-oo <-----------------> GHJ <-----------------> +oo
-oo <-> AAA <---------> GHJ <-----------------> +oo
-oo <-> AAA <-> DEF <-> GHJ <---------> KLM <-> +oo
-oo <-> AAA <-> DEF <-> GHJ <---------> KLM <-> +oo
-oo <-> AAA <-> DEF <-> GHJ <-> HIJ <-> KLM <-> +oo
-----删除[DEF]后的跳跃表是:-----
-oo <---------> GHJ <-----------------> +oo
-oo <---------> GHJ <-----------------> +oo
-oo <---------> GHJ <-----------------> +oo
-oo <---------> GHJ <-----------------> +oo
-oo <---------> GHJ <-----------------> +oo
-oo <-> AAA <-> GHJ <-----------------> +oo
-oo <-> AAA <-> GHJ <---------> KLM <-> +oo
-oo <-> AAA <-> GHJ <---------> KLM <-> +oo
-oo <-> AAA <-> GHJ <-> HIJ <-> KLM <-> +oo
-----删除[KLM]后的跳跃表是:-----
-oo <---------> GHJ <---------> +oo
-oo <---------> GHJ <---------> +oo
-oo <---------> GHJ <---------> +oo
-oo <---------> GHJ <---------> +oo
-oo <---------> GHJ <---------> +oo
-oo <-> AAA <-> GHJ <---------> +oo
-oo <-> AAA <-> GHJ <---------> +oo
-oo <-> AAA <-> GHJ <---------> +oo
-oo <-> AAA <-> GHJ <-> HIJ <-> +oo
-----删除[HIJ]后的跳跃表是:-----
-oo <---------> GHJ <-> +oo
-oo <---------> GHJ <-> +oo
-oo <---------> GHJ <-> +oo
-oo <---------> GHJ <-> +oo
-oo <---------> GHJ <-> +oo
-oo <-> AAA <-> GHJ <-> +oo
-oo <-> AAA <-> GHJ <-> +oo
-oo <-> AAA <-> GHJ <-> +oo
-oo <-> AAA <-> GHJ <-> +oo
-----删除[GHJ]后的跳跃表是:-----
-oo <-> AAA <-> +oo
-oo <-> AAA <-> +oo
-oo <-> AAA <-> +oo
-oo <-> AAA <-> +oo
-----删除[AAA]后的跳跃表是:-----
-oo <-> +oo
-----插入[ABC]之前的跳跃表是:-----
-oo <-> +oo
-----插入[ABC]之后的跳跃表是:-----
-oo <-> ABC <-> +oo
-----插入[DEF]之前的跳跃表是:-----
-oo <-> ABC <-> +oo
-----插入[DEF]之后的跳跃表是:-----
-oo <---------> DEF <-> +oo
-oo <---------> DEF <-> +oo
-oo <---------> DEF <-> +oo
-oo <---------> DEF <-> +oo
-oo <-> ABC <-> DEF <-> +oo
-----插入[KLM]之前的跳跃表是:-----
-oo <---------> DEF <-> +oo
-oo <---------> DEF <-> +oo
-oo <---------> DEF <-> +oo
-oo <---------> DEF <-> +oo
-oo <-> ABC <-> DEF <-> +oo
-----插入[KLM]之后的跳跃表是:-----
-oo <---------> DEF <---------> +oo
-oo <---------> DEF <---------> +oo
-oo <---------> DEF <---------> +oo
-oo <---------> DEF <---------> +oo
-oo <-> ABC <-> DEF <-> KLM <-> +oo
-----插入[HIJ]之前的跳跃表是:-----
-oo <---------> DEF <---------> +oo
-oo <---------> DEF <---------> +oo
-oo <---------> DEF <---------> +oo
-oo <---------> DEF <---------> +oo
-oo <-> ABC <-> DEF <-> KLM <-> +oo
-----插入[HIJ]之后的跳跃表是:-----
-oo <---------> DEF <-----------------> +oo
-oo <---------> DEF <-----------------> +oo
-oo <---------> DEF <-----------------> +oo
-oo <---------> DEF <-> HIJ <---------> +oo
-oo <-> ABC <-> DEF <-> HIJ <-> KLM <-> +oo
-----插入[GHJ]之前的跳跃表是:-----
-oo <---------> DEF <-----------------> +oo
-oo <---------> DEF <-----------------> +oo
-oo <---------> DEF <-----------------> +oo
-oo <---------> DEF <-> HIJ <---------> +oo
-oo <-> ABC <-> DEF <-> HIJ <-> KLM <-> +oo
-----插入[GHJ]之后的跳跃表是:-----
-oo <---------> DEF <-------------------------> +oo
-oo <---------> DEF <-------------------------> +oo
-oo <---------> DEF <-------------------------> +oo
-oo <---------> DEF <---------> HIJ <---------> +oo
-oo <-> ABC <-> DEF <-> GHJ <-> HIJ <-> KLM <-> +oo
-----插入[AAA]之前的跳跃表是:-----
-oo <---------> DEF <-------------------------> +oo
-oo <---------> DEF <-------------------------> +oo
-oo <---------> DEF <-------------------------> +oo
-oo <---------> DEF <---------> HIJ <---------> +oo
-oo <-> ABC <-> DEF <-> GHJ <-> HIJ <-> KLM <-> +oo
-----插入[AAA]之后的跳跃表是:-----
-oo <-----------------> DEF <-------------------------> +oo
-oo <-----------------> DEF <-------------------------> +oo
-oo <-----------------> DEF <-------------------------> +oo
-oo <-----------------> DEF <---------> HIJ <---------> +oo
-oo <-> AAA <-> ABC <-> DEF <-> GHJ <-> HIJ <-> KLM <-> +oo
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值