美文网首页数据结构和算法分析Java数据结构和算法
常见数据结构的实现(一):跳跃表

常见数据结构的实现(一):跳跃表

作者: 鸿胪少卿 | 来源:发表于2020-02-22 10:36 被阅读0次

    简书的小伙伴们好,这是我在简书写的第一篇文章哈。我写这篇文章的目的主要是和大家分享一些想法,交流学习一下。

    这系列的文章是分析常见数据结构的实现,包括跳跃表、二叉堆、前缀树、红黑树等等。。。数据结构这门课在学习与工作中都非常重要,所以我觉得有必要把自己的想法拿出来和大家分享交流,互相学习。

    下面就开始正题吧!(第一次写文章,如果某些地方语言、思路表达不当或者有误,希望大家包容一下吧哈哈)

    介绍

    链表是一种基本的数据结构,而跳跃表是一种特殊的有序链表。

    • 跳跃表是由多层有序链表组合而成的,最底一层的链表保存了所有的数据,每向上的一层链表依次保存了下一层链表的部分数据。
    • 相邻的两层链表中元素相同的节点之间存在引用关系,一般是上层节点中存在一个指向下层节点的引用
    • 跳跃表的目的在于提高了查询效率,同时也牺牲了一定的存储空间。

    下面的一张图是我自己绘制的跳跃表,每层包含了头节点并且是单向链表:

    skip_list.png

    分析

    按照上面的图,所有节点包含以下内容:存储的元素,指向同一层中下一个节点的引用,指向下一层中对应节点的引用。

    那么我们可以如下构建节点类和跳跃表类:

    //跳跃表的实现
    //元素有序且不重复
    public class SkipList {
    
        private Node head;//顶层头节点    
        private int rate;//相邻两层元素个数的比例    
        private int level;//跳跃表层数    
        private int length;//底层节点个数    
        private int size;//所有层节点个数
        private final boolean order;//true表示正序,false表示逆序    
        private Random random;//随机数    
        private Stack<Node> stack;//保存查询时遍历的节点
        
        //节点类
        private static class Node {
            
            private Comparable comparable;
            private Node right;//同一层的右边节点
            private Node down;//下一层的对应节点
            
            public Node(Comparable comparable) {
                this.comparable = comparable;
                this.right = null;
                this.down = null;
            }
        }
        
        public SkipList(int rate, int level, boolean order) {
            this.rate = rate;
            this.level = level;
            this.length = 0;
            this.size = 0;
            this.order = order;
            this.random = new Random();
            this.stack = new Stack<Node>();
            this.head = new Node(null);//头节点的值默认为null
            Node temp = head;
            for (int i = 1; i < level; i++) {
                temp.down = new Node(null);
                temp = temp.down;
            }
        }
    
    }
    
    • rate表示相邻两层链表的元素数量之比,也就是下一层每添加多少个元素时,就向上一层继续添加一个元素。这个值可以由个人决定
    • order表示有序链表保存元素的顺序,对于整型数据,从前向后,元素从小到大为true,对于字符型数据,从前向后,元素的字典序依次靠后为true,等等
    • random用于添加操作时生成随机数的种子
    • stack表示查询操作时保存查询路径上某些节点的栈,Stack类可以自己实现

    SkipList类构造方法的最后几句代码表示初始化几个头节点,其中head指向最上层的头节点。我的实现中跳跃表保存的元素不重复,如果想要保存重复的元素,只需要在原来的基础上稍作修改即可

    查找元素

    添加、删除操作都依赖于查找操作,所以先介绍查找操作

    查找操作是自顶向下、从左向右的,也就是先在最上一层链表中从左向右查找,然后依次向下层查找,直到最下一层的某个节点结束。具体的操作依赖于保存元素的顺序,假设保存的是整型数据,order为true,那么元素从前向后依次变大:

    • 先在最上一层查找小于所指定元素的最右的节点,将节点入栈,然后转向下一层
    • 在当前层继续向后查找满足上述条件的节点,同样将其入栈,向下一层继续查找,直到查找到最下一层满足条件的节点

    从上面的分析中可以知道,stack中保存的是查找路径中每层最右边的节点,最底层的那个节点除外。

    举个例子,在第一张图中,查找元素6时,查找路径为head、3、3、3、5、5,stack中保存的元素依次为3、3、5。查找元素10时,查找路径为head、3、3、7、7、7、8,stack中保存的元素依次为3、7、7。特殊情况下,头节点也会入栈

    为什么这里设计成这样的查找路径,以及栈中保存查找路径上每层最右的节点?主要是在单链表的情况下,查找元素时可以统一元素存在和不存在两种情况,同时添加、删除元素时方便改变节点之间的引用关系。下面贴出查找操作的代码:

        //查询元素,自顶向下
        //正序时,返回底层【小于】给定值的最大的节点,包含头节点
        //逆序时,返回底层【大于】给定值的最小的节点,包含头节点
        private Node search(Comparable comparable) {
            stack.clear();
            Node temp = head;//从顶层开始
            while (true) {
                while (temp.right != null) {
                    if (order && temp.right.comparable.compareTo(comparable) >= 0)//正序时查找当前层【小于】给定值的最大的节点
                        break;
                    if (!order && temp.right.comparable.compareTo(comparable) <= 0)//逆序时查找当前层【大于】给定值的最小的节点
                        break;
                    temp = temp.right;
                }
                if (temp.down == null)//找到底层的节点
                    break;
                stack.push(temp);//stack保存遍历路径中每一层最右边的节点,除底层外
                temp = temp.down;//转到下一层
            }
            return temp;
        }
    

    如果理解了查找操作的过程,那么上面的代码就很容易看懂了。查找操作返回的是最下一层满足那个条件的节点(有可能是头节点)。

    添加元素

    在我的实现中,跳跃表不保存重复的元素,所以只有当所指定元素不存在时,才执行添加操作。

    添加操作是自底向上的,并且根据指定的rate按照一定的概率向上层添加节点。添加时需要维护同层节点之间的关系,同时也要维护当前节点与下一层对应节点的关系。这里只需要注意的一点是,什么条件下才能向上层继续添加?只有当随机数满足条件并且当前层不是最上一层时,才能继续添加。

        //添加元素
        //若元素已存在,则返回,保证无重复元素
        public void insert(Comparable comparable) {
            Node temp = search(comparable);
            if (temp.right != null && temp.right.comparable.compareTo(comparable) == 0)//元素已存在
                return;
            Node node = new Node(comparable);
            Node other;
            //根据随机数,自底向上添加每层的新节点
            while (true) {
                node.right = temp.right;
                temp.right = node;//当前层添加完毕
                size++;
                if (random.nextInt(rate) != 0 || stack.isEmpty())
                    break;
                //若随机数为0且还未到顶层,则向上层添加元素
                temp = stack.pop();
                other = node;
                node = new Node(comparable);
                node.down = other;
            }
            length++;
            return;
        }
    

    再强调一下,查找操作返回的是最下一层满足条件的节点。注意下,while循环里面向上层添加单个节点的过程实际上分解到了两次循环中,先维护上下层节点的关系,再维护同层节点的关系。

    删除元素

    当所指定元素存在时,删除操作需要删除所有层中的对应元素。如果某一层中不存在指定的元素,那么上面的所有层中肯定也不会存在,因此可以直接跳出循环,操作结束。

        //删除元素
        //若元素不存在,则返回,否则删除所有层中包含的元素
        public void delete(Comparable comparable) {
            Node temp = search(comparable);
            if (temp.right == null || temp.right.comparable.compareTo(comparable) != 0)//元素不存在
                return;
            while (true) {
                if (temp.right == null || temp.right.comparable.compareTo(comparable) != 0) //当前层的元素不存在
                    break;
                //从底层开始,依次删除每层的元素
                temp.right = temp.right.right;
                size--;
                if (stack.isEmpty())//到达顶层
                    break;
                temp = stack.pop();//转到上一层
            }
            length--;
            return;
        }
    

    后记

    从上面的讨论中可以看到,跳跃表的查找、添加、删除操作其实并不难理解,这个数据结构比较简单。当然这篇文章是我的个人理解,欢迎感兴趣的读者一起来交流,提出建议。后面我会介绍其他一些常用数据结构的实现,希望大家继续关注哦~

    下面的链接是我个人的一点总结,包括常见的数据结构、设计模式、基本算法等等。。当然还在继续完善中,如果你们觉得我写的不错的话,那就给这篇文章、github点个赞或者star一下吧,谢谢啦!

    https://github.com/eunji2018/data_structure

    欣赏美可以使人愉悦~~

    相关文章

      网友评论

        本文标题:常见数据结构的实现(一):跳跃表

        本文链接:https://www.haomeiwen.com/subject/nhakqhtx.html