跳表是个好东西你值得拥有!

跳表是一种用于高效查找的有序数据结构,它通过构建多级索引来实现近似的二分查找。在有序的单链表中,跳表通过随机添加节点到不同层级的索引,允许在查找时快速跳过多个节点,从而提高查找效率。本文介绍了跳表的结构、实现方法,包括插入、删除、查找和范围查找操作,并提供了相关代码示例。
摘要由CSDN通过智能技术生成

由于数组支持随机访问,所以在一个有序的数组里面我们想查找一个数据我们可以使用下标进行二分查找,但是数组的问题在于他的有序插入是个大问题,在最后面插入也还好,如果要插入到最前面那代价就太大了,而且随着数据量的上升这个问题更加严重。与之相对的单链表则是另一种情形他插入简单,但是不支持随机访问。随着数据量的上升查找的速度为O(n)显然是不够看的,那么我们是否有个大胆的猜想,如果有一种链表可以实现或者能让我们近似于二分查找那么这个问题不就迎刃而解了吗!

1、跳表的结构

跳表真的是表如其名他可以跳过某些数据(这个一切都有一个前提----有序,这个有序是要靠我们自己来保证的,当然我们为了有序也是要付出一些额外的代价但是这个是值得的),这个特性使得他的查找速度几乎是能和二分查找媲美的,那么他是怎么做的呢,我们来看看他的结构
跳表结构

他是通过构建多级索引来实现这个特性的,查找的时候从高级索引向下查找知道查找到节点,如果现象一下如果是单链表[1,3,4,5,7,8,9,10,13,16,17,18]如果我们想查找8这个数字那么需要一次查到1,3,4,5,6,7最后由7查到8,但是如果我们加了索引,我们现在可以利用索引从高到底开始查询,如图我们先查询1节点,然后1节点在最高一级索引也就是第二级索引后面指向了7,比较一下7比8小,那我们一步就跳到了7的位置,3,4,5都不用比了,7节点在第二级索引后面指向的是13,13是比8大的,那我们就从二级索引往一级索引搜索,7节点在一级索引后面指向的节点是9,9也比8大啊那我们的就从一级索引往原始链表跳,在原始的链表后面是8刚好是我们要找的数字,那么这就找到了。在走二级索引的时候我们一次就跳过了3个节点,是不是有二分查找那味了。你细品如果找的是18是不是相对于单链表就快的更多了!
说到这里想必你对跳表的结构已经成竹在胸了,那么下面我们就试着来写一个出来。

2、跳表的实现

先来一个基础类和方法的定义

class ListNode:
    def __init__(self, value = None, level = None):
        self._value = value #当前节点的值
        self._after_nodes = [] if level is None else [None] * level #当前节点在索引里面都指向了那些位置


class SkipList:
    _MAX_LEVEL = 4 #最大的索引层数
    
    def __init__(self):
        self._level_count = 1 #当前的索引层数
        self._head = ListNode() #头节点没有任何数据
        self._head._after_nodes = [None] * self._MAX_LEVEL #头节点的指向
 	
 	def find(self, value):
        '''
        查找一个元素,返回一个 ListNode 对象
        '''
        pass


    def find_range(self, begin_value, end_value) :
        '''
        查找一个元素,返回一组有序 ListNode 对象
        '''
        pass
        

    def insert(self, value):
        '''
        插入一个元素,返回 True 或 False
        '''
        pass


    def delete(self, value):
        '''
        删除一个元素,返回 True 或 False
        '''
        pass
  
	def _random_level(self, p = 0.5):
        '''
        返回随机层数
        '''

说一下_random_level函数的作用,这个函数是为了产生一个随机层数,为什么要这个随机层数呢,这个随机层数的作用是,在我们插入一个节点的时候,是否需要把这个节点放到高层级的索引上面,想象一下,如果你高级索引一个点都不加进去那么你就成了单链表了,但是你不能每个点都加,每个点都加那不就是多级单链表了,所以这个地方需要一个随机层数,随机到了第几层,那就在这几层里面加上这个节点。
下面我们就增加一点点细节

def _random_level(self, p = 0.5):
    '''
    返回随机层数
    '''
    level = 1
    while random.random() < p and level < self._MAX_LEVEL:
        level += 1
    return level

增加一个节点的代码

def insert(self, value):
    '''
    插入一个元素,返回 True 或 False
    '''
    current_level = self._random_level() #获得当前节点需要放到那些索引层上
    self._level_count = max(self._level_count, current_level) #更新当前跳表的层数
    new_node = ListNode(value, current_level)
    before_nodes = [self._head] * current_level #新产生的节点的  前节点
    current_node = self._head
    for layer_index in range(current_level - 1, -1, -1):
        while current_node._after_nodes[layer_index] and current_node._after_nodes[layer_index]._value < value:
            current_node = current_node._after_nodes[layer_index]
        if current_node._after_nodes[layer_index] and current_node._after_nodes[layer_index]._value == value:
        	#如果走到这里说明这个节点已经存在了
            return False
        before_nodes[layer_index] = current_node #layer_index层新节点的 前节点
   
    for layer_index in range(current_level - 1, -1, -1):
    	#其实就是一个简单的链表插入 before_nodes[layer_index]._after_nodes[layer_index] 是layer_index层 新节点的 前节点对应的后节点
        new_node._after_nodes[layer_index] = before_nodes[layer_index]._after_nodes[layer_index]
        before_nodes[layer_index]._after_nodes[layer_index] = new_node
    return True

删除一个节点

def delete(self, value):
    '''
    删除一个元素,返回 True 或 False
    '''
    need_update_node = [None] * self._level_count
    current_node = self._head
    for layer_index in range(self._level_count - 1, -1, -1):
        while current_node._after_nodes[layer_index] and current_node._after_nodes[layer_index]._value < value:
            current_node = current_node._after_nodes[layer_index]
        need_update_node[layer_index] = current_node
    
    if current_node._after_nodes[0] and current_node._after_nodes[0]._value != value: #判断是否找到了这个节点
        return False
    for layer_index in range(self._level_count - 1, -1, -1):
        if need_update_node[layer_index]._after_nodes[layer_index] and need_update_node[layer_index]._after_nodes[layer_index]._value == value:
            #这个节点的索引放到了这一层上才能删除这节点的索引
            need_update_node[layer_index]._after_nodes[layer_index] = need_update_node[layer_index]._after_nodes[layer_index]._after_nodes[layer_index]
    return True

查找节点

def find(self, value):
    '''
    查找一个元素,返回一个 ListNode 对象
    '''
    current_node = self._head
    for layer_index in range(self._level_count - 1, -1, -1):
        while current_node._after_nodes[layer_index] and current_node._after_nodes[layer_index]._value < value:
            current_node = current_node._after_nodes[layer_index]
        if current_node._after_nodes[layer_index] and current_node._after_nodes[layer_index]._value == value:
            return current_node._after_nodes[layer_index]
    return False


def find_range(self, begin_value, end_value) :
    '''
    查找一个元素,返回一组有序 ListNode 对象
    '''
    current_node = self._head
    begin_node = None
    for layer_index in range(self._level_count - 1, -1, -1):
        while current_node._after_nodes[layer_index] and current_node._after_nodes[layer_index]._value < begin_value:
            current_node = current_node._after_nodes[layer_index]
        if current_node._after_nodes[layer_index] and current_node._after_nodes[layer_index]._value >= begin_value:
            begin_node = current_node._after_nodes[layer_index]

    result = []
    while begin_node and begin_node._value < end_value:
        result.append(begin_node)
        begin_node = begin_node._after_nodes[0]
    return result

打印跳表结构

def pprint(self):
    '''
    打印跳表
    '''
    for layer_index in range(self._level_count - 1, -1, -1):
        layer_info = f'layer num "{layer_index}":'
        current_node = self._head._after_nodes[layer_index]
        while current_node:
            layer_info += f' -> {current_node._value}'
            current_node = current_node._after_nodes[layer_index]
        print(layer_info)

做个简单的测试

if __name__ == "__main__":
    l = SkipList()
    for i in [1,3,4,5,7,8,9,10,13,16,17,18]:
        l.insert(i)
    l.pprint()
    if l.delete(10):
        print("delete 10 success.")
    if not l.delete(12):
        print("delete 12 fail.")
    print("find 18 : ",l.find(18)._value)
    print("find data between 4 and 10:")
    for d in l.find_range(4,110):
        print(d._value, end ='->')

跳表测试数据

因为索引都是保存的下一个节点的地址所以相对于上图真实的结构是这样的
在这里插入图片描述

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值