Unverified Commit 48bf0f44 authored by Zhikai Zeng's avatar Zhikai Zeng Committed by GitHub
Browse files

Update skiplist.md

check and fix typo
parent 83360cad
Loading
Loading
Loading
Loading
+13 −9
Original line number Diff line number Diff line
@@ -2,7 +2,7 @@

跳表(Skip List)是由 William Pugh 发明的一种查找数据结构结构,支持对数据的快速查找,插入和删除。

跳表的期望空间复杂度为 $O(n)$ ,跳表的查询,插入和删除操作的期望复杂度都为 $O(\log n)$ 。
跳表的期望空间复杂度为 $O(n)$ ,跳表的查询,插入和删除操作的期望时间复杂度都为 $O(\log n)$ 。

## 基本思想

@@ -12,7 +12,7 @@

一个有序链表的查找操作,就是从头部开始逐个比较,直到当前节点的值大于或者等于目标节点的值。很明显,这个操作的复杂度是 $O(n)$ 。

跳表在有序链表的基础上,引入了 **分层** 的概念。首先,跳表的每一层都是一个有序链表,特别地,最底层是一个初始的有序链表。每个位于第 $i$ 层的节点有 $p$ 的概率出现在第 $i+1$ 层, $p$ 为常数。可以计算出,跳表的期望层数为 $\log_{\frac{1}{p}}n$ 。
跳表在有序链表的基础上,引入了 **分层** 的概念。首先,跳表的每一层都是一个有序链表,特别地,最底层是初始的有序链表。每个位于第 $i$ 层的节点有 $p$ 的概率出现在第 $i+1$ 层, $p$ 为常数。可以计算出,跳表的期望层数为 $\log_{\frac{1}{p}}n$ 。

在跳表中查找,就是从最高层开始,水平地逐个比较,直至当前节点大于等于目标节点。若是当前节点等于目标节点,则成功查找;若是大于目标节点或者是达到链表的末尾,就退回上一个元素后下降到下一层,直到找到目标节点或者达到最底层的末尾。这样一来,查找的过程中会跳过一些没有必要的比较,所以相比于有序链表的查询,跳表的查询更快。可以证明,跳表查询的平均复杂度为 $O(\log n)$ 。

@@ -20,7 +20,11 @@

### 空间复杂度

对于一个节点节点而言,节点的最高层数为 $i$ 的概率为 $p^{i-1}(1 - p)$ 。所以,跳表的期望层数为 $\sum_{i=1}^{\infin} ip^{i - 1}(1-p) = \frac{1}{1 - p}$ 。又因为 $p$ 为常数,所以跳表的 **平均空间复杂度** 为 $O(n)$ 。
对于一个节点节点而言,节点的最高层数为 $i$ 的概率为 $p^{i-1}(1 - p)$ 。所以,跳表的期望层数为 
$$
\sum_{i=1}^{\infin} ip^{i - 1}(1-p) = \frac{1}{1 - p},
$$
且因为 $p$ 为常数,所以跳表的 **期望空间复杂度** 为 $O(n)$ 。

在最坏的情况下,每一层有序链表等于初始有序链表,即跳表的 **最差空间复杂度** 为 $O(n \log n)$ 。

@@ -28,7 +32,7 @@

记 n 个节点的跳表的期望层数为 $L(n)$ 。

从后向前分析查找路径,这个过程可以分为从底层爬到第 $L(n)$ 层和后续操作两个部分。在分析时,假设一个节点的具体信息在它被访问之前是未知的。
从后向前分析查找路径,这个过程可以分为从底层爬到第 $L(n)$ 层和后续操作两个部分。在分析时,假设一个节点的具体信息在它被访问之前是未知的。

假设当前我们处于一个第 $i$ 层的节点 $x$ ,我们并不知道 $x$ 的最大层数和 $x$ 左侧节点的最大层数,只知道 $x$ 的最大层数至少为 $i$ 。如果 $x$ 的最大层数大于 $i$ ,那么下一步应该是向上走,这种情况的概率为 $p$ ;如果 $x$ 的最大层数等于 $i$ ,那么下一步应该是向左走,这种情况概率为 $1-p$ 。

@@ -43,15 +47,15 @@ $$

解得 $C(i)=\frac{i}{p}$ 。

可以得出:在长度为 $n$ 的跳表中,从最底层爬到第 $L(n)$ 层的期望步数存在上界 $\frac{L(n) - 1}{p}$ 。
由此可以得出:在长度为 $n$ 的跳表中,从最底层爬到第 $L(n)$ 层的期望步数存在上界 $\frac{L(n) - 1}{p}$ 。

现在只需要分析爬到第 $L(n)$ 层后还要再走多少步。易得,到了第 $L(n)$ 层后,向左走的步数不会超过第 $L(n)$ 层及更高层的节点数总和,而这个总和的期望为 $\frac{1}{p}$ 。所以到了第 $L(n)$ 层后向左走的步数存在上界 $\frac{1}{p}$ 。同理,到了第 $L(n)$ 层后向上走的步数存在上界 $\frac{1}{p}$ 。
现在只需要分析爬到第 $L(n)$ 层后还要再走多少步。易得,到了第 $L(n)$ 层后,向左走的步数不会超过第 $L(n)$ 层及更高层的节点数总和,而这个总和的期望为 $\frac{1}{p}$ 。所以到了第 $L(n)$ 层后向左走的期望步数存在上界 $\frac{1}{p}$ 。同理,到了第 $L(n)$ 层后向上走的期望步数存在上界 $\frac{1}{p}$ 。

所以,跳表查询的期望查找步数为 $\frac{L(n) - 1}{p} + \frac{2}{p}$ ,又因为 $L(n)=\log_{\frac{1}{p}}n$ ,所以跳表查询的 **期望复杂度** 为 $O(\log n)$ 。
所以,跳表查询的期望查找步数为 $\frac{L(n) - 1}{p} + \frac{2}{p}$ ,又因为 $L(n)=\log_{\frac{1}{p}}n$ ,所以跳表查询的 **期望时间复杂度** 为 $O(\log n)$ 。

在最坏的情况下,每一层有序链表等于初始有序链表,查找过程相当于对最高层的有序链表进行查询,即跳表查询操作的 **最差时间复杂度** 为 $O(n)$ 。

插入操作和删除操作就是进行一遍查询的过程,记录途中需要修改的节点,最后完成修改。易得每一层至多只需要修改一个节点,又因为 i 跳表期望高度为 $\log_{\frac{1}{p}}n$ ,所以插入和修改的 **期望复杂度** 也为 $O(\log n)$ 。
插入操作和删除操作就是进行一遍查询的过程,途中记录需要修改的节点,最后完成修改。易得每一层至多只需要修改一个节点,又因为跳表期望层数为 $\log_{\frac{1}{p}}n$ ,所以插入和修改的 **期望时间复杂度** 也为 $O(\log n)$ 。

## 具体实现