HOWTO · Python

Python で木データ構造を実装する

この記事では、Python で木データ構造を実装する方法を説明します。

このページの内容

木構造では、辺でつながったノードとしてデータを整理します。各辺は親ノードと子ノードを結び、子ノードを持たないノードは葉と呼ばれます。木には二分木、三分木、二分探索木、AVL木などがあります。二分木では、各ノードが持てる子は最大2つです。このガイドでは、独自の Node クラスで小さな二分木を作り、深さ優先の3種類の順序で走査する方法と、ライブラリ anytree を使う方法を紹介します。

根が10で、その子が34と89、34の下に45と50がある二分木

Pythonでツリー構造をゼロから実装する

シンプルな二分木のノードには、値を格納する場所と、2つの子ノードへの参照が必要です。以下の Node クラスでは、値を data に格納します。left と right は最初は None で、後から子ノードを参照できます。これらのオブジェクトをつなぐと木構造になります。

class Node:
    def __init__(self, data):
        self.data = data
        self.left = None
        self.right = None


root = Node(10)
root.left = Node(34)
root.right = Node(89)
root.left.left = Node(45)
root.left.right = Node(50)

ここでは、root が 34 と 89 の親です。ノード 45 と 50 は 34 の子で、89、45、50 は葉です。各ノードが持つ子への参照は最大2つなので、この構造は二分木です。ほかの種類の木では、left と right のような固定属性ではなく、子ノードのコレクションを使うなど、別の表現が必要になる場合があります。

Pythonで二分木を走査する

走査では各ノードを1回ずつ処理します。再帰呼び出しに対してどの位置でノードを訪問するかによって、処理順序が決まります。先行順巡回では、現在のノードを左部分木、右部分木より先に訪問します。中間順巡回では、左部分木、現在のノード、右部分木の順に訪問します。後行順巡回では、両方の部分木を訪問してから現在のノードを訪問します。以下の完全な例では、同じ木を構築し、それぞれの順序で表示します。

class Node:
    def __init__(self, data):
        self.data = data
        self.left = None
        self.right = None


def preorder(node):
    if node:
        print(node.data)
        preorder(node.left)
        preorder(node.right)


def inorder(node):
    if node:
        inorder(node.left)
        print(node.data)
        inorder(node.right)


def postorder(node):
    if node:
        postorder(node.left)
        postorder(node.right)
        print(node.data)


root = Node(10)
root.left = Node(34)
root.right = Node(89)
root.left.left = Node(45)
root.left.right = Node(50)

print("Pre-order:")
preorder(root)
print("In-order:")
inorder(root)
print("Post-order:")
postorder(root)

再帰呼び出しでは、最初のセクションで作成したものと同じ left および right の参照をたどります。別の木を走査する場合は、ノードの構築部分を置き換え、走査関数はそのまま使えます。どの位置でノードを訪問するかによって、値の並び順が決まります。

Pythonライブラリを使ってツリーを実装する

この小さな二分木の例よりノード数が多い木や、より多くの機能が必要な場合は、anytree を使うとノード間の関係を管理し、木を描画できます。プロジェクトの環境に pip install anytree でインストールしてください。このAPIでは、Node の位置引数にノード名を指定します。子を親に接続するには、キーワード引数として parent=root を渡します。RenderTree は階層をたどり、各レベルの表示に使う接頭辞を返します。

上記と同じ形の木を作るには、まず root = Node("10") とし、続けて Node("34", parent=root) と Node("89", parent=root) を作ります。Node("45", parent=left_child) と Node("50", parent=left_child) を作ると、どちらの葉も 34 の下に配置できます。その後、for pre, fill, node in RenderTree(root) で反復処理し、pre + node.name を表示します。この例はライブラリAPIの説明であり、実行を示す証拠は含まれていません。