在 Redis 中,跳表(Skip List)是一种用于实现有序集合(Sorted Set)底层数据结构的算法。与传统的平衡树(如红黑树)相比,跳表结构相对简单,并且在实现上更加直观。
跳表通过在有序链表上构建多级索引来加速搜索操作,跳表的每一层都是一个有序链表,且每一层的元素是下一层的子集,通过多级索引,可以在对数级别的时间复杂度内进行插入、删除和查找操作。
跳表的基本结构
一个跳表节点包含以下几个部分:
score:节点的分数,用于排序。value:节点的值。forward:一个数组,包含指向不同层级的下一个节点的指针。level:节点的层级。
跳表的具体实现
下面是跳表的基本实现,包括节点的定义、跳表的初始化、插入、删除和查找操作。
节点定义
首先,定义跳表节点结构。
java
class SkipListNode {
double score;
String value;
SkipListNode[] forward;
public SkipListNode(int level, double score, String value) {
this.score = score;
this.value = value;
this.forward = new SkipListNode[level + 1];
}
}
跳表定义
定义跳表结构。
java
class SkipList {
private static final int MAX_LEVEL = 16;
private static final double P = 0.5;
private SkipListNode head;
private int level;
public SkipList() {
this.level = 0;
this.head = new SkipListNode(MAX_LEVEL, 0, null);
}
// 随机生成节点层级
private int randomLevel() {
int lvl = 0;
while (Math.random() < P && lvl < MAX_LEVEL) {
lvl++;
}
return lvl;
}
// 插入节点
public void insert(double score, String value) {
SkipListNode[] update = new SkipListNode[MAX_LEVEL + 1];
SkipListNode x = head;
for (int i = level; i >= 0; i--) {
while (x.forward[i] != null && x.forward[i].score < score) {
x = x.forward[i];
}
update[i] = x;
}
int lvl = randomLevel();
if (lvl > level) {
for (int i = level + 1; i <= lvl; i++) {
update[i] = head;
}
level = lvl;
}
x = new SkipListNode(lvl, score, value);
for (int i = 0; i <= lvl; i++) {
x.forward[i] = update[i].forward[i];
update[i].forward[i] = x;
}
}
// 删除节点
public void delete(double score, String value) {
SkipListNode[] update = new SkipListNode[MAX_LEVEL + 1];
SkipListNode x = head;
for (int i = level; i >= 0; i--) {
while (x.forward[i] != null && x.forward[i].score < score) {
x = x.forward[i];
}
update[i] = x;
}
x = x.forward[0];
if (x != null && x.score == score && x.value.equals(value)) {
for (int i = 0; i <= level; i++) {
if (update[i].forward[i] != x) break;
update[i].forward[i] = x.forward[i];
}
while (level > 0 && head.forward[level] == null) {
level--;
}
}
}
// 查找节点
public SkipListNode search(double score) {
SkipListNode x = head;
for (int i = level; i >= 0; i--) {
while (x.forward[i] != null && x.forward[i].score < score) {
x = x.forward[i];
}
}
x = x.forward[0];
if (x != null && x.score == score) {
return x;
}
return null;
}
}
示例使用
下面是一个简单的示例,展示如何使用上述跳表实现插入、删除和查找操作。
java
public class SkipListExample {
public static void main(String[] args) {
SkipList skipList = new SkipList();
// 插入节点
skipList.insert(3.0, "Alice");
skipList.insert(1.0, "Bob");
skipList.insert(2.0, "Charlie");
// 查找节点
SkipListNode node = skipList.search(2.0);
if (node != null) {
System.out.println("Found node: " + node.value);
} else {
System.out.println("Node not found.");
}
// 删除节点
skipList.delete(1.0, "Bob");
// 查找被删除的节点
node = skipList.search(1.0);
if (node != null) {
System.out.println("Found node: " + node.value);
} else {
System.out.println("Node not found.");
}
}
}
Redis 中使用跳表的优势
- 时间复杂度:跳表的插入、删除和查找操作的平均时间复杂度都是 (O(\log n)),与平衡树相当,但实现更为简单。
- 空间复杂度:跳表在最坏情况下的空间复杂度为 (O(n \log n)),但在实际使用中,空间效率较高。
- 实现简单:相对于红黑树和 AVL 树等平衡树,跳表的实现较为简单,易于理解和调试。
Redis 选择跳表作为有序集合的数据结构,主要是因为跳表在实现排序、范围查询等操作时,性能优越且实现简单。希望这段代码和解释能帮助你更好地理解跳表的原理和实现。