プログラミング入門

再帰もスタックも使わない!Morris Pre Order Traversalをゼロから理解しよう

「二分木の探索って、再帰かスタックを使うのが当たり前じゃないの?」そう思っている方、多いんじゃないでしょうか。

実は、再帰もスタックも一切使わず、さらに追加メモリO(1)で二分木を走査するアルゴリズムがあるんです。それが今回紹介する Morris Pre Order Traversal(モリス前順走査) です。🌳

アルゴリズムの面接対策としても話題になっているテクニックなので、ぜひ一緒に理解していきましょう!

そもそもPreorder Traversal(前順走査)とは?

binary tree algorithm
binary tree algorithm / Photo by Thomas P via Pexels

二分木の走査には大きく3種類あります。

  • Preorder(前順):ルート → 左 → 右
  • Inorder(中順):左 → ルート → 右
  • Postorder(後順):左 → 右 → ルート

通常の前順走査は再帰やスタックで簡単に書けますが、それだとO(n)の追加メモリが必要になります。Morris法はこれをO(1)に抑えるのがポイントです。

Morris Pre Order Traversalの仕組み 🔍

イメージとしては、「木の中に一時的なリンクを張って、戻り道を作る」という感じです。

具体的には、現在のノードの左部分木の右端(inorder predecessor)を探して、そこに一時リンク(スレッド)を張ります。これによって、スタックなしで親ノードに戻れるようになります。

ざっくりとした流れをまとめるとこんな感じです。

  1. 現在ノード(curr)がNoneになるまでループ
  2. 左の子がなければ → 値を記録して右へ進む
  3. 左の子があれば → 左部分木の右端ノード(predecessor)を探す
  4. predecessorの右がNoneなら → 値を記録し、一時リンクを張って左へ進む
  5. predecessorの右がcurrなら → リンクを外して右へ進む

Pythonで実装してみよう 🐍

ポイントをコメント付きで書いてみます。

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

def morris_preorder(root):
    result = []
    curr = root

    while curr:
        if curr.left is None:
            # 左の子がなければ値を記録して右へ
            result.append(curr.val)
            curr = curr.right
        else:
            # 左部分木の右端(predecessor)を探す
            predecessor = curr.left
            while predecessor.right and predecessor.right != curr:
                predecessor = predecessor.right

            if predecessor.right is None:
                # 一時リンクを張って左へ進む(ここで値を記録!)
                result.append(curr.val)  # Preorderなので先に記録
                predecessor.right = curr
                curr = curr.left
            else:
                # 一時リンクを外して右へ進む
                predecessor.right = None
                curr = curr.right

    return result

# テスト用の木を作成
#       1
#      / \
#     2   3
#    / \
#   4   5
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)

print(morris_preorder(root))

ここが重要です。Inorder版との違いは値を記録するタイミングだけです。Preorderでは「左の子がない」か「一時リンクを新たに張る直前」に値を記録します。この1箇所の違いを意識するだけで、Inorderとの使い分けがぐっと楽になりますよ。

まとめ 📝


Morris Pre Order Traversalのポイントをおさらいします。

  • ✅ 再帰なし・スタックなし・追加メモリO(1)で前順走査できる
  • ✅ 一時リンク(スレッド)を使って「戻り道」を作るのがコアアイデア
  • ✅ Inorder版との違いは「値を記録するタイミング」だけ

最初は難しく見えますが、仕組みを一度つかめると「なるほど!」となる美しいアルゴリズムです。面接でさらっと説明できると、かなり印象アップしますよ😊

ぜひ自分の手でコードを動かして、木の走査順を確かめてみてください!

Morris Pre Order Traversal 実行結果
Morris Pre Order Traversal 実行結果

📚 関連商品・おすすめ書籍

スッキリわかるPython入門 第2版 (スッキリわかる入門シリーズ)

もしも

スッキリわかるPython入門 第2版 (スッキリわかる入門シリーズ)

初心者に定番のPython入門書

Amazonで見る

実践Claude Code入門―現場で活用するためのAIコーディングの思考法

もしも

実践Claude Code入門―現場で活用するためのAIコーディングの思考法

AIコーディングの現場活用法を学ぶ一冊

Amazonで見る

Python Web開発実践入門 ―― FastAPIによるWebAPI開発と非同期処理

もしも

Python Web開発実践入門 ―― FastAPIによるWebAPI開発と非同期処理

FastAPIでWebAPI開発を実践的に学ぶ

Amazonで見る

※本記事にはアフィリエイトリンクが含まれます。

ABOUT ME
やまちゃん
これまで学生と社会人を合わせて5000人以上にプログラミング学習を指導。 ゼロからイチをわかりやすく解説する専門家として活動しており、本業ではArduinoを用いたIoT開発とロボットプログラミングが専門。 Pythonを用いたアプリ開発、ウェブアプリケーションの開発で業務の効率化をサポートしています。

COMMENT

メールアドレスが公開されることはありません。 が付いている欄は必須項目です