1 跳表基础
1.1 概念
跳表是特殊的链表,其特点如下:
- 多层组成,每一层都是有序链表
- 一个元素在某一层出现,一定会出现在其下一层的链表中
- 最底层是原始链表,包含跳表中所有元素
- 链表中的节点维护了指向其他节点的指针
- 目的是快速访问/插入/更新/删除
1.2 复杂度分析
跳表性质:
- 第k级索引的节点个数是k-1级索引节点个数的一半,第k级索引的节点个数是
n/(2^k)
- 第k级索引的节点个数是k-1级索引节点个数的一半,原始链表节点个数是n,最高级别的索引节点数是2,
n/(2^h)=2
,其索引层数是log(n)-1
- 第k级索引的节点个数是k-1级索引节点个数的一半,原始链表节点个数是n,其索引总节点数是
n/2, n/4, n/8, ..., 8, 4, 2
,等比数列求和公式n-2
,或者是n-2^(-n-1))
。
1.2.1 时间复杂度分析
- 查询一个数据的时间复杂度是
log(n)
链表总节点数是n,其高度是log(n)-1 + 1 = log(n)
,每一层遍历的节点数最大是3,因此,查找到目标数字的时间复杂度是3*log(n)=log(n)
。跳表是基于单链表和二分查找的原理,借助二分思想实现快速查找。
1.2.2 空间复杂度分析
- 空间复杂度是
n
第k级索引的节点个数是k-1级索引节点个数的一半,索引节点的个数是n-2
。
2 跳表应用
2.1 Redis有序集合
redis中的有序集合是集成了哈希表以及跳表。
- 哈希表查找单个元素,时间复杂度O(1)
- 跳表方便区间查找
2.2 平衡数据结构对比
No. | 跳表 | 平衡查找树 |
---|---|---|
1 | 概率性平衡(随机确定是否添加下一级索引节点) | 严格平衡 |
2 | 原理简单,编码容易实现 | 原理较复杂,有已实现案例如HashMap |
2 | 查找 时间复杂度:平均log(n) ,最坏n | 查找 时间复杂度:平均log(n) |
3 跳表模版
class Skiplist {
/**
* https://leetcode-cn.com/problems/design-skiplist/solution/java-jing-jian-shi-xian-by-stg/
*/
Node head = new Node(0, null, null);
Random random = new Random();
Node[] stack = new Node[64];
public Skiplist() {
}
/**
* 从左向右,从上到下
*
* @param target
* @return
*/
public boolean search(int target) {
// 指针下沉到下一层
for (Node p = head; p != null; p = p.down) {
while (p.right != null && p.right.val < target) {
// 索引上的节点值小雨目标值,需要指针右移
p = p.right;
}
// p是本层索引最后一个节点,或者其右侧节点值大于目标值
if (p.right != null && p.right.val == target) {
return true;
}
}
return false;
}
/**
* 1 找到目标位置的前一个节点
* 2
*
* @param num
*/
public void add(int num) {
int lv = -1;
for (Node p = head; p != null; p = p.down) {
while (p.right != null && p.right.val < num) {
p = p.right;
}
stack[++lv] = p;
}
boolean isInsertUp = true;
Node downNode = null;
while (isInsertUp && lv >= 0) {
Node insert = stack[lv--];
insert.right = new Node(num, insert.right, downNode);
downNode = insert.right;
// 是否在在上一层插入此节点的索引
isInsertUp = (random.nextInt() & 1) == 0;
}
if (isInsertUp) {
head = new Node(0, new Node(num, null, downNode), head);
}
}
public boolean erase(int num) {
boolean isExists = false;
for (Node p = head; p != null; p = p.down) {
while (p.right != null && p.right.val < num) {
p = p.right;
}
/**
* 在每一层,找到目标节点的前一个节点
* 如果删除的是索引未知的节点,for循环还会继续下沉到愿链表进行删除
*
*/
if (p.right != null && p.right.val <= num) {
isExists = true;
p.right = p.right.right;
}
}
return isExists;
}
class Node {
int val;
Node right;
Node down;
Node(int x) {
val = x;
}
Node(int x, Node right, Node down) {
val = x;
this.right = right;
this.down = down;
}
}
}
引用
https://juejin.im/post/57fa935b0e3dd90057c50fbc