跳表

2021/03/13 Algorithms 共 3334 字,约 10 分钟

跳表

redis跳表skiplist参考: 参考链接

参考链接

1206. 设计跳表

image-20210606175409821

通过这张图,可以先确定跳表中每个节点的数据结构:

class Node{
    Integer value; //节点值
    Node[] next; // 节点在不同层的下一个节点

    public Node(Integer value,int size) { // 用size表示当前节点在跳表中索引几层
        this.value = value;
        this.next = new Node[size];
    }
}

知道了层数,这下就好办了。思路如下:

  1. 先随机出来一个层数,new要插入的节点,先叫做插入节点newNode;
  2. 根据跳表实际的总层数从上往下分析,要插入一个节点newNode时,先找到节点在该层的位置:因为是链表,所以需要一个节点node,满足插入插入节点newNode的值刚好不大于node的下一个节点值,当然,如果node的下个节点为空,那么也是满足的。

我们先把找节点在某一层位置的方法封装起来:

/**
* 找到level层 value 刚好不小于node 的节点
* @param node 从哪个节点开始找
* @param levelIndex 所在层
* @param value 要插入的节点值
* @return
*/
private Node findClosest(Node node,int levelIndex,int value){
    while ((node.next[levelIndex])!=null && value >node.next[levelIndex].value){
        node = node.next[levelIndex];
    }
    return node;
}
  1. 确定插入节点newNode在该层的位置后,先判断下newNode的随机层数是否小于当前跳表的总层数,如果是,则用链表的插入方法将newNode插入即可。
  2. 如此循环,直到最底层插入newNode完毕。
  3. 循环完毕后,还需要判断下newNode随机出来的层数是否比跳表的实际层数还要大,如果是,直接将超过实际层数的跳表的头节点指向newNode即可,该跳表的实际层数也就变为newNode的随机层数了。

以上就是插入的算法。

理解了插入算法后,查找跟删除就简单多了。

不管是插入、查找还是删除,均是从跳表上层往下层分析,复用上面的findClosest方法,找到要查询的值 在该层closest的节点。比如查询,只需要判断findClosest出来的节点值是否等于该查询值即可,是就返回,不是则继续往下层判断。删除方法思想也是一致的。

import random
class Node:
    def __init__(self, value, size):
        # 节点值
        self.value = value
        # 节点在不同层的下一个节点. 用size表示当前节点在跳表中索引几层
        self.next = [None for i in range(size)]

class Skiplist:
    # 插入:1. 先随机出来一个层数,new要插入的节点,先叫做插入节点newNode;2. 根据跳表实际的总层数从上往下分析,要插入一个节点newNode时,先找到节点在该层的位置:因为是链表,所以需要一个节点node,满足插入插入节点newNode的值刚好不大于node的下一个节点值,当然,如果node的下个节点为空,那么也是满足的。3. 确定插入节点newNode在该层的位置后,先判断下newNode的随机层数是否小于当前跳表的总层数,如果是,则用链表的插入方法将newNode插入即可。4. 如此循环,直到最底层插入newNode完毕。5. 循环完毕后,还需要判断下newNode随机出来的层数是否比跳表的实际层数还要大,如果是,直接将超过实际层数的跳表的头节点指向newNode即可,该跳表的实际层数也就变为newNode的随机层数了。不管是插入、查找还是删除,均是从跳表上层往下层分析,复用上面的findClosest方法,找到要查询的值 在该层closest的节点。查询:只需要判断findClosest出来的节点值是否等于该查询值即可,是就返回,不是则继续往下层判断。
    def __init__(self):
        self.default_max_level = 32
        self.default_p_factor = 0.25
        # 哨兵头节点
        self.head = Node(None, self.default_max_level)
        # 表示当前nodes的实际层数,它从1开始
        self.current_level = 1

    # 找到level层 value 大于node 的节点
    def __find_closest(self, node, level_index, value):
        while node.next[level_index] is not None and value > node.next[level_index].value:
            node = node.next[level_index]
        return node

    def __random_level(self):
        level = 1
        while random.random() < self.default_p_factor and level < self.default_max_level:
            level += 1
        return level

    def search(self, target: int) -> bool:
        search_node = self.head
        for i in range(self.current_level - 1, -1, -1):
            search_node = self.__find_closest(search_node, i, target)
            if search_node.next[i] is not None and search_node.next[i].value == target:
                return True
        return False

    def add(self, num: int) -> None:
        level = self.__random_level()
        update_node = self.head
        new_node = Node(num, level)
        # 计算出当前num 索引的实际层数,从该层开始添加索引
        for i in range(self.current_level - 1, -1, -1):
            # 找到本层最近离num最近的list
            update_node = self.__find_closest(update_node, i, num)
            if i < level:
                if update_node.next[i] is None:
                    update_node.next[i] = new_node
                else:
                    temp = update_node.next[i]
                    update_node.next[i] = new_node
                    new_node.next[i] = temp
        # 如果随机出来的层数比当前的层数还大,那么超过currentLevel的head 直接指向newNode
        if level > self.current_level:
            for i in range(self.current_level, level):
                self.head.next[i] = new_node
            self.current_level = level

        
    def erase(self, num: int) -> bool:
        # 由于我们的实现是在一个跳表中只有一个节点拥有num的值,通过next数组模拟在不同层数,因此需要遍历所有层数删除节点。
        deleted = False
        search_node = self.head
        for i in range(self.current_level - 1, -1, -1):
            search_node = self.__find_closest(search_node, i, num)
            if search_node.next[i] is not None and search_node.next[i].value == num:
                # 删除当前层中的模拟节点。删除方法与删除一个普通链表的节点相同。
                search_node.next[i] = search_node.next[i].next[i]
                deleted = True
                continue
        return deleted

Search

    Table of Contents