Algorithm Search⌘K

Proofs: Thompson's construction

These are the human proofs of Thompson's construction, at full length.

Thompson's construction accepts exactly the language of the expression, with at most 2m states full

This proof shows: correctness.

Statement. Let rr be a regular expression of size mm and let NN be the output of THOMPSON on the postfix form of rr. Then L(N)=L(r)L(N) = L(r), NN has at most 2m2m states and 4m4m moves, and each state has at most two outgoing moves.

Setup. Each token of the postfix form is a node tt of the syntax tree of rr, and the step that handles tt pushes one fragment (st,ft)(s_t, f_t). Let V(t)V(t) be the set of states created while the tokens of the subtree of tt were handled. The tokens of a subtree form a contiguous block of the postfix form that ends with tt, so V(t)V(t) is the disjoint union of the sets of the children of tt and the states created at tt itself. We say that a move exists at time TT when it was added at or before step TT; moves are never removed. A path reads ww when the symbols on its moves, in order, spell ww.

Lemma 1. At the moment the fragment of tt is pushed: (a) every move that leaves a state of V(t)V(t) ends in V(t)V(t); (b) ftf_t has no outgoing move; (c) sts_t has no incoming move.

Proof. By induction on the syntax tree. For a leaf, lines L4 to L6 create s,fs, f with at most the move s→fs \to f, and no other move touches them. For a union, by the induction hypothesis the children satisfy (a) to (c) when they are pushed. Between the push of a child and the step of tt, the only steps are those of tokens of the other child, which create new states and add moves only out of new states and out of exits of fragments of that other subtree; these touch no state of the first child, whose sets are disjoint. At line L10 the moves added leave ss (to s1,s2∈V(t)s_1, s_2 \in V(t)) and leave f1,f2f_1, f_2 (to f∈V(t)f \in V(t)). So (a) holds, ff is new so (b) holds, and ss is new and gets no incoming move, so (c) holds. A star is the same, with the moves of line L15 all inside V(t)V(t). For a concatenation, line L12 adds only f1→s2f_1 \to s_2, inside V(t)V(t); ft=f2f_t = f_2 had no outgoing move and gets none; st=s1s_t = s_1 had no incoming move and gets none. □\square

Lemma 2. After the fragment of tt is pushed, no state of V(t)∖{ft}V(t) \setminus \{f_t\} gains an outgoing move.

Proof. A later step adds moves out of new states, which are not in V(t)V(t), and out of the exits fuf_u of fragments uu that it pops. Let uu be such a fragment with fu∈V(t)f_u \in V(t). The sets of a parse tree are nested or disjoint, and uu is pushed after tt, so V(t)⊆V(u)V(t) \subseteq V(u), and tt lies in the subtree of uu. The exit fuf_u is a state created at uu, unless uu is a concatenation, in which case fuf_u is the exit of its right child. Following right children of concatenations down from uu, we reach a node whose exit was created at that node. The state fuf_u lies in V(t)V(t) only if tt is on this chain, and then ft=fuf_t = f_u. □\square

Lemma 3. At any time after the fragment of tt is pushed, let PP be a path that starts at sts_t, ends at ftf_t, and visits ftf_t only at its end. Then every state of PP is in V(t)V(t) and every move of PP existed when the fragment was pushed.

Proof. Every move of PP leaves a state other than ftf_t. By induction along PP: the first state sts_t is in V(t)V(t); if the current state q≠ftq \ne f_t is in V(t)V(t), then by Lemma 2 its outgoing moves all existed at the push, and by Lemma 1(a) they end in V(t)V(t). □\square

Call a path as in Lemma 3 a clean path of tt. Since moves are never removed, the clean paths of tt are the same at every time after the push.

Lemma 4. For every node tt and string ww, a clean path of tt reads ww if and only if w∈L(t)w \in L(t).

Proof. By induction on the syntax tree.

  1. Leaves. The fragment of aa has only s→afs \xrightarrow{a} f, so the only clean path reads aa. The fragment of ε\varepsilon has only s→εfs \xrightarrow{\varepsilon} f, which reads ε\varepsilon. The fragment of ∅\emptyset has no move, and s≠fs \ne f, so it has no clean path.
  2. Union t=t1∣t2t = t_1 \mid t_2. A clean path PP starts with s→s1s \to s_1 or s→s2s \to s_2, say s→s1s \to s_1, since ss has no other move. From s1s_1, as long as PP has not reached f1f_1, Lemmas 2 and 1(a) for t1t_1 keep it in V(t1)V(t_1). It must reach f1f_1, because the only move into ff comes from f1f_1 or f2f_2, and f2∉V(t1)f_2 \notin V(t_1). The only move out of f1f_1 is f1→ff_1 \to f. So P=s→Q→fP = s \to Q \to f where QQ is a clean path of t1t_1. By the induction hypothesis QQ reads a string of L(t1)L(t_1), so PP reads a string of L(t)L(t). Conversely, a clean path of t1t_1 reading w∈L(t1)w \in L(t_1), with the moves s→s1s \to s_1 and f1→ff_1 \to f added, is a clean path of tt that reads ww; the same holds for t2t_2.
  3. Concatenation t=t1t2t = t_1 t_2. A clean path from s1s_1 stays in V(t1)V(t_1) until it reaches f1f_1, by the argument of case 2, and it must reach f1f_1, because f2∉V(t1)f_2 \notin V(t_1). The first visit to f1f_1 ends a clean path Q1Q_1 of t1t_1. The only move out of f1f_1 is f1→s2f_1 \to s_2, and the rest of the path is a path from s2s_2 to f2f_2 that visits f2f_2 only at its end, that is, a clean path Q2Q_2 of t2t_2. It cannot return to V(t1)V(t_1), since by Lemma 1(c) s1s_1 has no incoming moves and the other states of V(t1)V(t_1) are entered only from V(t1)V(t_1). So PP reads xyxy with x∈L(t1)x \in L(t_1) and y∈L(t2)y \in L(t_2). Conversely, two such clean paths joined by f1→s2f_1 \to s_2 form a clean path of tt.
  4. Star t=t1∗t = t_1^*. A clean path either is the single move s→fs \to f, which reads ε∈L(t)\varepsilon \in L(t), or starts with s→s1s \to s_1. In the second case, cut the path at each visit to f1f_1. Each piece from s1s_1 to the next visit of f1f_1 is a clean path of t1t_1 by the argument of case 2, and after each visit the path takes f1→s1f_1 \to s_1 and starts a new piece, or takes f1→ff_1 \to f and ends. So PP reads x1x2⋯xkx_1 x_2 \cdots x_k with k≥1k \ge 1 and each xi∈L(t1)x_i \in L(t_1), which is in L(t1)k⊆L(t)L(t_1)^k \subseteq L(t). Conversely, ε\varepsilon is read by s→fs \to f, and for k≥1k \ge 1 and xi∈L(t1)x_i \in L(t_1), the clean paths of t1t_1 for the xix_i, joined by f1→s1f_1 \to s_1 and framed by s→s1s \to s_1 and f1→ff_1 \to f, form a clean path of tt. □\square

Theorem. L(N)=L(r)L(N) = L(r).

Proof. Line L16 returns the fragment of the root t=rt = r, with start srs_r and the one accepting state frf_r, which has no outgoing move by Lemma 1(b). A string ww is in L(N)L(N) if and only if some path from srs_r to frf_r reads ww. Such a path ends at its first visit to frf_r, because frf_r has no outgoing move, so it is a clean path of rr. By Lemma 4 this holds if and only if w∈L(r)w \in L(r). □\square

Size. Lines L4, L9 and L14 create two states for each leaf, union and star, and line L12 creates none, so ∣Q∣≤2m|Q| \le 2m. A leaf adds at most one move, a union four, a concatenation one and a star four, so there are at most 4m4m moves. An entry created at line L9 or L14 gets exactly two moves at its creation; an entry created at line L4 gets at most one. An exit gets outgoing moves only when a fragment with that exit is popped. After the pop, the new fragment's exit is a new state (lines L10 and L15) or f2f_2 (line L12, when the popped exit was f1f_1), so the popped exit is never again the exit of a fragment on the stack. Hence each exit gets moves once, one or two of them, and every state has at most two outgoing moves. ■\blacksquare

Back to Thompson's construction