SkipList数据结构学习

来源:互联网 发布:鳄鱼有耳朵吗 知乎 编辑:程序博客网 时间:2024/05/23 19:19

1.跳表是什么:

跳表是平衡树的一种替代的数据结构,但是和红黑树不相同的是,跳表对于树的平衡的实现是基于一种随机化的算法的,这样也就是说跳表的插入和删除的工作是比较简单 的。

2.为什么要使用跳表:

目前经常使用的平衡数据结构有:B树,红黑树,AVL树,Splay Tree, Treep等。想象一下,给你一张草稿纸,一只笔,一个编辑器,你能立即实现一颗红黑树,或者AVL树出来吗? 很难吧,这需要时间,要考虑很多细节,要参考一堆算法与数据结构之类的树,还要参考网上的代码,相当麻烦。用跳表吧,跳表是一种随机化的数据结构,目前开源软件 Redis 和 LevelDB 都有用到它,它的效率和红黑树以及 AVL 树不相上下,但跳表的原理相当简单,只要你能熟练操作链表,就能轻松实现一个 SkipList。

3.跳表原理

有序表的搜索

考虑一个有序表:

clip_image001

从该有序表中搜索元素 < 23, 43, 59 > ,需要比较的次数分别为 < 2, 4, 6 >,总共比较的次数为 2 + 4 + 6 = 12 次。有没有优化的算法吗?  链表是有序的,但不能使用二分查找。类似二叉搜索树,我们把一些节点提取出来,作为索引。得到如下结构:
clip_image002
这里我们把 < 14, 34, 50, 72 > 提取出来作为一级索引,这样搜索的时候就可以减少比较次数了。我们还可以再从一级索引提取一些元素出来,作为二级索引,变成如下结构:
clip_image003
这里元素不多,体现不出优势,如果元素足够多,这种索引结构就能体现出优势来了。这基本上就是跳表的核心思想,其实也是一种通过“空间来换取时间”的一个算法,通过在每个节点中增加了向前的指针,从而提升查找的效率。
下面的结构是就是跳表:
其中 -1 表示 INT_MIN, 链表的最小值,1 表示 INT_MAX,链表的最大值。
clip_image005
跳表具有如下性质:
(1) 由很多层结构组成
(2) 每一层都是一个有序的链表
(3) 最底层(Level 1)的链表包含所有元素
(4) 如果一个元素出现在 Level i 的链表中,则它在 Level i 之下的链表也都会出现。
(5) 每个节点包含两个指针,一个指向同一链表中的下一个元素,一个指向下面一层的元素。
跳表的搜索
clip_image007
例子:查找元素 117
(1) 比较 21, 比 21 大,往后面找
(2) 比较 37,   比 37大,比链表最大值小,从 37 的下面一层开始找
(3) 比较 71,  比 71 大,比链表最大值小,从 71 的下面一层开始找
(4) 比较 85, 比 85 大,从后面找
(5) 比较 117, 等于 117, 找到了节点。

4.跳表简单实现:

代码如下:


节点数据结构SkipNode

public class SkipNode {
    private int data;
    private SkipNode next[] = null;


    public SkipNode(int data, int level) {
        this.data = data;
        this.next = new SkipNode[level];
    }
    public int getData() {
        return data;
    }
    public SkipNode[] getNext() {
        return next;
    }
}




public class Main {

    public static void main(String[] args) {
        SkipList list = new SkipList();
        int size = 100000;
        long start = System.currentTimeMillis();
        for (int index = 0; index < size; ++index) {
            list.insert(index);
        }
        long end = System.currentTimeMillis();
        System.out.println((end - start));
    }

}

/**
 * Created by yangzhuo02 on 2015/5/26.
 */


import java.util.Random;


public class SkipList {
//设定对打level为32
    private static int MAXLEVEL = 32;
    private int level = 0;
    private SkipNode head;


    public SkipList() {
        this.level = level;
        this.head = new SkipNode(0, MAXLEVEL + 1);
    }


    /**
     *节点插入的时候会随机分配一个level
     *
     **/
    private int randomLevel() {
        Random random = new Random();
        int level = Math.abs(random.nextInt() % MAXLEVEL);
        return  level > this.level ? (this.level + 1) : level;
    }
/**
     *插入操作
     *
     **/
    public void insert(int value) {


        SkipNode tmpHead = this.head;
        //频繁的生成大空间数组,是非常消耗时间的,尤其在性能比较差的机器上
        SkipNode[] updateNode = new SkipNode[MAXLEVEL - 1];
        int index = level;
        for (; index > 0; --index) {
            while (tmpHead != null && tmpHead.getData() < value) {
            updateNode[index] = tmpHead.getNext()[index];
            tmpHead = tmpHead.getNext()[index];
            }
        }


        boolean exists = (tmpHead != null && tmpHead.getData() == value);
        if (exists) {
            return;
        }


        int level = this.randomLevel();


        SkipNode insertNode = new SkipNode(value, level + 1);
        for (int tmpIndex = level; tmpIndex >= 0; --tmpIndex) {
       
        if (updateNode[tmpIndex] == null) {
        updateNode[tmpIndex] = this.head;
        }
       
        SkipNode next = updateNode[tmpIndex].getNext()[tmpIndex];
        insertNode.getNext()[tmpIndex] = next;
            updateNode[tmpIndex].getNext()[tmpIndex] = insertNode;
        }
        this.level = Math.max(level, this.level);
    }


    /*
    这里没有做同步处理
     */
    public boolean find(int value) {
        SkipNode tmpHead = this.head;
        for (int index = level; index > 0; --index) {
            while (tmpHead.getNext()[index] != null && tmpHead.getNext()[index].getData() < value) {
                tmpHead = tmpHead.getNext()[index];
            }
        }
        return tmpHead != null && tmpHead.getData() == value;
    }
/**
     *删除操作,先找到对应的节点,依次删除
     *
     **/
    public void delete(int value) {


        SkipNode tmpHead = this.head;
        //使用这个是应为,只有这样才能获取需要删除节点的前驱和后继节点
        //最后如果该层的节点都删除了,还需要修改level的值
        SkipNode[] updateNode = new SkipNode[level + 1];
        int index = level;
        for (; index > 0; --index) {
            while (tmpHead.getNext()[index] != null && tmpHead.getNext()[index].getData() < value) {
                tmpHead = tmpHead.getNext()[index];
                updateNode[index] = tmpHead;
            }
        }


        if (tmpHead.getData() == value) {
            for (int tmpIndex = level; tmpIndex >= 0; --tmpIndex) {
                SkipNode tmpNode = updateNode[index].getNext()[tmpIndex];
                updateNode[index] = tmpNode.getNext()[tmpIndex];


                if (updateNode[index] != null) {
                    --level;
                }
            }
        }
    }


}


0 0
原创粉丝点击