hirohirohirohirosのブログ

地方国立大学に通う情報系学部4年

トポロジカルソートをPythonで実装して関数化する

hirohirohirohiros.hatenablog.com

概要

 トポロジカルソートは,有向無閉路グラフ(DAG)の各ノードについて,どのノードも出力先のノードより後ろに並ぶように並べるソートです.ようするに,ノードと一列に並べて,辺の向きが右向きのみになるように並び替えるソートのことです.(または左向きのみ)
 トポロジカルソートに掛けるグラフが,有向閉路グラフだったときこのソートは失敗するので,有向グラフが閉路かどうかの判定にも使えます.(こっちの用途の方が多そう?)

アルゴリズムの内容

 幅優先探索を使います.
 今回は,ノードを一列に並べて,辺の向きが右向きのみになるようにソートする事をイメージします.(a→b→c→...というイメージ)
 a→bという有向グラフがあったとき,aに一つ出力辺があり,bに一つ入力辺があると言うようにします.
 まず,各ノードについて,入力辺の本数を数えます.これは,a→bを表す入力a bが与えられたら,list[b] += 1とすれば良いです.これを全てのノードについて行い,入力辺の本数を要素としたリストを用意します.
 このリストについて,list[i] = 0であるようなノードiは入力辺が0ということです.つまり,そのノードは,x→iとなるようなノードxと辺が存在しないということなので,一番左に並べるのが適切であると分かります.よって,ソート済みのノードを集めたリストansに,list[i] = 0となるようなノードを全て入れます.(この順番は適当で良いです.トポロジカルソートはノードの順序さえ守られていれば良いので.)さらにノードi達について,それらをキューに入れます.
 つぎに,キューから一つノードiを取り出します.iの位置が確定したということで,iの出力辺先のノードjについてjの入力辺の数を-1します.そして,list[j] = 0となったら,入力辺が無くなったということでansに入れ,キューにも入れます.
 これをキューが空になるまで繰り返します.
 閉路があった時どうなるか考えます.a→b⇆cというような閉路があった時,まず入力辺0のaが取り出されます.そして,bの入力辺が-1されます.しかしbの入力辺の本数は0になりません.よってbはキューに入りません.そしてキューが空になるので処理が終了します.つまり,ansの中身はaのみになり,閉路分の2ノード足りません.
 このように閉路があるとansに入れられる事が無いので,閉路の数だけノード数が少なくなります.つまり,ソート済みリストのノードの数が,元のノードの数より少なかったら閉路,そうでなかったら無閉路と判定できます.
 計算量はO(V+E)となります.

pythonコード

 以上を実装しました.閉路の時-1を返すようにしてますが,こういう場合Noneを返す方が適切なんですかね……?(成功時返すのがリストでなく数値の時,-1と区別がつきにくいため)

from collections import deque
def topological_sort(node, input_edge):
    """トポロジカルソート

    有向グラフの順序を守るようにソートする
    閉路があるか判定も出来る
    計算量: O(E+V)

    Args:
        node (list): edge[i] = [a, b,...] iからa,b,...に辺が伸びている
        input_edge (list): input_edge[i] = iの入力辺の本数

    Returns:
        list or -1:閉路が存在しないとき
                      ソート済みのリスト
                   閉路が存在する時
                      -1   
    """
    N = len(input_edge)
    ans = [i for i in range(N) if input_edge[i] == 0]
    que = deque(ans)
    while que:
        q = que.popleft()
        for e in node[q]:
            input_edge[e] -= 1
            if input_edge[e] == 0:
                que.append(e)
                ans.append(e)
    if len(ans) == N:
        return ans
    return -1