【算法笔记】有序表——跳表

目录

《【算法笔记】有序表------AVL树》
《【算法笔记】有序表------SB树》
《【算法笔记】有序表------跳表》
《【算法笔记】有序表------相关题目》


1、算法概述

  • 跳表:跳表(Skip List)是一种非常巧妙的数据结构,它通过给有序链表增加多级索引的方式,实现了近乎平衡二叉查找树的查询效率。

  • 跳表的核心思想:

    • 想象一个有序链表,查找一个元素只能从头到尾顺序扫描,时间复杂度是 O(n)。
    • 跳表的核心思想非常直观:通过建立多级"索引"来跳过大量不必要的节点,从而加速查找过程。
    • 跳表的本质是用空间换时间的方法,用多层级的冗余来跳过不必要的节点,从而加速查找过程。
    • 1、一级索引:我们从底层的数据链表开始,每隔一个(或两个)节点就提取一个节点,在上面组成一层稀疏的索引链表。
    • 2、多级索引:然后可以再为这层索引建立更稀疏的上一级索引,如此往复。
    • 3、查找过程:查找时,从最高层索引开始,像坐地铁一样,先坐快线(高层索引)快速接近目的地,再换乘普线(低层索引)或步行(底层链表)到达精确位置。
    • 通过这种"分层跳跃"的策略,搜索的时间复杂度可以降低到 O(log n)。
    • 在跳表的结构中,越是底层的,节点数据越完善,最底层包含了所有的节点。
  • 跳表的查询操作:

    • 查找是跳表所有操作的基础。
    • 过程:从跳表的当前最高层 level的头节点开始。
    • 1、在当前层向后遍历,如果下一个节点的值小于目标值,则继续向后。
    • 2、如果下一个节点的值大于等于目标值,则下降到下一层,继续上述过程。
    • 3、直到下降至底层(第0层),此时如果下一个节点的值等于目标值,则查找成功;否则失败。
    • 示例:假设要查找元素 36,路径可能是:
    • 顶层头节点 -> L2: 20 -> (下降至L1) -> L1: 30 -> L1: (下降至L0) -> L0: 36 。
  • 跳表的插入操作:

    • 插入操作的关键在于如何决定新节点的"高度"(层数),这通过一个随机化算法来完成。
    • 1、确定插入位置:首先执行与查找类似的逻辑,找到新节点在底层链表中的位置,
    • 并在搜索过程中记录下每一层中最后一个小于新节点值的节点(通常保存在一个 update数组中)。
    • 2、随机生成层数:为新节点随机生成一个层数 level。
    • 常用的是"抛硬币"法,即从第1层开始,每次有概率 p(如50%)晋升到下一层,直到晋升失败或达到最大层数限制。
    • 3、调整指针:创建新节点,然后对于第 0层到第 level层,将新节点的每层指针指向 update[i]的后继节点,
    • 再将 update[i]在该层的指针指向新节点。如果新节点的层数超过了当前跳表的最大层数,则需要更新跳表的最大层数。
  • 跳表的删除操作:

  • 删除是插入的逆过程。

  • 1、查找前驱节点:类似插入,先查找待删除节点,并在过程中记录每一层中待删除节点的前驱节点(存入 update数组)。

  • 2、调整指针:找到待删除节点后,对于其出现的每一层,将对应前驱节点(update[i])的指针指向待删除节点的下一个节点。

  • 3、更新跳表层数:删除后,如果最高层因删除而变为空链,则需要降低跳表的层数。

  • 跳表的性能指标:

    • 1、时间复杂度:由于索引的随机分布是均匀的,跳表操作的平均时间复杂度非常高效。最坏情况(虽然概率极低)下会退化为链表。
    • 所以查找、插入、删除的平均时间复杂度为O(logn),最坏时间复杂度为O(n)
    • 2、空间复杂度:索引节点需要额外空间,但所有索引节点的总和约为 n/(1-p)。当 p=1/2时,总和约为 2n,
    • 因此空间复杂度是 O(n),是典型的用空间换时间。
  • 跳表的实际应用

    • 正是因为其高效的性能和相对简单的实现,跳表在一些著名的开源项目中得到了应用:
    • 1、Redis:使用跳表来实现其有序集合(Sorted Set)数据类型。
    • 2、LevelDB / RocksDB:使用跳表作为内存中的键值存储(MemTable)。
  • 总结

    • 跳表是一种优雅而强大的数据结构。它通过多级索引和随机化技术,在有序链表的基础上实现了对数时间复杂度的查找、插入和删除操作。
    • 虽然它通过牺牲一定的空间来换取时间,但其原理直观、实现相对简单、并发性能好的特点,使其在实际应用中,尤其是在数据库和缓存系统中,占据了重要的一席之地。

2、利用跳表实现的自定义map

java 复制代码
    /**
     * 利用跳表实现的自定义map
     */
    public static class SkipListMap<K extends Comparable<K>, V> {
        // 平衡概率, 在插入节点时,随机生成的数小于这个值,继续升级层数,大于等于这个值,停止升级层数
        private static final double PROBABILITY = 0.5;
        // 头结点
        private final Node<K, V> head;
        private int size;
        // 最大的层数
        private int maxLevel;

        public SkipListMap() {
            this.head = new Node<>(null, null);
            // 初始化时加入一个为null的节点,代表第0层
            head.nextNodes.add(null);
            size = 0;
            maxLevel = 0;
        }

        public boolean containsKey(K key) {
            if (key == null) {
                return false;
            }
            // 先找到整棵树小于key最右侧的节点
            Node<K, V> less = mostRightLessNodeInTree(key);
            // 判断下一个节点是不是和key相等
            Node<K, V> next = less.nextNodes.get(0);
            return next != null && next.isKeyEqual(key);
        }

        public void put(K key, V value) {
            if (key == null) {
                return;
            }
            // 先判断存不存在,找到整棵树小于key的最右侧节点,然后看下一个是不是和key相等
            Node<K, V> less = mostRightLessNodeInTree(key);
            Node<K, V> find = less != null ? less.nextNodes.get(0) : null;
            if (find != null && find.isKeyEqual(key)) {
                // 已经存在,更新即可
                find.value = value;
                return;
            }
            // 先随机获得增加节点的level
            int newNodeLevel = 0;
            while (Math.random() < PROBABILITY) {
                newNodeLevel++;
            }
            // 如果新的level大于整棵树的最大level,最大level用新的,并补齐层数
            while (newNodeLevel > this.maxLevel) {
                head.nextNodes.add(null);
                this.maxLevel++;
            }
            // 创建新的节点,并在新的节点中补齐层数
            Node<K, V> newNode = new Node<>(key, value);
            for (int i = 0; i <= newNodeLevel; i++) {
                newNode.nextNodes.add(null);
            }
            // 从需要添加层的最高层往下加入节点
            // 虽然也能从newNodeLevel开始,但是从最高开始效率更高,因为会利用到跳表的高效查询能力
            int level = this.maxLevel;
            Node<K, V> pre = this.head;
            while (level >= 0) {
                // 现在当前层找到最右侧的小于key的节点
                pre = mostRightLessNodeInLevel(key, pre, level);
                if (level <= newNodeLevel) {
                    newNode.nextNodes.set(level, pre.nextNodes.get(level));
                    pre.nextNodes.set(level, newNode);
                }
                level--;
            }
            size++;

        }

        public V get(K key) {
            if (key == null) {
                return null;
            }
            // 小于key的最右侧节点
            Node<K, V> node = mostRightLessNodeInTree(key);
            // 小于key的最右侧节点的下一个节点
            node = node.nextNodes.get(0);
            return node != null && node.isKeyEqual(key) ? node.value : null;
        }

        public void remove(K key) {
            if (!containsKey(key)) {
                return;
            }
            // 从上往下依次删除节点
            int level = this.maxLevel;
            Node<K, V> pre = head;
            Node<K, V> next;
            while (level >= 0) {
                // 找到当前层小于key最右侧的节点
                pre = mostRightLessNodeInLevel(key, pre, level);
                next = pre.nextNodes.get(level);
                // pre的下一个next如果和key相同,就是要删除的
                if (next != null && next.isKeyEqual(key)) {
                    // pre设置成下一个的下一个,就是删除当前的next
                    pre.nextNodes.set(level, next.nextNodes.get(level));
                }
                // 如果当前层没有节点了,就删除这一层,并整体降低高度
                // 在level层只有一个节点了,就是默认节点head
                if (level != 0 && pre == head && pre.nextNodes.get(level) == null) {
                    head.nextNodes.remove(level);
                    this.maxLevel--;
                }
                level--;
            }
            size--;
        }

        public K firstKey() {
            return head.nextNodes.get(0) != null ? head.nextNodes.get(0).key : null;
        }

        public K lastKey() {
            int level = maxLevel;
            Node<K, V> cur = head;
            // 从最大层一直到底,依次往下跳到第0层的最后一个
            while (level >= 0) {
                Node<K, V> next = cur.nextNodes.get(level);
                while (next != null) {
                    cur = next;
                    next = cur.nextNodes.get(level);
                }
                level--;
            }
            return cur.key;
        }

        public K ceilingKey(K key) {
            if (key == null) {
                return null;
            }
            Node<K, V> less = mostRightLessNodeInTree(key);
            Node<K, V> next = less.nextNodes.get(0);
            return next != null ? next.key : null;
        }

        public K floorKey(K key) {
            if (key == null) {
                return null;
            }
            Node<K, V> less = mostRightLessNodeInTree(key);
            Node<K, V> next = less.nextNodes.get(0);
            return next != null && next.isKeyEqual(key) ? next.key : less.key;
        }

        public int size() {
            return size;
        }

        /**
         * 在当前cur的level层找到小于key最后一个节点
         * 现在来到的节点是cur,来到了cur的level层,在level层上,找到<key最后一个节点并返回
         */
        private Node<K, V> mostRightLessNodeInLevel(K key, Node<K, V> cur, int level) {
            Node<K, V> next = cur.nextNodes.get(level);
            while (next != null && next.isKeyLess(key)) {
                cur = next;
                next = cur.nextNodes.get(level);
            }
            return cur;
        }

        /**
         * 查找真个树最右侧小于key的节点
         * 从最高层开始,一路找下去,一直找到第0层小于key的最右的节点
         */
        private Node<K, V> mostRightLessNodeInTree(K key) {
            if (key == null) {
                return null;
            }
            // 从最高层往下找
            int level = this.maxLevel;
            Node<K, V> cur = this.head;
            while (level >= 0) {
                // 每一层找到小于key最右侧的节点
                // 上一层的最右侧的cur,到下一层直接从cur开始,达到的效果是跳过前面到cur的节点,下一层从cur开始查找
                // 这种结构里面嵌套链表巧妙的设计,是跳表提升效率的地方
                cur = mostRightLessNodeInLevel(key, cur, level--);
            }
            return cur;
        }


        /**
         * 跳表的节点
         */
        static class Node<K extends Comparable<K>, V> {
            private final K key;
            private V value;
            // 下一级的节点
            private final ArrayList<Node<K, V>> nextNodes;

            public Node(K key, V value) {
                this.key = key;
                this.value = value;
                this.nextNodes = new ArrayList<>();
            }

            /**
             * 当前节点的key是否比otherKey小,true,不是false
             */
            public boolean isKeyLess(K otherKey) {
                return otherKey != null && (key == null || key.compareTo(otherKey) < 0);
            }

            /**
             * 当前节点的key是否和otherKey相等
             */
            public boolean isKeyEqual(K otherKey) {
                return (key == null && otherKey == null)
                        || (key != null && otherKey != null && key.compareTo(otherKey) == 0);
            }
        }
    }

整体代码和测试:

java 复制代码
import java.util.ArrayList;
import java.util.Objects;
import java.util.TreeMap;

/**
 * 跳表:跳表(Skip List)是一种非常巧妙的数据结构,它通过给有序链表增加多级索引的方式,实现了近乎平衡二叉查找树的查询效率。
 * <br>
 * 跳表的核心思想:
 * 想象一个有序链表,查找一个元素只能从头到尾顺序扫描,时间复杂度是 O(n)。
 * 跳表的核心思想非常直观:通过建立多级"索引"来跳过大量不必要的节点,从而加速查找过程。
 * 跳表的本质是用空间换时间的方法,用多层级的冗余来跳过不必要的节点,从而加速查找过程。
 * 1、一级索引:我们从底层的数据链表开始,每隔一个(或两个)节点就提取一个节点,在上面组成一层稀疏的索引链表。
 * 2、多级索引:然后可以再为这层索引建立更稀疏的上一级索引,如此往复。
 * 3、查找过程:查找时,从最高层索引开始,像坐地铁一样,先坐快线(高层索引)快速接近目的地,再换乘普线(低层索引)或步行(底层链表)到达精确位置。
 * 通过这种"分层跳跃"的策略,搜索的时间复杂度可以降低到 O(log n)。
 * 在跳表的结构中,越是底层的,节点数据越完善,最底层包含了所有的节点。
 * <br>
 * 跳表的查询操作:
 * 查找是跳表所有操作的基础。
 * 过程:从跳表的当前最高层 level的头节点开始。
 * 1、在当前层向后遍历,如果下一个节点的值小于目标值,则继续向后。
 * 2、如果下一个节点的值大于等于目标值,则下降到下一层,继续上述过程。
 * 3、直到下降至底层(第0层),此时如果下一个节点的值等于目标值,则查找成功;否则失败。
 * 示例:假设要查找元素 36,路径可能是:
 * 顶层头节点 -> L2: 20 -> (下降至L1) -> L1: 30 -> L1: (下降至L0) -> L0: 36 。
 * <br>
 * 跳表的插入操作:
 * 插入操作的关键在于如何决定新节点的"高度"(层数),这通过一个随机化算法来完成。
 * 1、确定插入位置:首先执行与查找类似的逻辑,找到新节点在底层链表中的位置,
 * 并在搜索过程中记录下每一层中最后一个小于新节点值的节点(通常保存在一个 update数组中)。
 * 2、随机生成层数:为新节点随机生成一个层数 level。
 * 常用的是"抛硬币"法,即从第1层开始,每次有概率 p(如50%)晋升到下一层,直到晋升失败或达到最大层数限制。
 * 3、调整指针:创建新节点,然后对于第 0层到第 level层,将新节点的每层指针指向 update[i]的后继节点,
 * 再将 update[i]在该层的指针指向新节点。如果新节点的层数超过了当前跳表的最大层数,则需要更新跳表的最大层数。
 * <br>
 * 跳表的删除操作:
 * 删除是插入的逆过程。
 * 1、查找前驱节点:类似插入,先查找待删除节点,并在过程中记录每一层中待删除节点的前驱节点(存入 update数组)。
 * 2、调整指针:找到待删除节点后,对于其出现的每一层,将对应前驱节点(update[i])的指针指向待删除节点的下一个节点。
 * 3、更新跳表层数:删除后,如果最高层因删除而变为空链,则需要降低跳表的层数。
 * <br>
 * 跳表的性能指标:
 * 1、时间复杂度:由于索引的随机分布是均匀的,跳表操作的平均时间复杂度非常高效。最坏情况(虽然概率极低)下会退化为链表。
 * 所以查找、插入、删除的平均时间复杂度为O(logn),最坏时间复杂度为O(n)
 * 2、空间复杂度:索引节点需要额外空间,但所有索引节点的总和约为 n/(1-p)。当 p=1/2时,总和约为 2n,
 * 因此空间复杂度是 O(n),是典型的用空间换时间。
 * <br>
 * 跳表的实际应用
 * 正是因为其高效的性能和相对简单的实现,跳表在一些著名的开源项目中得到了应用:
 * 1、Redis:使用跳表来实现其有序集合(Sorted Set)数据类型。
 * 2、LevelDB / RocksDB:使用跳表作为内存中的键值存储(MemTable)。
 * <br>
 * 总结
 * 跳表是一种优雅而强大的数据结构。它通过多级索引和随机化技术,在有序链表的基础上实现了对数时间复杂度的查找、插入和删除操作。
 * 虽然它通过牺牲一定的空间来换取时间,但其原理直观、实现相对简单、并发性能好的特点,使其在实际应用中,尤其是在数据库和缓存系统中,占据了重要的一席之地。
 */
public class SkipList {

    /**
     * 利用跳表实现的自定义map
     */
    public static class SkipListMap<K extends Comparable<K>, V> {
        // 平衡概率, 在插入节点时,随机生成的数小于这个值,继续升级层数,大于等于这个值,停止升级层数
        private static final double PROBABILITY = 0.5;
        // 头结点
        private final Node<K, V> head;
        private int size;
        // 最大的层数
        private int maxLevel;

        public SkipListMap() {
            this.head = new Node<>(null, null);
            // 初始化时加入一个为null的节点,代表第0层
            head.nextNodes.add(null);
            size = 0;
            maxLevel = 0;
        }

        public boolean containsKey(K key) {
            if (key == null) {
                return false;
            }
            // 先找到整棵树小于key最右侧的节点
            Node<K, V> less = mostRightLessNodeInTree(key);
            // 判断下一个节点是不是和key相等
            Node<K, V> next = less.nextNodes.get(0);
            return next != null && next.isKeyEqual(key);
        }

        public void put(K key, V value) {
            if (key == null) {
                return;
            }
            // 先判断存不存在,找到整棵树小于key的最右侧节点,然后看下一个是不是和key相等
            Node<K, V> less = mostRightLessNodeInTree(key);
            Node<K, V> find = less != null ? less.nextNodes.get(0) : null;
            if (find != null && find.isKeyEqual(key)) {
                // 已经存在,更新即可
                find.value = value;
                return;
            }
            // 先随机获得增加节点的level
            int newNodeLevel = 0;
            while (Math.random() < PROBABILITY) {
                newNodeLevel++;
            }
            // 如果新的level大于整棵树的最大level,最大level用新的,并补齐层数
            while (newNodeLevel > this.maxLevel) {
                head.nextNodes.add(null);
                this.maxLevel++;
            }
            // 创建新的节点,并在新的节点中补齐层数
            Node<K, V> newNode = new Node<>(key, value);
            for (int i = 0; i <= newNodeLevel; i++) {
                newNode.nextNodes.add(null);
            }
            // 从需要添加层的最高层往下加入节点
            // 虽然也能从newNodeLevel开始,但是从最高开始效率更高,因为会利用到跳表的高效查询能力
            int level = this.maxLevel;
            Node<K, V> pre = this.head;
            while (level >= 0) {
                // 现在当前层找到最右侧的小于key的节点
                pre = mostRightLessNodeInLevel(key, pre, level);
                if (level <= newNodeLevel) {
                    newNode.nextNodes.set(level, pre.nextNodes.get(level));
                    pre.nextNodes.set(level, newNode);
                }
                level--;
            }
            size++;

        }

        public V get(K key) {
            if (key == null) {
                return null;
            }
            // 小于key的最右侧节点
            Node<K, V> node = mostRightLessNodeInTree(key);
            // 小于key的最右侧节点的下一个节点
            node = node.nextNodes.get(0);
            return node != null && node.isKeyEqual(key) ? node.value : null;
        }

        public void remove(K key) {
            if (!containsKey(key)) {
                return;
            }
            // 从上往下依次删除节点
            int level = this.maxLevel;
            Node<K, V> pre = head;
            Node<K, V> next;
            while (level >= 0) {
                // 找到当前层小于key最右侧的节点
                pre = mostRightLessNodeInLevel(key, pre, level);
                next = pre.nextNodes.get(level);
                // pre的下一个next如果和key相同,就是要删除的
                if (next != null && next.isKeyEqual(key)) {
                    // pre设置成下一个的下一个,就是删除当前的next
                    pre.nextNodes.set(level, next.nextNodes.get(level));
                }
                // 如果当前层没有节点了,就删除这一层,并整体降低高度
                // 在level层只有一个节点了,就是默认节点head
                if (level != 0 && pre == head && pre.nextNodes.get(level) == null) {
                    head.nextNodes.remove(level);
                    this.maxLevel--;
                }
                level--;
            }
            size--;
        }

        public K firstKey() {
            return head.nextNodes.get(0) != null ? head.nextNodes.get(0).key : null;
        }

        public K lastKey() {
            int level = maxLevel;
            Node<K, V> cur = head;
            // 从最大层一直到底,依次往下跳到第0层的最后一个
            while (level >= 0) {
                Node<K, V> next = cur.nextNodes.get(level);
                while (next != null) {
                    cur = next;
                    next = cur.nextNodes.get(level);
                }
                level--;
            }
            return cur.key;
        }

        public K ceilingKey(K key) {
            if (key == null) {
                return null;
            }
            Node<K, V> less = mostRightLessNodeInTree(key);
            Node<K, V> next = less.nextNodes.get(0);
            return next != null ? next.key : null;
        }

        public K floorKey(K key) {
            if (key == null) {
                return null;
            }
            Node<K, V> less = mostRightLessNodeInTree(key);
            Node<K, V> next = less.nextNodes.get(0);
            return next != null && next.isKeyEqual(key) ? next.key : less.key;
        }

        public int size() {
            return size;
        }

        /**
         * 在当前cur的level层找到小于key最后一个节点
         * 现在来到的节点是cur,来到了cur的level层,在level层上,找到<key最后一个节点并返回
         */
        private Node<K, V> mostRightLessNodeInLevel(K key, Node<K, V> cur, int level) {
            Node<K, V> next = cur.nextNodes.get(level);
            while (next != null && next.isKeyLess(key)) {
                cur = next;
                next = cur.nextNodes.get(level);
            }
            return cur;
        }

        /**
         * 查找真个树最右侧小于key的节点
         * 从最高层开始,一路找下去,一直找到第0层小于key的最右的节点
         */
        private Node<K, V> mostRightLessNodeInTree(K key) {
            if (key == null) {
                return null;
            }
            // 从最高层往下找
            int level = this.maxLevel;
            Node<K, V> cur = this.head;
            while (level >= 0) {
                // 每一层找到小于key最右侧的节点
                // 上一层的最右侧的cur,到下一层直接从cur开始,达到的效果是跳过前面到cur的节点,下一层从cur开始查找
                // 这种结构里面嵌套链表巧妙的设计,是跳表提升效率的地方
                cur = mostRightLessNodeInLevel(key, cur, level--);
            }
            return cur;
        }


        /**
         * 跳表的节点
         */
        static class Node<K extends Comparable<K>, V> {
            private final K key;
            private V value;
            // 下一级的节点
            private final ArrayList<Node<K, V>> nextNodes;

            public Node(K key, V value) {
                this.key = key;
                this.value = value;
                this.nextNodes = new ArrayList<>();
            }

            /**
             * 当前节点的key是否比otherKey小,true,不是false
             */
            public boolean isKeyLess(K otherKey) {
                return otherKey != null && (key == null || key.compareTo(otherKey) < 0);
            }

            /**
             * 当前节点的key是否和otherKey相等
             */
            public boolean isKeyEqual(K otherKey) {
                return (key == null && otherKey == null)
                        || (key != null && otherKey != null && key.compareTo(otherKey) == 0);
            }
        }
    }

    public static void main(String[] args) {
        System.out.println("======");
        functionTest("SkipListMap");
        System.out.println("======");
        performanceTest("SkipListMap");
    }


    public static void functionTest(String prefix) {
        System.out.println(prefix + " 功能测试开始");
        TreeMap<Integer, Integer> treeMap = new TreeMap<>();
        SkipListMap<Integer, Integer> target = new SkipListMap<>();
        int maxK = 500;
        int maxV = 50000;
        int testTime = 1000000;
        boolean success = true;
        for (int i = 0; i < testTime; i++) {
            int addK = (int) (Math.random() * maxK);
            int addV = (int) (Math.random() * maxV);
            treeMap.put(addK, addV);
            target.put(addK, addV);

            int removeK = (int) (Math.random() * maxK);
            treeMap.remove(removeK);
            target.remove(removeK);

            int querryK = (int) (Math.random() * maxK);
            boolean treeMapAns = treeMap.containsKey(querryK);
            boolean ans = target.containsKey(querryK);
            if (treeMapAns != ans) {
                System.out.println("containsKey 错误");
                System.out.printf("key:%d, treeMapAns:%b, ans:%b\n", querryK, treeMapAns, ans);
                success = false;
                break;
            }

            if (treeMap.containsKey(querryK)) {
                int v1 = treeMap.get(querryK);
                int v2 = target.get(querryK);
                if (v1 != v2) {
                    System.out.println("get 错误");
                    System.out.printf("key:%d, treeMapAns:%d, ans:%d\n", querryK, v1, v2);
                    success = false;
                    break;
                }
                Integer f1 = treeMap.floorKey(querryK);
                Integer f2 = target.floorKey(querryK);
                if (!Objects.equals(f1, f2)) {
                    System.out.println("floorKey 错误");
                    System.out.printf("key:%d, treeMapAns:%d, ans:%d\n", querryK, f1, f2);
                    success = false;
                    break;
                }
                f1 = treeMap.ceilingKey(querryK);
                f2 = target.ceilingKey(querryK);
                if (!Objects.equals(f1, f2)) {
                    System.out.println("ceilingKey 错误");
                    System.out.printf("key:%d, treeMapAns:%d, ans:%d\n", querryK, f1, f2);
                    success = false;
                    break;
                }
            }

            Integer f1 = treeMap.firstKey();
            Integer f2 = target.firstKey();
            if (!Objects.equals(f1, f2)) {
                System.out.println("firstKey 错误");
                System.out.printf("key:%d, treeMapAns:%d, ans:%d\n", querryK, f1, f2);
                success = false;
                break;
            }

            f1 = treeMap.lastKey();
            f2 = target.lastKey();
            if (!Objects.equals(f1, f2)) {
                System.out.println("lastKey 错误");
                System.out.printf("key:%d, treeMapAns:%d, ans:%d\n", querryK, f1, f2);
                success = false;
                break;
            }
            int treeMapSize = treeMap.size();
            int ansSize = target.size();
            if (treeMapSize != ansSize) {
                System.out.println("size 错误");
                System.out.printf("key:%d, treeMapAns:%d, ans:%d\n", querryK, treeMapSize, ansSize);
                success = false;
                break;
            }
        }
        if (!success) {
            System.out.println("测试失败");
            return;
        }
        System.out.println(prefix + " 功能测试结束");
    }

    public static void performanceTest(String prefix) {
        System.out.println(prefix + " 性能测试开始");
        TreeMap<Integer, Integer> treeMap = new TreeMap<>();
        SkipListMap<Integer, Integer> target = new SkipListMap<>();
        long start;
        long end;
        int max = 1000000;
        System.out.println("顺序递增加入测试,数据规模 : " + max);
        start = System.currentTimeMillis();
        for (int i = 0; i < max; i++) {
            treeMap.put(i, i);
        }
        end = System.currentTimeMillis();
        System.out.println("treeMap 运行时间 : " + (end - start) + "ms");

        start = System.currentTimeMillis();
        for (int i = 0; i < max; i++) {
            target.put(i, i);
        }
        end = System.currentTimeMillis();
        System.out.println(prefix + " 运行时间 : " + (end - start) + "ms");


        System.out.println("顺序递增删除测试,数据规模 : " + max);
        start = System.currentTimeMillis();
        for (int i = 0; i < max; i++) {
            treeMap.remove(i);
        }
        end = System.currentTimeMillis();
        System.out.println("treeMap 运行时间 : " + (end - start) + "ms");

        start = System.currentTimeMillis();
        for (int i = 0; i < max; i++) {
            target.remove(i);
        }
        end = System.currentTimeMillis();
        System.out.println(prefix + " 运行时间 : " + (end - start) + "ms");

        System.out.println("顺序递减加入测试,数据规模 : " + max);
        start = System.currentTimeMillis();
        for (int i = max; i >= 0; i--) {
            treeMap.put(i, i);
        }
        end = System.currentTimeMillis();
        System.out.println("treeMap 运行时间 : " + (end - start) + "ms");

        start = System.currentTimeMillis();
        for (int i = max; i >= 0; i--) {
            target.put(i, i);
        }
        end = System.currentTimeMillis();
        System.out.println(prefix + " 运行时间 : " + (end - start) + "ms");

        System.out.println("顺序递减删除测试,数据规模 : " + max);
        start = System.currentTimeMillis();
        for (int i = max; i >= 0; i--) {
            treeMap.remove(i);
        }
        end = System.currentTimeMillis();
        System.out.println("treeMap 运行时间 : " + (end - start) + "ms");

        start = System.currentTimeMillis();
        for (int i = max; i >= 0; i--) {
            target.remove(i);
        }
        end = System.currentTimeMillis();
        System.out.println(prefix + " 运行时间 : " + (end - start) + "ms");


        System.out.println("随机加入测试,数据规模 : " + max);
        start = System.currentTimeMillis();
        for (int i = 0; i < max; i++) {
            treeMap.put((int) (Math.random() * i), i);
        }
        end = System.currentTimeMillis();
        System.out.println("treeMap 运行时间 : " + (end - start) + "ms");

        start = System.currentTimeMillis();
        for (int i = max; i >= 0; i--) {
            target.put((int) (Math.random() * i), i);
        }
        end = System.currentTimeMillis();
        System.out.println(prefix + " 运行时间 : " + (end - start) + "ms");

        System.out.println("随机删除测试,数据规模 : " + max);
        start = System.currentTimeMillis();
        for (int i = 0; i < max; i++) {
            treeMap.remove((int) (Math.random() * i));
        }
        end = System.currentTimeMillis();
        System.out.println("treeMap 运行时间 : " + (end - start) + "ms");

        start = System.currentTimeMillis();
        for (int i = max; i >= 0; i--) {
            target.remove((int) (Math.random() * i));
        }
        end = System.currentTimeMillis();
        System.out.println(prefix + " 运行时间 : " + (end - start) + "ms");

        System.out.println(prefix + " 性能测试结束");
    }

}

后记

个人学习总结笔记,不能保证非常详细,轻喷

相关推荐
挖矿大亨2 小时前
c++中的函数调用运算符重载
前端·c++·算法
wadesir2 小时前
Rust语言BM算法实现(从零开始掌握Boyer-Moore字符串搜索算法)
算法·rust·.net
硅农深芯2 小时前
深入解析 AECQ100 标准中的 Cpk:保障汽车电子元器件质量的关键指标
人工智能·算法·汽车·芯片·cpk
AI 菌2 小时前
Qwen-Image:复杂文本渲染与精准图像编辑的图像生成基础模型
人工智能·算法·计算机视觉·大模型·千问
Cx330❀2 小时前
《C++ 递归、搜索与回溯》第1题:汉诺塔问题
开发语言·c++·算法·面试·回归算法
BlackWolfSky2 小时前
鸿蒙中级课程笔记1—CodeGenie功能介绍
笔记·华为·鸿蒙
航Hang*2 小时前
Photoshop 图形与图像处理技术——第5章:路径与形状的应用
图像处理·笔记·ui·photoshop
QT 小鲜肉2 小时前
【Linux命令大全】001.文件管理之split命令(实操篇)
linux·运维·服务器·网络·笔记
chinesegf2 小时前
如何在沙盒环境中进行内购测试
笔记·ios