チューリングマシンシミュレーター&解説

チューリングマシンによる加算の実装例:シミュレーターでステップを追う

Tags: チューリングマシン, 計算理論, 計算モデル, シミュレーション, アルゴリズム, 加算

はじめに:チューリングマシンによる計算とは

前回の記事では、チューリングマシンの基本的な構成要素と動作原理について解説しました。チューリングマシンは、簡単な仕組みでありながら、現代のコンピュータが行うあらゆる計算を実行できる究極の計算モデルとして知られています。しかし、その抽象的な定義から、「実際にどのように計算を行うのか」という具体的なイメージを持つことは容易ではないかもしれません。

本記事では、チューリングマシンが具体的な計算を行う例として、「2つの自然数の加算」を取り上げます。加算という身近な演算をチューリングマシンでどのように実現するのかを解説し、その動作をシミュレーターで追体験することを通じて、チューリングマシンによる計算のメカニズムへの理解を深めることを目指します。

加算問題のチューリングマシンによる表現

まず、チューリングマシンが加算を行うために、計算対象となる数値をテープ上にどのように表現するかを決める必要があります。ここでは、最も単純な方法として「単項表現」を用います。

単項表現では、自然数 $n$ を $n$ 個の記号 1 の列で表現します。例えば、数値 3 は 111、数値 2 は 11 と表現されます。

2つの自然数 $a$ と $b$ の加算 $a+b$ を計算する場合、これらをテープ上の記号列として「1の列($a$個) # 1の列($b$個)」のような形式で与えることとします。# は2つの数値を区切るための特殊な記号です。例えば、3 + 2 を計算する場合、初期テープ内容は ...B111#11B... のようになります(Bはブランク記号)。

加算の結果は、テープ上に $a+b$ 個の 1 が並んだ状態(例: ...B11111B...)となることを目指します。

加算アルゴリズムの概要

1の列($a$個) # 1の列($b$個) という形式で与えられたテープ内容を、1の列($a+b$個) に変換するアルゴリズムを考えます。様々な方法が考えられますが、ここでは比較的簡単な方法を採用します。

  1. テープ上の最初の # 記号を探します。
  2. # 記号を 1 に書き換えます。これでテープ上には $a$個の 1#1 になった記号、$b$個の 1 が並び、合計 $a+b+1$ 個の 1 がある状態になります。
  3. 最初の 1 を探し、それをブランク B に書き換えます。
  4. ヘッドをテープの右端まで移動させます。
  5. 右端のブランク B1 に書き換えます。
  6. ステップ3に戻り、テープの左端に残っている最初の 1 を削除し、右端に 1 を追加するという操作を繰り返します。
  7. この操作を $a$ 回繰り返すと、左側の $a$ 個の 1 が全てブランクに置き換えられ、右側に $a$ 個の 1 が追加された状態になります。
  8. テープ全体を見ると、$a$個のブランク、$#$が置き換わった1、$b$個の1、そして追加された$a$個の1が並んでいます。先頭のブランク列の右にある1の列は、$1$(元の#) $+ b$個(元の右側の1)$+ a$個(追加された1)で、$a+b+1$個の1になっています。
  9. しかし、目標は $a+b$ 個の 1 です。ステップ2で #1 に置き換えたことで、余分な 1 が1つ増えています。そこで、右端に移動させる操作を $a$ 回繰り返した後、左端に残った $a$個のブランクの右にある最初の 1 をブランクに置き換える最終処理を加えます。
  10. 最後に、ヘッドをテープの左端(最初の 1 が現れる位置)まで移動させ、停止します。

このアルゴリズムをより単純化するために、少し異なるアプローチをとることもできます。例えば:

  1. テープ上の最初の # を探し、それを 1 に書き換えます。
  2. ヘッドを一番左に戻り、最初の 1 を探し、それをブランクに書き換えます。
  3. ヘッドを一番右に移動させます。
  4. 一番右の 1 を探し、それをブランクに書き換えます。
  5. ヘッドを一番左に戻り、まだ 1 が残っていればステップ2に戻ります。
  6. 全ての 1 が消えたら(つまり、元の数が0になったら)、停止します。これは加算ではありませんね。

より一般的な方法として、最初の 1 の列と二番目の 1 の列を連結する方法があります。

  1. テープ上の最初の 1 を探し、それをブランクに置き換えます。
  2. ヘッドを右に移動させ、最初のブランクにぶつかるまでスキップします。これは、元の #1 に置き換わった後、右側の 1 の列の末尾、あるいはそれより右側のブランクになります。
  3. 直前に通過した記号が # から置き換わった 1 であることを確認し、その右隣のブランクに 1 を書き込みます。
  4. ヘッドを左に移動させ、最初のブランクにぶつかるまでスキップします。これは、ステップ1で 1 を置き換えた位置のすぐ右隣になります。
  5. 左端に残っている最初の 1 を探し(ステップ1で置換した位置から右方向)、あればステップ1に戻ります。
  6. 左端に 1 が見つからなくなったら、全ての 1 が移動されたので、テープ上の # から置き換わった 1 を探し、それをブランクに置き換えます。
  7. ヘッドを左端(結果の 1 の列の始まり)まで移動させ、停止します。

今回は、より直感的な「片方の 1 の列を全て削除し、その数を記憶しておき、もう片方の列の右にその数だけ 1 を追加する」という方法に似たアプローチを考えます。ただし、チューリングマシンには「数を記憶する」ためのメモリはありません。状態を使ってこれを実現する必要がありますが、状態数が増えて複雑になります。

シンプルな方法として、前述の「#1 に置き換え、左側の 1 を一つずつ消しながら右端に追加する」アルゴリズムの変種を考えます。

アルゴリズム詳細(例:3+2 を計算する 111#11 の場合)

  1. 初期状態:テープは ...B 1 1 1 # 1 1 B... ヘッドは最初の 1 の上にいるとします。
  2. 最初の 1B に書き換え、ヘッドを右に移動し始めます。
  3. 右方向へ移動しながら、最初の B にぶつかるまで 1# をスキップします。
  4. B にぶつかったら、それを 1 に書き換えて、ヘッドを左に移動し始めます。
  5. 左方向へ移動しながら、最初の B にぶつかるまで 1# をスキップします。
  6. B にぶつかったら、その右隣の記号を確認します。もしそれが 1 であれば、ステップ2に戻り、その 1B に書き換える操作を繰り返します。
  7. ステップ6で右隣の記号が 1 ではなく # であった場合、それは左側の全ての 1 を処理し終えたことを意味します。
  8. 今、ヘッドは左端の B の右隣(元の # の位置)にいます。この #1 に書き換えます。
  9. ヘッドを一番左に戻り、最初の 1 がある位置で停止します。

このアルゴリズムでは、入力が 111#11 の場合、 * 最初の 1 (左端) を B に → B11#11 * 右端に移動し B1 に → B11#111 (ヘッドは一番右の 1 の右の B にいてそれを1にした後、左に戻り最初のBの右まで来る) * 次の 1 (左から2番目) を B に → BB1#111 * 右端に移動し B1 に → BB1#1111 * 次の 1 (左から3番目) を B に → BBB#1111 * 右端に移動し B1 に → BBB#11111 * 左端の B の右隣を見ると # なので、左側の 1 は全て移動済み。 * #1 に → BBB11111 * ヘッドを一番左に戻る → 最初の 1 (元の#) の上で停止。テープは ...BB B 1 1 1 1 1 B... となり、3+2=5 が正しく計算されています。

チューリングマシンの定義

上記のアルゴリズムを実現するためのチューリングマシンを定義します。チューリングマシンは以下の要素で構成されます。

遷移関数 δ を表形式で示します(これはあくまで一例です。アルゴリズムの解釈によって遷移は変わります)。

| 現在の状態 | 読み取った記号 | 書き込む記号 | 移動方向 | 次の状態 | 説明 | | :--------- | :------------- | :----------- | :------- | :------- | :--------------------------------------- | | $q_0$ | 1 | B | R | $q_1$ | 左端の 1 を消して右へ 이동開始 | | $q_0$ | # | 1 | R | $q_1$ | # が先頭にある場合は #1 にし右へ | | $q_0$ | B | B | S | $q_f$ | 空のテープなら停止 | | $q_1$ | 1 | 1 | R | $q_1$ | 右へスキップ (1) | | $q_1$ | # | # | R | $q_1$ | 右へスキップ (#) | | $q_1$ | B | 1 | L | $q_2$ | 右端の B1 にし左へ移動開始 | | $q_2$ | 1 | 1 | L | $q_2$ | 左へスキップ (1) | | $q_2$ | # | # | L | $q_2$ | 左へスキップ (#) | | $q_2$ | B | B | R | $q_3$ | 左端の B に到達。右隣を確認するため右へ | | $q_3$ | 1 | B | R | $q_1$ | 右隣が 1 なら、それを消して再び右へ移動 | | $q_3$ | # | 1 | S | $q_f$ | 右隣が # なら、加算終了。#1 にし停止 | | $q_3$ | B | B | S | $q_f$ | (エラーケースまたは特殊ケース) |

補足: 上記の遷移表は簡易的なものであり、実際のシミュレーター実装には詳細な状態設計が必要です。特に、入力が 1#111# のような場合、テープの端の処理や B の扱いは注意が必要です。上記の表は、あくまで「1の列 # 1の列」という標準的な入力形式を想定したアルゴリズムの一例を示しています。

シミュレーターでの動作確認

この遷移表をもとに、お手元のチューリングマシンシミュレーターで機械を構成し、動作を追ってみましょう。シミュレーターでは、以下の要素が通常表示されます。

例えば、入力 11#1 (2+1) に対してシミュレーターを実行すると、以下のようなステップが視覚的に表示されるはずです。

  1. 初期状態 $q_0$、テープは ...B 1 1 # 1 B...、ヘッドは最初の 1 の上。 遷移: $(q_0, 1) \to (q_1, B, R)$ テープ: ...B B 1 # 1 B...、ヘッドは2番目の 1 の上、状態 $q_1$。
  2. 状態 $q_1$、読み取り 1。 遷移: $(q_1, 1) \to (q_1, 1, R)$ テープ: ...B B 1 # 1 B...、ヘッドは # の上、状態 $q_1$。 ... (右方向への移動が続く)
  3. 状態 $q_1$、読み取り B (右端)。 遷移: $(q_1, B) \to (q_2, 1, L)$ テープ: ...B B 1 # 1 1...、ヘッドは右から2番目の 1 の上、状態 $q_2$。 ... (左方向への移動が続く)
  4. 状態 $q_2$、読み取り B (左端のB)。 遷移: $(q_2, B) \to (q_3, B, R)$ テープ: ...B B 1 # 1 1...、ヘッドは左から2番目の B の上、状態 $q_3$。
  5. 状態 $q_3$、読み取り 1 (左から3番目の 1)。 遷移: $(q_3, 1) \to (q_1, B, R)$ テープ: ...B B B # 1 1...、ヘッドは # の上、状態 $q_1$。 ... (再び右端への移動)
  6. 状態 $q_1$、読み取り B (右端)。 遷移: $(q_1, B) \to (q_2, 1, L)$ テープ: ...B B B # 1 1 1...、ヘッドは右から2番目の 1 の上、状態 $q_2$。 ... (左方向への移動が続く)
  7. 状態 $q_2$、読み取り B (左端のB)。 遷移: $(q_2, B) \to (q_3, B, R)$ テープ: ...B B B # 1 1 1...、ヘッドは左から2番目の B の上、状態 $q_3$。
  8. 状態 $q_3$、読み取り #。 遷移: $(q_3, #) \to (q_f, 1, S)$ テープ: ...B B B 1 1 1 1...、ヘッドは #1 に置き換わったセルの上、状態 $q_f$ (停止)。

最終的なテープの状態は ...B B B 1 1 1 B... となり、最初の 1 の列から数えて3つの 1 が並んでいます。これは 2 + 1 = 3 という正しい計算結果を示しています。

シミュレーターの各ステップでのテープ内容、ヘッド位置、状態、そして適用される遷移ルールを注意深く観察することで、抽象的な遷移関数がどのように具体的なテープ操作や計算手順に対応しているのかを視覚的に理解することができます。

まとめ

本記事では、チューリングマシンがどのようにして具体的な計算を行うのかを理解するために、「2つの自然数の加算」を例に解説しました。単項表現で数値を表現し、特定のアルゴリズムに基づいてテープ上の記号を操作することで加算が実現できることを示しました。また、そのアルゴリズムを遷移関数として定義し、シミュレーター上でその動作を追体験することの重要性を述べました。

チューリングマシンによる加算は、非常に単純な計算の例ですが、この仕組みを応用することで、より複雑な計算も原理的には実行可能です。シミュレーターを活用し、様々な入力に対してこの加算機を動かしてみたり、他の簡単な計算(例:特定の記号列をコピーする、回文を認識するなど)を行うチューリングマシンを自分で設計してみたりすることで、チューリングマシンの計算能力とその限界について、より深く探求することができるでしょう。

チューリングマシンは計算機科学の基礎であり、その動作原理を深く理解することは、コンピュータやアルゴリズムの本質を捉える上で非常に役立ちます。ぜひシミュレーターを活用して、自らの手を動かしながら学習を進めてください。