zoukankan      html  css  js  c++  java
  • 7.跳表:各方面都非常优秀的动态数据结构

    跳表基于单链表实现,链表查找、插入、删除绝大部分时间花在遍历链表上,跳表使用索引来优化链表遍历的过程,使得跳表具有非常优秀的查找、插入、删除性能,并且是天然的动态数据结构

    • 查找、插入、删除时间复杂度都是O(logn)

    • 跳表原理的理解

      • 二叉搜索通过计算mid的值,使得每一次要遍历的数据量减半,那么链表可不可以实现类似的功能呢

      • 如果有一个指针指向链表中点,那么在搜索时先与中点比较,根据比较结果决定是从链表头开始遍历还是从中点开始遍历,这样平均每次要遍历的数据量就减少了一半

      • 如果多设置几个索引,甚至设置多级索引,那么遍历链表所花费的时间将大大降低,而额外付出的空间不过是几个指针

      • 跳表示意图

      • 原链表查找4,需要1->2->3->4,而加了索引之后1->3->4;查找5更是只需1->5;在链表很大时跳表的查找效率会显著提高。

    • 跳表实现上的注意点

      • 与红黑树相比跳表的实现难度要简单不少,但还是有几处注意点

      • 节点设置

        • 数据(data):存储该节点数据

        • 索引数组(forward[]):存储该节点各层的索引(每个索引也是一个节点),索引指是指向下一节点的指针

        • 查找结点(值设为value)

      • 实际上跳表实现的核心就是结点的查找

        • 查找时从头节点、最上层索引开始

        • 1.找到该层索引中data小于value的最大节点(这个节点后面的节点值要么等于value要么大于value)

        • 2.若本层已经是第0层索引(也就是到了原链表)则此时的节点就是值小于等于value的最后一个节点,这个节点后面一个就是我们要找的值

        • 3.若本层不是第0层索引,则去下一层,重复1-2-3过程

      • 跳表的随机函数

        • 数据不断的插入过程中,如果索引一直没有更新,考虑极端情况:所有数据都在两个索引之间,跳表退化为了链表

        • 使用随机函数解决上述退化问题:在插入值新值时,不仅将新节点插入原链表,并且插入n层对应的索引

        • n是一个随机值,理论上要使得一级索引的数目为链表长度的n/2,二级索引的数目为n/4……

    跳表Java实现(insert优化)

    /**
     * 跳表是在单链表的基础上加入索引,使查找效率大大提高的一种各方面性能都很优秀的数据结构
     * 跳表存储正整数,且不重复
     * @author hzk
     */
    public class mySkipList {
        private final int MAX_LEVEL = 16;//最大索引层数(0~原链表 15~最高一级索引)
        private int levelCount = 1;//跳表当前索引层数
        public Node head = new Node();//跳表头
    
        /**
         * 查找跳表中值为value的节点
         *
         * @param value 要查找的值
         * @return 找到返回对应节点,否则返回null
         */
        public Node find(int value) {
            Node p = head;
            //找到该层索引中小于value的最大节点
            for (int i = levelCount - 1; i >= 0; i--) {
                while (p.forwards[i] != null && p.forwards[i].data < value) {
                    p = p.forwards[i];
                }
            }
    
            if (p.forwards[0] != null && p.forwards[0].data == value)
                return p.forwards[0];
            else
                return null;
        }
    
        /**
         * 将value插入跳表中
         *
         * @param value 待加入数据
         */
        public void insert(int value) {
            int level = randomLevel();
            if (levelCount < level) levelCount = level;
    
            Node p = head;
            Node newNode = new Node();
            newNode.data = value;
            newNode.maxLevel = level;
    
            Node path[] = new Node[level];//存储查找value时经过各层的索引
            for (int i = level - 1; i >= 0; i--) {
                while (p.forwards[i] != null && p.forwards[i].data < value) {
                    p = p.forwards[i];
                }
                path[i] = p;
            }
    
            //将value插入各层索引中
            for (int i = level - 1; i >= 0; i--) {
                newNode.forwards[i] = path[i].forwards[i];
                path[i].forwards[i] = newNode;
            }
        }
    
        /**
         * insert的优化版本,去掉了path[]
         * @param value 待加入数据
         */
        public void insert_optimized(int value) {
            int level = randomLevel();
            if (levelCount < level) levelCount = level;
    
            Node p = head;
            Node newNode = new Node();
            newNode.data = value;
            newNode.maxLevel = level;
    
            for (int i = level - 1; i >= 0; i--) {
                while (p.forwards[i] != null && p.forwards[i].data < value)
                    p = p.forwards[i];
    
                //这层索引是最后一个则直接插入
                if (p.forwards[i] == null) {
                    p.forwards[i] = newNode;
                }
                //否则插在中间
                else {
                    newNode.forwards[i] = p.forwards[i];
                    p.forwards[i] = newNode;
                }
            }
        }
    
        /**
         * 删除跳表中值为value的节点及索引
         *
         * @param value 待删除结点的值
         */
        public void delete(int value) {
            Node path[] = new Node[levelCount];
            Node p = head;
    
            for (int i = levelCount - 1; i >= 0; i--) {
                while (p.forwards[i] != null && p.forwards[i].data < value)
                    p = p.forwards[i];
                path[i] = p;
            }
    
            //找到
            if (p.forwards[0] != null && p.forwards[0].data == value) {
                //删除节点所有索引
                for (int i = levelCount - 1; i >= 0; i--) {
                    if (p.forwards[i] != null && p.forwards[i].data == value) {
                        p.forwards[i] = p.forwards[i].forwards[i];
                    }
                }
            }
        }
    
        /**
         * 随机生成索引层数,索引层数和生成概率负相关
         * 尽量使一级索引占全部索引的50%,二级索引占25%,三级索引占12.5%……
         * 随机函数能防止链表数据全部集中在某两个索引之间
         *
         * @return
         */
        private int randomLevel() {
            int level = 1;
    
            while (Math.random() < 0.5 && level < MAX_LEVEL)
                level++;
    
            return level;
        }
    
        public void printAll() {
            Node p = head;
            while (p.forwards[0] != null) {
                System.out.print(p.forwards[0].data + " ");
                p = p.forwards[0];
            }
            System.out.println();
        }
    
        public void skipListText() {
            mySkipList list = new mySkipList();
            list.insert(1);
            list.insert(2);
            list.insert(3);
            list.printAll();
            System.out.println(list.find(2));
            list.delete(2);
            list.printAll();
            list.insert_optimized(2);
            list.printAll();
        }
    
        public class Node {
            private int data = -1;//节点数据
            private Node forwards[] = new Node[MAX_LEVEL];//存储节点上层索引
            private int maxLevel = 0;//最大索引层数
    
            @Override
            public String toString() {
                StringBuilder builder = new StringBuilder();
    
                builder.append("{ data: ");
                builder.append(data);
                builder.append("; levels: ");
                builder.append(maxLevel);
                builder.append(" }");
    
                return builder.toString();
            }
        }
    }
    
  • 相关阅读:
    ExtJs2.0学习系列(1)Ext.MessageBox
    PDF加水印
    ExtJs2.0学习系列(2)Ext.Panel
    负载均衡(续)
    位运算设置权限续(转)
    Excel导出问题解决方案(导出时前面的0自动被去掉)
    SELECT INTO 和 INSERT INTO SELECT 两种表复制语句
    位运算设置权限
    WCF开发实战系列一:创建第一个WCF服务
    通过SQL Server的位运算功能巧妙解决多选查询
  • 原文地址:https://www.cnblogs.com/codespoon/p/13246836.html
Copyright © 2011-2022 走看看