【C++】L = {aⁿbᵐaⁿ⁺ᵐ}(n, m ≥ 1)を受理するチューリングマシンの構築方法
チューリングマシン(Turing Machine)とは
チューリングマシンは、0型文法(タイプ0文法)から生成される言語の語を受理するために用いられる装置です。チューリングマシン(TM)は、セルに区切られた無限長のテープからなる数学的モデルであり、このテープに入力が与えられます。TMは入力テープを読み取るヘッドを備え、状態レジスタがマシンの現在の状態を保持します。入力記号を読み込むと、その記号は別の記号に置き換えられ、内部状態が変化し、ヘッドは左右いずれかのセルへ移動します。TMが最終状態に到達すれば入力文字列は受理され、到達できなければ拒否されます。
TMは次の7つ組(Q, X, Σ, δ, q₀, B, F)として形式的に定義できます。
- Q:状態の有限集合
- X:テープアルファベット
- Σ:入力アルファベット
- δ:遷移関数。δ : Q × X → Q × X × {左シフト, 右シフト}
- q₀:初期状態
- B:空白記号
- F:最終状態の集合
構築対象となる言語
今回の目標は、次の言語を受理するチューリングマシンTMを構築することです。
L = { aⁿ bᵐ a⁽ⁿ⁺ᵐ⁾ | n, m ≥ 1 }
受理される語の例
- abaa(n=1, m=1)
- aabaaa(n=2, m=1)
- abbaaa(n=1, m=2)
- aaabaaaa(n=3, m=1)
つまり、「n個のa + m個のb + (n+m)個のa」という並びの文字列です(n, m ≥ 1)。したがって、文字列に含まれるaの最小個数は常に3、bの最小個数は常に1となります(n=m=1のとき)。
アプローチの概要
マシンはまず、先頭のn個のaとそれに続くm個のbを受け入れます。その後、後半のaに出会うたびに、それまでに読み込んだbやaを順番に消去していきます。最終的に新しいaが現れなくなり、ヘッドが先頭の入力文字まで戻った時点で、すべての文字が正しく処理されたことになります。以下、具体的な入力文字列に対する動作を段階的に確認しましょう。
状態q₀からの遷移
- δ(q₀, a) → (q₁, x, R):状態q₀で読み取った文字が「a」の場合、状態q₁へ遷移し、その文字を「x」に書き換えて右へ移動し、文字列の次の文字を指します。
例:aabaaa → xabaaa(先頭の文字がxになり、ヘッドは次のaへ右移動) - δ(q₀, b) → (q₃, x, R):状態q₀で読み取った文字が「b」の場合、状態q₃へ遷移し、その文字を「x」に書き換えて右へ移動します。
例:babaaa… → xabaaa…(先頭の文字がxになり、ヘッドは次の文字へ右移動)
ここで「x」は、先頭の文字(処理済みである印)を表すために使用します。
状態q₁からの遷移
- δ(q₁, a) → (q₁, a, R):状態q₁で読み取った文字が「a」の場合、状態q₁にとどまり、右へ移動して次の文字を指します。
例:xaabaaa… → xaabaaa…(残りのaはそのまま右へ進む) - δ(q₁, b) → (q₂, b, R):状態q₁で読み取った文字が「b」の場合、状態q₂へ遷移し、右へ移動します。
例:xaabaaa… → xaabaaa…(残りのbもそのまま右へ進む)
状態q₂からの遷移
- δ(q₂, b) → (q₂, b, R):状態q₂で読み取った文字が「b」の場合、状態q₂にとどまり、右へ移動します。
例:xaabbbaaa… → xaabbbaaa…(残りのbはそのまま右へ進む) - δ(q₂, z) → (q₂, z, R):状態q₂で読み取った文字が「z」の場合、状態q₂にとどまり、右へ移動します。
例:xaabaazz… → xaabaazz…(残りのzはそのまま右へ進む) - δ(q₂, a) → (q₃, z, L):状態q₂で読み取った文字が「a」の場合、それを「z」に書き換え、状態q₃へ遷移して左へ移動します。
例:xaabaazz… → xaabazzz…(aをzに置き換えて左移動)
状態q₃からの遷移
- δ(q₃, z) → (q₃, z, L):状態q₃で読み取った文字が「z」の場合、状態q₃にとどまり、左へ移動します。
例:xaabzzzz… → xaabzzzz…(zはそのまま左へ進む) - δ(q₃, b) → (q₂, z, R):状態q₃で読み取った文字が「b」の場合、それを「z」に書き換え、状態q₂へ遷移して右へ移動します。これによりbがすべて置き換えられていきます。
例:xaabzzzz… → xaazzzzz…(bをzに置き換えて右移動) - δ(q₃, a) → (q₂, z, R):状態q₃で読み取った文字が「a」の場合、それを「z」に書き換え、状態q₂へ遷移して右へ移動します。これによりaがすべて置き換えられていきます。
例:xaazzzz… → xaazzzzz…(aをzに置き換えて右移動) - δ(q₃, x) → (q₄, z, R):状態q₃で読み取った文字が「x」の場合、それを「z」に書き換え、状態q₄へ遷移して右へ移動します。ここで先頭の記号に到達したことになります。
例:xzzzzzzz… → zzzzzzzz…(xをzに置き換えて右移動)
状態q₄からの遷移
- δ(q₄, z) → (q₄, z, R):状態q₄で読み取った文字が「z」の場合、状態q₄にとどまり、右へ移動します。この時点ですべての文字がzになっています。
例:zzzzzzzz… → zzzzzzzz…(すべてのzはそのまま右へ進む) - δ(q₄, $) → (qf, $, R):状態q₄で読み取る文字が残っておらず、文字列の末尾を表す「$」に到達した場合、最終状態qfへ遷移します。これは文字列が受理されたことを意味します。
例:zzzzzzzz$ → zzzzzzzz$(末尾の$では何もせず最終状態へ)
下図はこのチューリングマシンの状態遷移図です。

動作のポイント
このマシンでは、記号「x」が文字列の先頭位置の目印、「z」がすでに処理済み(対応関係が確認済み)の記号を表します。後半のaを1つ読むごとに、前半のaまたはbを1つずつzへ置き換えていくことで、後半のaの個数が前半のaとbの合計(n+m)と一致しているかを検証しています。すべての記号がzに置き換わり、余りも不足もなければ文字列は受理されます。
実行例
入力
aabaaa q0: aabaaa → q1: xabaaa → q1: xabaaa → q2: xabaaa → q3: xabzaa → q2: xazzaa q2: xazzaa → q3: xazzza → q3: xazzza → q3: xazzza → q2: xzzzzza → q2: xzzzzza q2: xzzzzza → q2: xzzzzza → q2: xzzzzza → q2: xzzzzzz → q3: xzzzzzz…….. q3: xzzzzzz → q3: xzzzzzz → q4: zzzzzzz → q4: zzzzzzz…….. → qf: zzzzzzz$
最終状態qfに到達したため、入力「aabaaa」(n=2, m=1)は正しく受理されました。
-
L = {AⁱBʲCᵏ | i < j < k、i ≥ 1} を認識するチューリングマシンの構築方法
ここでは、言語 L = {AⁱBʲCᵏ | i < j < k、i ≥ 1} を認識するチューリングマシンの構築方法を解説します。この言語は A・B・C の 3 種類の記号のみで構成される文字列を扱い、B の個数が A の個数より厳密に多く、さらに C の個数が B の個数より厳密に多いことが条件となります。w は入力文字列を表します。たとえば w = AABBBBCCCCC の場合、A が 2 個・B が 4 個・C が 5 個であり、2 < 4 < 5 という条件を満たすため、このチューリングマシンは文字列を受け入れます。解法のアプローチこの問題を解く基本的な考え方
-
L = {aⁱbʲcᵏ | i × j = k、i, j, k ≥ 1} を受理するチューリングマシンの構築方法
本記事では、言語 L = {aⁱbʲcᵏ | i × j = k、i, j, k ≥ 1} に対応するチューリングマシンの構築方法を解説します。この言語は、A・B・C の3種類の記号のみからなる文字列のうち、「A の個数(i)と B の個数(j)の積が C の個数(k)と一致する」という条件を満たすものの集合を表しています。 受理される文字列の例 例えば、w = AABBBBCCCCCCCC という入力文字列を考えてみましょう。この場合、A が2個、B が4個、C が8個含まれており、2 × 4 = 8 という関係が成立しているため、このチューリングマシンはこの文字列を受理します。 構築のアプ