目录
一、什么是跳表
我们知道链表这种结构,虽然他的插入和删除的时间复杂度很高,只有O(1),但是它的查找却很慢,为O(n)。例如我们要查找一个元素,不得不遍历一边链表才能找到:
但是其实我们能像下图中那样,对链表建立一级“索引”,以提高查找速度,即每两个结点提取一个结点到上一级,我们把抽出来的那一级叫做索引或索引层,索引层有个next指针指向索引在链表中位置:
当然这只是一级索引,如果数据量庞大,我们可以建立两级、三级,甚至十级索引,这样的链表查找时间复杂度可以高达O(logn)。
这种链表加多级索引的结构,就是跳表。
二、跳表的内存问题
也许你也已经发现了,在链表上添加这些索引,会明显的增大内存,因为需要额外的空间来存储这些索引层元素。
这几级索引的结点总和就是 n/2+n/4+n/8…+8+4+2=n-2。所以,跳表的空间复杂度是 O(n)。
但是因为索引层元素实际上只是存储了一个next指针(指向下一个索引)以及down指针(指向下一级索引),所以如果链表内的实际有效数据很大,那这些指针的内存完全可以忽略不计。
而且我们也可以通过跳转索引间隔,来动态的控制索引所占内存,例如我们修改索引间隔为3时。
总的索引结点大约就是 n/3+n/9+n/27+...+9+3+1=n/2。尽管空间复杂度还是 O(n),但比上面的每两个结点抽一个结点的索引构建方法,要减少了一半的索引结点存储空间。
三、跳表的复杂度分析
跳表的查找、删除和插入的时间复杂度都是O(logn)。
四、跳表索引动态更新
当我们不停地往跳表中插入数据时,如果我们不更新索引,就有可能出现某 2 个索引结点之间数据非常多的情况。极端情况下,跳表还会退化成单链表。
所以当我们往跳表中插入数据的时候,我们可以选择同时将这个数据插入到部分索引层中。如何选择加入哪些索引层呢?我们通过一个随机函数,来决定将这个结点插入到哪几级索引中,比如随机函数生成了值 K,那我们就将这个结点添加到第一级到第 K 级这 K 级索引中。随机函数的选择很有讲究,从概率上来讲,能够保证跳表的索引大小和数据大小平衡性,不至于性能过度退化。