1、原理
跳表是一种概率型数据结构 ,通过多层有序链表实现高效查找,时间复杂度接近平衡树(O(log n))。其核心思想是通过层级索引加速搜索,结构类似火车时刻表的"快车-慢车"模式。
关键特性:
- 多层链表 :
- 第 0 层是完整的有序链表。
- 上层链表是下层的"快速通道",节点间隔逐渐增大。
- 随机层数:插入节点时随机生成层数(如抛硬币,50%概率上升一层)。
- 跳跃查找:从最高层开始搜索,若下一节点值大于目标,则"下降"到下层继续。
示例:
查找值 6
的路径(从顶层开始):
Level 3: 1 ---------------------------> 9
Level 2: 1 --------5--------> 9
Level 1: 1 ---3---5---7----> 9
Level 0: 1-2-3-4-5-6-7-8-9
2、性能分析
-
时间复杂度 :
- 查找/插入/删除 :平均 O(log n),最坏 O(n)(取决于随机层数的分布)。
-
空间复杂度 :平均 O(n)(每层链表存储部分节点)。
-
对比平衡树 :
特性 跳表 平衡树(如AVL) 实现复杂度 简单 复杂 范围查询 高效(顺序遍历底层链表) 需要中序遍历 并发支持 更易实现锁分段 较难
3、 适用场景
- 替代平衡树:需要高效查找且实现简单的场景(如 Redis 的有序集合)。
- 范围查询 :查找区间内的所有值(如
[5, 10]
)。 - 动态数据:频繁插入和删除的场景(维护成本低于平衡树)。
- 内存数据库:适合内存中的高性能数据结构。
不适用场景:
- 对严格 O(log n) 最坏性能有要求的场景。
- 内存极度受限(跳表空间开销略高于数组)。
4、代码实现
Python:
python
import random
from typing import Optional
class SkipNode:
def __init__(self, val: int = -1, levels: int = 0):
self.val = val
self.next = [None] * levels
class SkipList:
def __init__(self, max_level: int = 16):
self.max_level = max_level
self.head = SkipNode(levels=self.max_level)
self.level = 0 # 当前最大层数
def _random_level(self) -> int:
level = 1
while random.random() < 0.5 and level < self.max_level:
level += 1
return level
def search(self, target: int) -> bool:
curr = self.head
for i in range(self.level - 1, -1, -1):
while curr.next[i] and curr.next[i].val < target:
curr = curr.next[i]
curr = curr.next[0]
return curr and curr.val == target
def add(self, num: int) -> None:
update = [self.head] * self.max_level
curr = self.head
# 查找插入位置并记录每层的前驱节点
for i in range(self.level - 1, -1, -1):
while curr.next[i] and curr.next[i].val < num:
curr = curr.next[i]
update[i] = curr
# 随机生成层数
new_level = self._random_level()
if new_level > self.level:
for i in range(self.level, new_level):
update[i] = self.head
self.level = new_level
# 创建新节点并更新指针
new_node = SkipNode(num, new_level)
for i in range(new_level):
new_node.next[i] = update[i].next[i]
update[i].next[i] = new_node
# 调用示例
sl = SkipList()
sl.add(3)
sl.add(6)
sl.add(1)
print(sl.search(6)) # 输出: True
print(sl.search(5)) # 输出: False
golang:
Go
package main
import (
"fmt"
"math/rand"
"time"
)
const maxLevel = 16
type SkipNode struct {
val int
next []*SkipNode
}
type SkipList struct {
head *SkipNode
maxLevel int
currLevel int
}
func NewSkipList() *SkipList {
rand.Seed(time.Now().UnixNano())
head := &SkipNode{val: -1, next: make([]*SkipNode, maxLevel)}
return &SkipList{head: head, maxLevel: maxLevel, currLevel: 1}
}
func (sl *SkipList) randomLevel() int {
level := 1
for rand.Float64() < 0.5 && level < sl.maxLevel {
level++
}
return level
}
func (sl *SkipList) Search(target int) bool {
curr := sl.head
for i := sl.currLevel - 1; i >= 0; i-- {
for curr.next[i] != nil && curr.next[i].val < target {
curr = curr.next[i]
}
}
curr = curr.next[0]
return curr != nil && curr.val == target
}
func (sl *SkipList) Add(num int) {
update := make([]*SkipNode, sl.maxLevel)
curr := sl.head
// 查找插入位置并记录前驱节点
for i := sl.currLevel - 1; i >= 0; i-- {
for curr.next[i] != nil && curr.next[i].val < num {
curr = curr.next[i]
}
update[i] = curr
}
// 生成随机层数
newLevel := sl.randomLevel()
if newLevel > sl.currLevel {
for i := sl.currLevel; i < newLevel; i++ {
update[i] = sl.head
}
sl.currLevel = newLevel
}
// 创建新节点并更新指针
newNode := &SkipNode{val: num, next: make([]*SkipNode, newLevel)}
for i := 0; i < newLevel; i++ {
newNode.next[i] = update[i].next[i]
update[i].next[i] = newNode
}
}
// 调用示例
func main() {
sl := NewSkipList()
sl.Add(3)
sl.Add(6)
sl.Add(1)
fmt.Println(sl.Search(6)) // true
fmt.Println(sl.Search(5)) // false
}
php:
php
<?php
class SkipNode {
public $val;
public $next = array();
public function __construct($val, $levels) {
$this->val = $val;
$this->next = array_fill(0, $levels, null);
}
}
class SkipList {
private $maxLevel = 16;
private $head;
private $currentLevel = 1;
public function __construct() {
$this->head = new SkipNode(-1, $this->maxLevel);
}
private function randomLevel() {
$level = 1;
while (mt_rand() / mt_getrandmax() < 0.5 && $level < $this->maxLevel) {
$level++;
}
return $level;
}
public function search($target) {
$curr = $this->head;
for ($i = $this->currentLevel - 1; $i >= 0; $i--) {
while ($curr->next[$i] !== null && $curr->next[$i]->val < $target) {
$curr = $curr->next[$i];
}
}
$curr = $curr->next[0];
return $curr !== null && $curr->val === $target;
}
public function add($num) {
$update = array_fill(0, $this->maxLevel, $this->head);
$curr = $this->head;
// 查找插入位置并记录前驱节点
for ($i = $this->currentLevel - 1; $i >= 0; $i--) {
while ($curr->next[$i] !== null && $curr->next[$i]->val < $num) {
$curr = $curr->next[$i];
}
$update[$i] = $curr;
}
// 生成随机层数
$newLevel = $this->randomLevel();
if ($newLevel > $this->currentLevel) {
for ($i = $this->currentLevel; $i < $newLevel; $i++) {
$update[$i] = $this->head;
}
$this->currentLevel = $newLevel;
}
// 创建新节点并更新指针
$newNode = new SkipNode($num, $newLevel);
for ($i = 0; $i < $newLevel; $i++) {
$newNode->next[$i] = $update[$i]->next[$i];
$update[$i]->next[$i] = $newNode;
}
}
}
// 调用示例
$sl = new SkipList();
$sl->add(3);
$sl->add(6);
$sl->add(1);
echo $sl->search(6) ? 'true' : 'false'; // 输出: true
echo $sl->search(5) ? 'true' : 'false'; // 输出: false
?>
java:
java
import java.util.Arrays;
import java.util.Random;
class SkipNode {
int val;
SkipNode[] next;
public SkipNode(int val, int levels) {
this.val = val;
this.next = new SkipNode[levels];
}
}
public class SkipList {
private static final int MAX_LEVEL = 16;
private SkipNode head;
private int currentLevel;
private Random random;
public SkipList() {
this.head = new SkipNode(-1, MAX_LEVEL);
this.currentLevel = 1;
this.random = new Random();
}
private int randomLevel() {
int level = 1;
while (random.nextDouble() < 0.5 && level < MAX_LEVEL) {
level++;
}
return level;
}
public boolean search(int target) {
SkipNode curr = head;
for (int i = currentLevel - 1; i >= 0; i--) {
while (curr.next[i] != null && curr.next[i].val < target) {
curr = curr.next[i];
}
}
curr = curr.next[0];
return curr != null && curr.val == target;
}
public void add(int num) {
SkipNode[] update = new SkipNode[MAX_LEVEL];
Arrays.fill(update, head);
SkipNode curr = head;
// 查找插入位置并记录前驱节点
for (int i = currentLevel - 1; i >= 0; i--) {
while (curr.next[i] != null && curr.next[i].val < num) {
curr = curr.next[i];
}
update[i] = curr;
}
// 生成随机层数
int newLevel = randomLevel();
if (newLevel > currentLevel) {
for (int i = currentLevel; i < newLevel; i++) {
update[i] = head;
}
currentLevel = newLevel;
}
// 创建新节点并更新指针
SkipNode newNode = new SkipNode(num, newLevel);
for (int i = 0; i < newLevel; i++) {
newNode.next[i] = update[i].next[i];
update[i].next[i] = newNode;
}
}
// 调用示例
public static void main(String[] args) {
SkipList sl = new SkipList();
sl.add(3);
sl.add(6);
sl.add(1);
System.out.println(sl.search(6)); // true
System.out.println(sl.search(5)); // false
}
}
5. 核心逻辑
- 层级索引:上层链表作为快速通道,加速查找。
- 随机层数:插入时通过概率控制层数,平衡索引密度。
- 查找路径:从高层向底层逐级下降,缩小搜索范围。
6、优化与扩展
- 动态调整层数概率:根据数据量调整上升概率(如从 50% 调整为 1/4)。
- 支持重复值:在节点中增加计数器或链表存储相同值。
- 范围查询优化:记录每层的边界指针,快速定位区间。
7、总结
跳表通过多层索引 和概率平衡,在简单实现的同时达到高效操作,适合需要动态数据管理的场景(如 Redis 有序集合)。其代码实现比平衡树更简洁,且支持高效的范围查询,是替代传统复杂数据结构的优秀选择。