Algorithm Search⌘K

Proofs: Skip list

These are the human proofs of Skip list, at full length.

The expected cost of a skip list search is O(log n) full

This proof shows: expected-time, space.

Fix a set of n≥1n \ge 1 keys and a key kk. The levels ℓ(x)\ell(x) of the nodes are independent, with Pr⁡[ℓ(x)≥i]=pi−1\Pr[\ell(x) \ge i] = p^{i-1} for 1≤i≤M1 \le i \le M and a constant 0<p<10 < p < 1. Ignore the cap MM first; it only makes levels smaller. The cost of a search is the number of moves: the moves to the right in line L3 and the moves down in line L2. Insert and delete do one search and then O(ℓ(z))O(\ell(z)) or O(ℓ(x))O(\ell(x)) more work, and E[ℓ(x)]=1/(1−p)\mathbb{E}[\ell(x)] = 1/(1-p), so it is enough to bound the search.

Space. The node xx has ℓ(x)\ell(x) pointers, and E[ℓ(x)]=∑i≥1Pr⁡[ℓ(x)≥i]=∑i≥1pi−1=1/(1−p)\mathbb{E}[\ell(x)] = \sum_{i \ge 1} \Pr[\ell(x) \ge i] = \sum_{i \ge 1} p^{i-1} = 1/(1-p). So the expected number of pointers is n/(1−p)n/(1-p).

Number of levels. Let NiN_i be the number of nodes with level ≥i\ge i, so E[Ni]=npi−1\mathbb{E}[N_i] = n p^{i-1}. The top level S.levelS.level is at most L+∑i>L[Ni≥1]L + \sum_{i > L} [N_i \ge 1] for each LL, and [Ni≥1]≤Ni[N_i \ge 1] \le N_i.

The backward walk. Reverse the search path: start at the node where the search ends at level 1, and walk back to the header at level S.levelS.level. At a node yy on level ii of the path, the previous step of the search was either a move down into level ii at yy, which happens exactly when ℓ(y)>i\ell(y) > i or yy is the header, or a move right into yy along level ii. For yy a node, the backward walk at yy has seen only the fact that ℓ(y)≥i\ell(y) \ge i (and facts about other nodes), so, by independence, Pr⁡[ℓ(y)>i∣the walk so far]=p\Pr[\ell(y) > i \mid \text{the walk so far}] = p. So each backward step is a move up with probability at least pp (exactly pp at a node, 1 at the header), independent of the past.

Climbing to level LL. Let L=⌈log⁡1/pn⌉L = \lceil \log_{1/p} n \rceil, so pL≤1/np^{L} \le 1/n and npL−1≤1/pn p^{L-1} \le 1/p. The backward steps taken while the walk is below level LL are dominated by the number of trials needed to get L−1L - 1 successes, each trial a success with probability pp. This number has expectation (L−1)/p(L - 1)/p.

The levels from LL up. On level i≥Li \ge L, the moves to the right are at most NiN_i, since each visits a different node of level ≥i\ge i. The moves down from levels above LL are at most the number of levels above LL, which is at most ∑i>LNi\sum_{i > L} N_i. So the expected number of steps at levels ≥L\ge L is at most

∑i≥LE[Ni]+∑i>LE[Ni]≤2∑i≥Lnpi−1=2npL−11−p≤2p(1−p).\sum_{i \ge L} \mathbb{E}[N_i] + \sum_{i > L} \mathbb{E}[N_i] \le 2 \sum_{i \ge L} n p^{i-1} = \frac{2 n p^{L-1}}{1-p} \le \frac{2}{p(1-p)}.

Total. The expected cost of a search is at most

L−1p+2p(1−p)≤log⁡1/pnp+2p(1−p)=O(log⁡n)\frac{L - 1}{p} + \frac{2}{p(1-p)} \le \frac{\log_{1/p} n}{p} + \frac{2}{p(1-p)} = O(\log n)

for constant pp. For p=1/2p = 1/2 this is 2lg⁡n+82 \lg n + 8. The cap MM can only lower the levels; if M≥LM \ge L, the bound on the climb holds unchanged, and the steps at levels ≥L\ge L are bounded as before. ■\blacksquare

Remarks. The bound holds for each set of keys and each query, because the only randomness is in the levels. With p=1/4p = 1/4 the bound is 4log⁡4n+32/3=2lg⁡n+O(1)4 \log_4 n + 32/3 = 2 \lg n + O(1), the same leading term as p=1/2p = 1/2, with 4/34/3 instead of 22 pointers per node on average.

Skip list operations keep the level lists sorted and return the right answers full

This proof shows: correctness, invariant, termination.

Write Li\mathcal{L}_i for the list obtained by following forward[i]forward[i] from the header, and N≥iN_{\ge i} for the set of nodes with ℓ(x)≥i\ell(x) \ge i. The invariant is (S1) L1\mathcal{L}_1 is strictly increasing and contains each node; (S2) for each ii, Li\mathcal{L}_i is N≥iN_{\ge i} in increasing key order; (S3) S.level=max⁡(1,max⁡xℓ(x))S.level = \max(1, \max_x \ell(x)) and $S.header.forward[i] = $ NIL for i>S.leveli > S.level. (S1) is (S2) for i=1i = 1. Treat the header as a node with key −∞-\infty and level MM.

Lemma 1 (search). Assume (S1) to (S3). When the loop of line L2 finishes level ii (after line L3 for that ii), x=update[i]x = update[i] is the last element of {header}∪{y∈N≥i:y.key<k}\{\text{header}\} \cup \{y \in N_{\ge i} : y.key < k\} in key order.

Proof. By downward induction on ii. Before level S.levelS.level, xx is the header, which is in every list. Before level i<S.leveli < S.level, xx is the node of the claim for i+1i + 1, which is in N≥i+1⊆N≥iN_{\ge i+1} \subseteq N_{\ge i} (or is the header), so it is on Li\mathcal{L}_i by (S2). Line L3 moves right along Li\mathcal{L}_i while the next key is less than kk. Since Li\mathcal{L}_i is sorted, it stops at the last node of Li\mathcal{L}_i with key <k< k, provided that xx starts at or before it, which holds because x.key<kx.key < k. The walk ends because the list is finite. □\square

Search. By Lemma 1 for i=1i = 1 and (S1), update[1]update[1] is the last node with key <k< k, so x=update[1].forward[1]x = update[1].forward[1] in line L4 is the first node with key ≥k\ge k, or NIL. Key kk is present if and only if this node exists and has key kk, which is the test of line L5. The search writes no field of the structure.

Insert. If kk is present, line L7 changes one value and nothing else, so the invariant holds. Otherwise let zz be the new node with level λ\lambda. For i≤min⁡(λ,S.level)i \le \min(\lambda, S.level), update[i]update[i] is the last node of Li\mathcal{L}_i with key <k< k by Lemma 1; for S.level<i≤λS.level < i \le \lambda, line L9 sets update[i]update[i] to the header, which is the last element of Li=(header)\mathcal{L}_i = (\text{header}) since that list is empty by (S3). Lines L12 and L13 put zz between update[i]update[i] and its old successor in Li\mathcal{L}_i, whose key is >k> k (it is ≥k\ge k by the choice of update[i]update[i], and kk is absent). So Li\mathcal{L}_i stays sorted and becomes N≥i∪{z}N_{\ge i} \cup \{z\} for i≤λi \le \lambda. For i>λi > \lambda the list does not change, and z∉N≥iz \notin N_{\ge i}. So (S2) holds. Line L9 raises S.levelS.level to λ\lambda when needed, so (S3) holds.

Delete. If kk is absent, line L15 returns and nothing changes. Otherwise xx is the node with key kk. For i≤ℓ(x)i \le \ell(x), x∈N≥ix \in N_{\ge i} is on Li\mathcal{L}_i, and its predecessor there is the last node with key <k< k, that is update[i]update[i] by Lemma 1, because ℓ(x)≤S.level\ell(x) \le S.level. Line L17 unlinks xx from Li\mathcal{L}_i. The lists for i>ℓ(x)i > \ell(x) do not hold xx. So each Li\mathcal{L}_i becomes N≥i∖{x}N_{\ge i} \setminus \{x\} in order, and (S2) holds. Line L18 lowers S.levelS.level while the top list is empty, which restores (S3).

Termination. Each walk of line L3 moves along a finite list, the loops of lines L2, L11 and L16 have at most MM rounds, and the loop of line L18 lowers S.levelS.level, which is at least 1. ■\blacksquare

Back to Skip list