Algorithm Search⌘K

Proofs: Position heap

These are the human proofs of Position heap, at full length.

The position heap keeps its invariant, and SEARCH reports exactly the occurrences of the pattern full

This proof shows: correctness, complexity.

We use the pseudocode, the notation and the invariant (H1) to (H3) of the page. Write Ti=T[i…n−1]T_i = T[i \ldots n - 1] for the suffix at ii, and xix_i for the node with pos(xi)=ipos(x_i) = i.

Lemma 1 (PREPEND). If the heap of TT satisfies (H1) to (H3), then after PREPEND(c) the heap satisfies them for T′=c⋅TT' = c \cdot T.

Proof. Each old node keeps its string and its suffix, which now starts at a position one larger; so (H2) still holds for the old nodes, and the old positions become 1,…,n1, \ldots, n. The walk of line L2 follows the symbols of T0′=T′T'_0 = T' while the nodes exist. It stops at some depth k<n+1k < n + 1: if it reached depth n+1n + 1, the node of T′T' itself would exist, and its holder xx would satisfy ∣str(x)∣=n+1>∣Tpos(x)′∣\lvert str(x) \rvert = n + 1 > \lvert T'_{pos(x)} \rvert, against (H2). So T′[k]T'[k] exists, and line L3 adds the node yy with str(y)=T′[0…k]str(y) = T'[0 \ldots k], a prefix of T0′T'_0, and pos(y)=0pos(y) = 0. This gives (H1) and (H2) for T′T'. The new node is a leaf, and its parent is the root or an old node with position at least 1, so (H3) holds. □\square

By induction, the heap built by nn calls of PREPEND satisfies (H1) to (H3). The trie has distinct symbols at the children of each node, so the walk along a string is unique, and for each string ss there is at most one node xx with str(x)=sstr(x) = s; if such a node exists, so does the node of every prefix of ss, its ancestor.

Lemma 2 (where an occurrence is). Let PP occur at ii. Then either str(xi)str(x_i) is a proper prefix of PP, or PP is a prefix of str(xi)str(x_i).

Proof. By (H2) str(xi)str(x_i) is a prefix of TiT_i, and PP is a prefix of TiT_i since it occurs at ii. Two prefixes of the same string are comparable under the prefix order. □\square

SEARCH is correct. Let $u_0 = $ root, u1,…,uku_1, \ldots, u_k be the nodes of the walk of line L4, with str(uj)=P[0…j−1]str(u_j) = P[0 \ldots j - 1]; the walk stops at k=mk = m or at a node with no child by P[k]P[k].

Every reported position is an occurrence. Line L5 reports a position only after checking it. Line L6 runs only if k=mk = m; then str(um)=Pstr(u_m) = P, and every node yy of the subtree of umu_m has PP as a prefix of str(y)str(y), which is a prefix of Tpos(y)T_{pos(y)} by (H2), so PP occurs at pos(y)pos(y).

Every occurrence is reported. Let PP occur at ii. By Lemma 2 there are two cases. If str(xi)str(x_i) is a proper prefix of PP, of length j<mj < m, then the node of P[0…j−1]P[0 \ldots j - 1] is xix_i, and since its ancestors exist the walk reaches it: xi=ujx_i = u_j with j≤kj \le k and j<mj < m. Line L5 checks ii and reports it. If PP is a prefix of str(xi)str(x_i), then the node of PP exists, as an ancestor of xix_i or xix_i itself, so the walk reaches depth k=mk = m, umu_m is the node of PP, and xix_i is in its subtree; line L6 reports ii.

No position is reported twice. The nodes checked at line L5 have depth less than mm and the nodes of line L6 have depth at least mm, and each node holds one position by (H1).

Complexity. The walk takes at most mm steps. Line L5 makes at most m−1m - 1 checks of at most mm symbols each. Line L6 visits the subtree of umu_m, and each of its nodes is an occurrence, so with zz occurrences it takes O(z+1)O(z + 1) time. The total is O(m2+z)O(m^2 + z). By (H1) the heap has n+1n + 1 nodes, so it takes O(n)O(n) words. ■\blacksquare

Back to Position heap