给你30s,如何跟口试官讲清楚跳表

打印 上一主题 下一主题

主题 1057|帖子 1057|积分 3171

查找

假设有如下如许一个有序链表:

想要查找 24、43、59,按照顺序遍历,分别必要比较的次数为 2、4、6
目前查找的时间复杂度是 O(N),如何提高查找服从?
很容易想到二分查找,将查找的时间复杂度降到 O(LogN)
详细来说,我们把链表中的一些节点提取出来,作为索引,雷同于二叉搜索树,得到如下结构:

这里我们把 10、30、50、80 提取出来作为一级索引,如许搜索的时间就可以利用二分查找来淘汰比较次数了。
我们还可以再从一级索引提取一些元素出来,作为二级索引,酿成如下结构:

比如如果想要查找 59,那么搜索路径就是下面如许的:

回顾下链表的定义:
  1. class ListNode {
  2.     private int val;
  3.         private ListNode next;
  4.    
  5.     public ListNode(int val) {
  6.         this.val = val;
  7.         this.next = null;
  8.     }
  9. }
复制代码
我们在每一个节点的基础上添加一个 down 指针,用来指向下一层的节点
  1. class Node {
  2.     private int val;
  3.         private ListNode next;
  4.     private ListNode down;
  5.    
  6.     public ListNode(int val) {
  7.         this.val = val;
  8.         this.next = null;
  9.         this.down = null;
  10.     }
  11. }
复制代码
如许,一个最简朴的跳表节点就定义出来了。
   我们这里说的只是最简朴的实现,像比如 Redis 的跳表实现和我们说的照旧有所差别的,当然了,头脑都是同等的
  所以跳表是什么?简朴来说,跳表就是支持二分查找的有序链表
详细的搜索算法如下:
  1. /* 如果存在 x, 返回 x 所在的节点, 否则返回 x 的后继节点 */  
  2. private Node find(x)  {  
  3.     p = top;  
  4.     while (true) {  
  5.         while (p.next.val < x){
  6.             p = p.next;
  7.         }
  8.         if (p.down == null){
  9.             return p.next;  
  10.         }
  11.         p = p.down;  
  12.     }
  13.     return null;
  14. }
复制代码
插入

关于插入,大家可能很容易想到往最下面一层的有序链表中添加数据,但是索引该咋办?索引要不要更新呢?
如果不更新索引,就可能出现两个索引节点之间数据非常多的情况,极端情况下跳表就会退化为单链表,从而使得查找服从从 O(LogN) 退化为 O(N)。
所以,我们在插入数据的时间,索引节点也必要相应的改变来避免查找服从的退化
比较容易想到的做法就是完全重修索引,我们每次插入数据后,都把这个跳表的索引删掉全部重修。因为索引的空间复杂度是 O(N),即:索引节点的个数是 O(N) 级别,每次完全重新建一个 O(N) 级别的索引,时间复杂度也是 O(N) 。造成的后果是:为了维护索引,导致每次插入数据的时间复杂度酿成了 O(N)。
那有没有其他服从比较高的方式来维护索引呢?
最理想的索引就是在原始链表中每隔一个元素抽取一个元素做为一级索引。换种说法,我们在原始链表中【随机】的选 n/2 个元素做为一级索引是不是也能通过索引提高查找的服从呢?
当然可以,因为一般随机选的元素相对来说都是比较均匀的。如下图所示,随机选择了 n/2 个元素做为一级索引,虽然不是每隔一个元素抽取一个,但是对于查找服从来讲,影响不大,比如我们想找元素 16,仍然可以通过一级索引,使得遍历路径较少了将近一半。

当然了,如果抽取的一级索引的元素恰恰是前一半的元素 1、3、4、5、7、8,那么查找服从确实没有提拔,但是如许的概率太小了。所以我们可以认为:当原始链表中元素数目足够大,且抽取足够随机的话,我们得到的索引是均匀的。所以,我们可以维护一个如许的索引:随机选 n/2 个元素做为一级索引、随机选 n/4 个元素做为二级索引、随机选 n/8 个元素做为三级索引,依次类推,不停到最顶层索引。这里每层索引的元素个数已经确定,且每层索引元素选取的足够随机,所以可以通过索引来提拔跳表的查找服从。
那代码详细该如何实现,使得在每次新插入元素的时间,尽量让该元素有 1/2 的几率创建一级索引、1/4 的几率创建二级索引、1/8 的几率创建三级索引....呢?
实在很简朴啦,搞一个概率算法就行了(详细是怎么个概率法这里就不详细表明白),当每次有数据要插入时,先通过概率算法告诉我们这个元素必要插入到几级索引中,然后开始维护索引并把数据插入到原始链表中。
如下所示,插入新元素 12,假设概率算法返回的效果是 4,表示新元素必要插入到 4 级索引中,同时,我们还必要创建 3 级索引、2 级索引和 1 级索引(也就是原始有序链表)

那插入数据时维护索引的时间复杂度是多少呢?
跳表中,每一层索引都是一个有序的单链表,元素插入到单链表的时间复杂度为 O(1),我们索引的高度最多为 LogN,当插入一个元素 x 时,最坏的情况就是元素 x 必要插入到每层索引中,所以插入数据的最坏时间复杂度是 O(LogN),最好的时间复杂度是 O(1)。
删除

跳表删除数据时,要把索引中对应节点也要删掉。如下图所示,如果要删除元素 8,必要把原始链表中的 8 和第 2、3 级索引的 8 都删除掉。

删除元素的过程跟查找元素的过程雷同,只不过在查找的路径上如果发现了要删除的元素 x,则实验删除操作。
跳表中,每一层索引都是一个有序的单链表,单链表删除元素的时间复杂度为 O(1),最多必要删除 LogN 个元素(索引层数为 LogN),所以删除元素的总时间包 = 查找元素的时间 + 删除 LogN 个元素的时间 = O(LogN ) + O(LogN ) = 2O(LogN ),忽略常数部分,删除元素的时间复杂度为 O(LogN)。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

西河刘卡车医

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表