Skip to content

Skip List

概率数据结构

时间复杂度基于产生的索引层数的概率 \(\mathrm{p} : \mathrm{O}(\log_{\frac{1}{p}}\mathrm{(N)})\)

跳表是对有序链表的改进

跳表的定义

interface SkipList<K : Comparable<K>, V> : KVOperation<K, V>, DataStructurePrintable {

    /**
     * 跳表的层数
     */
    fun level(): Int

}

/**
 * 默认最高层数
 */
const val DEFAULT_MAX_LEVEL = 16

/**
 * 默认产生层数的概率
 */
const val DEFAULT_P = 0.5

跳表的实现

跳表实现的核心思路在于对前驱节点的查找和索引的更新

class SkipListImpl<K : Comparable<K>, V>(
    val maxLevel: Int = DEFAULT_MAX_LEVEL,
    val p: Double = DEFAULT_P
) : SkipList<K, V> {

    companion object {
        private const val MIN_LEVEL = 1
    }

    /**
     * 随机
     */
    private val random = Random(System.currentTimeMillis())

    /**
     * 哨兵节点
     */
    private val head = SkipListNode<K, V>(null, null, this.maxLevel, this.p, true)

    /**
     * 跳表的层级,取决于跳表节点创建的随机出的层级
     */
    private var skipListLevel = MIN_LEVEL

    /**
     * 跳表的节点个数
     */
    private var nodeCount = 0

    /**
     * 跳表节点个数
     */
    override fun size(): Int = nodeCount

    /**
     * 当前跳表存储的层级
     */
    override fun level(): Int = this.skipListLevel

    override fun contains(key: K): Boolean {
        return get(key) != null
    }

    /**
     * 查找的逻辑 跳表查找的核心就是 从上到下、每层尽量向右,最后在底层确认
     *
     * 高层 → 定位范围 低层 → 精确查找(最终确认节点)
     *
     * 优先向右查找,如果不满足,再向下查找
     */
    override fun get(key: K): V? {
        if (isEmpty()) return null
        // 从哨兵节点 head 开始查找,所有的值都在 node.forward[level] 里面
        // 两个维度,一个处理同行(向右),一个处理同列(向下)
        var node = head
        var levelIdx = level() - 1

        while (levelIdx >= 0) {
            val storageNode = node.getRight(levelIdx)
            if (storageNode == null) {
                // 存储值的节点不存在,直接向下查找
                levelIdx--
            } else { // 存储值的节点存在,作比较
                val currKey = storageNode.key!!
                when {
                    currKey == key -> return storageNode.value
                    key > currKey -> {
                        // 继续向右查找
                        node = storageNode
                    }

                    else -> { // key < currKey 向下查找
                        levelIdx--
                    }
                }
            }
        }
        return null
    }

    override fun insert(key: K, value: V) {
        // 每一层 默认 head 为前驱节点
        // 前驱的核心是每一层最后一个小于key的,每一层本身就是顺序列表,所以不需要从Head再遍历
        val update = Array(maxLevel) { head }
        var currNode = head
        var levelIdx = level() - 1 // 从存储的最高的level开始,避免遍历无效的层数

        while (levelIdx >= 0) {
            val storageNode = currNode.getRight(levelIdx)
            if (storageNode == null) {
                update[levelIdx] = currNode
                levelIdx--
            } else {
                val storageKey = storageNode.key!!
                when {
                    key == storageKey -> {
                        // 更新的时候发现匹配到了值,直接更新,并退出
                        storageNode.value = value
                        return
                    }

                    key > storageKey -> {
                        // 继续向右查找,刷新当前的node
                        currNode = storageNode
                    }

                    else -> { // key < storageKey
                        update[levelIdx] = currNode  // 这一层找到了
                        levelIdx-- // 继续向下
                    }
                }
            }
        }

        // 真实的插入操作
        // 1. update中需要指向新的节点
        // 2. 插入的节点需要更新索引
        // 3. 那么从思路上来说,需要从下往上更新
        val newNode = SkipListNode(key, value, this.maxLevel, this.p)

        val newNodeLevel = newNode.getNodeLevel().apply {
            if (this > skipListLevel) { // 更新levelCount
                skipListLevel = this
            }
        }

        // kotlin 1.9 新语法 ..< 左闭右开
        // for (levelIndex in 0..<newNodeLevel) {
        for (levelIndex in 0 until newNodeLevel) {
            val updateNode = update[levelIndex] // 由于哨兵节点的设计,前驱节点一定存在
            val updateRight = updateNode.getRight(levelIndex)
            newNode.setRight(levelIndex, updateRight)
            updateNode.setRight(levelIndex, newNode)
        }

        nodeCount++
    }

    override fun remove(key: K): V? {
        // 删除节点的前驱节点
        val update = arrayOfNulls<SkipListNode<K, V>>(level())
        var node = head
        var levelIdx = level() - 1 // 删除的时候对于节点的更新不需要从最高层

        var target: SkipListNode<K, V>? = null

        while (levelIdx >= 0) {
            val storage = node.getRight(levelIdx)
            if (storage == null) {
                levelIdx--
            } else {
                val storageKey = storage.key!!
                when {
                    storageKey == key -> { // 找到需要删除的节点
                        target = storage
                        update[levelIdx] = node
                        levelIdx-- // 继续寻找下一层
                    }

                    storageKey < key -> { // 目前的值小于需要删除的值
                        node = storage // 继续向右查找
                    }

                    else -> { // storageKey > key
                        levelIdx-- // 继续向下查找
                    }
                }
            }
        }

        if (target == null) {
            println("not find target")
            return null
        }

        // 更新索引
        // 1. 对于被删除的节点,更新前驱节点的指向
        // 2. 如果前驱节点没有指向过当前节点,则不管
        // 3. 如果前驱节点指向了当前节点,更新到指向的节点的新节点
        val targetLevel = target.getNodeLevel()

        // 从最底层开始更新
        for (levelIndex in 0 until targetLevel) {
            // 如果存在前驱节点则更新
            update[levelIndex]?.apply {
                val left = this
                left.setRight(levelIndex, target.getRight(levelIndex))
            }
        }

        // 节点被删除了,重新更新跳表本身的层高,不能确定哪个是MAX,从上到下遍历一遍
        // 4) 收缩最高层级:当顶层为空时向下收缩
        while (skipListLevel > MIN_LEVEL && head.getRight(skipListLevel - 1) == null) {
            skipListLevel--
        }

        nodeCount--

        return target.value
    }

    override fun clear() {
        this.skipListLevel = MIN_LEVEL
        this.nodeCount = 0

        for (i in 0 until head.getNodeLevel()) {
            head.setRight(i, null)
        }
    }

    private inner class SkipListNode<K : Comparable<K>, V>(
        val key: K?,   // 不可变,唯一标识
        var value: V?, // 可变,更新值
        val maxLevel: Int,
        val p: Double,
        head: Boolean = false
    ) {

        // 节点的层数: 跳表的核心 概率化
        private val nodeLevel: Int = if (head) maxLevel else randomLevel()

        // 本质上由 forward[0] 构成了链表,其他层的都是索引
        private val forward: Array<SkipListNode<K, V>?> = arrayOfNulls(maxLevel)

        /**
         * 获取指向的值
         */
        fun getRight(level: Int): SkipListNode<K, V>? = forward[level]

        /**
         * 设置指向的值
         */
        fun setRight(level: Int, node: SkipListNode<K, V>?) {
            forward[level] = node
        }

        /**
         * 获取节点的随机层数
         */
        fun getNodeLevel(): Int = this.nodeLevel

        /**
         * 随机生成一个层数 [1, MAX_LEVEL],最终体现在创建的数组的大小上
         */
        private fun randomLevel(): Int {
            var level = 1
            while (random.nextDouble() < p && level < this.maxLevel) {
                level++
            }
            return level
        }
    }

    override fun print() {
        if (isEmpty()) {
            println("SkipList(empty)")
            return
        }

        // 底层(level 0)所有节点,作为列对齐基准;首列放入 HEAD
        val columns = mutableListOf<SkipListNode<K, V>>()
        columns.add(head)
        var n = head.getRight(0)
        while (n != null) {
            columns.add(n)
            n = n.getRight(0)
        }

        // 计算对齐宽度(考虑 HEAD 与所有 key 的宽度)
        val maxKeyLen = columns.drop(1).maxOfOrNull { it.key.toString().length } ?: 0
        val cellWidth = maxOf("(HEAD)".length, maxKeyLen) + 2
        val arrowToken = "-> "
        val arrowSpace = " ".repeat(arrowToken.length)

        // 自顶向下逐层打印
        for (lvl in level() - 1 downTo 0) {
            // 1) 该层的 incoming 目标集合:凡是在该层能被某个前驱指到的节点,都算 incoming
            val incoming = HashSet<SkipListNode<K, V>>()
            var from = head.getRight(lvl)
            while (from != null) {
                incoming.add(from)           // from 在该层有前驱(head 或上一节点)
                from = from.getRight(lvl)
            }

            val sb = StringBuilder().apply { append("Level ").append(lvl).append(": ") }

            // 2) 逐列打印:该列存在则打印 key;在“列间空隙”处,如果右侧列是 incoming,则画箭头
            for (i in columns.indices) {
                val node = columns[i]
                val label = if (node === head) "(HEAD)"
                else if (lvl < node.getNodeLevel()) node.key.toString()
                else ""
                sb.append(label.padEnd(cellWidth, ' '))

                if (i < columns.lastIndex) {
                    val rightNode = columns[i + 1]
                    val drawArrow = incoming.contains(rightNode) // 右侧列若在本层为 incoming,则在这里画箭头
                    sb.append(if (drawArrow) arrowToken else arrowSpace)
                }
            }
            println(sb.toString().trimEnd())
        }
    }

}