加入我们要开发一个游戏里面类似拍卖行的搜索功能,我们要支持输入道具名称的精准查询和不输入名称的全量查询.
拍卖行商品数量几十万件,对应数据库商品表的几十万条记录,按照商品名称精准查询好办,可以直接冲数据库查出来,
如果没有商品名称的全量查询怎么办?总不能把数据库所有记录查出来,还要支持不同字段的排序.
拍卖行商品列表是线性的,最容易表达线性结构的是数组和链表,但是数组使用二分查找可以最快定位,时间复杂度O(logN)<
插入过程的时间复杂度O(N);链表无法使用二分查找,查找的时间复杂度是O(N),插入的时间复杂度O(1),因此总体时间复杂度是O(N).
所以用到了跳跃表,跳跃表(skiplist)是一种基于有序链表的扩展,简称调表.
怎么快速查找到一个有序链表的某一结点?
既然是链表,就不能用二分查找,我们可以利用类似索引的思想,提取链表中部分关键节点.
确定新节点在关键节点中的位置,就可以回到原链表,迅速定位到对应的位置插入.
既然提出了一层关键节点作为索引,那我们为何不能从索引中进一步提取,2层,3层索引.这样当节点很多的时候,比较次数会减少到原来的四分之一,八分之一.
提取的极限是同一层只有两个节点的时候,因为一个节点没有比较的意义了.
当大量的新节点通过逐层比较,最终插入到原链表之后,需要从新节点中选取一部分提取到上一层,这个是否提取的策略是抛硬币.
为什么抛硬币?因为跳跃表删除和添加的节点是不可预测的,很难用一种有效的算法来保证跳表的索引分布始终均匀.
跳跃表的插入操作步骤:
1.新节点和各层索引节点逐一比较,确定原链表的插入位置,时间复杂度和数组的二分查找一样O(logN)
2.把索引插入到原链表,时间复杂度O(1)
3.利用抛硬币的随机方式,决定新节点是否提升为上一级索引,结果为"正"提升并继续抛硬币,结果为"负"则停止,时间复杂度O(logN)
总体上,跳跃表插入操作的时间复杂度O(logN),数据结构所占空间是2N,空间复杂度O(N)
跳跃表的删除比较简单,从索引层找到要删除的节点,逐层删除相同节点,如果某一层删除后只剩下一个节点,那么整层都可以删除了.
跳跃表删除操作步骤:
1.自上而下,查找第一个出现节点的索引,逐层找到每一层对应节点,时间复杂督导O(logN)
2.删除每一层查找到的节点,如果该层只剩下1个节点,删除整个一层(原链表除外),时间复杂度O(logN)
总体上,跳跃表删除操作的时间复杂度是O(logN)
跳跃表的有点事维持结构平衡的成本比较低,完全依靠随机
二叉查找树在多次插入删除后,需要Rebalance来重新调整结构平衡
跳跃表应用:1.Redis中的Sorted-set这种有序集合,正是对于跳跃表的改进和应用
2.关系数据库维护有序的记录集合使用的是B+树