コンピュータサイエンスにおいて、有向グラフのトポロジカルソートまたはトポロジカル順序付けとは、頂点uから頂点vへのすべての有向エッジ(u,v)について、 uがvより前に来るような、頂点の線形順序付けのことです。例えば、グラフの頂点は実行すべきタスクを表し、エッジは、あるタスクが別のタスクより先に実行されなければならないという制約を表す場合があります。この場合、トポロジカル順序付けは、タスクの有効な順序付けに過ぎません。
正確には、トポロジカルソートとは、各ノードv が、そのすべての依存関係が訪問された後にのみ訪問されるグラフの走査のことです。トポロジカル順序付けは、グラフに有向サイクルがない場合、つまり有向非巡回グラフ(DAG) である場合に限り可能です。任意の DAG には少なくとも 1 つのトポロジカル順序付けがあり、 それを構築するための線形時間アルゴリズムが存在します。トポロジカルソートは、フィードバックアークセットなどのランキング問題をはじめ、多くの用途があります。トポロジカルソートは、 DAG に連結されていないコンポーネントがある場合にも可能です。

トポロジカルソートの典型的な応用は、依存関係に基づいて一連のジョブまたはタスクをスケジュールすることです。ジョブは頂点で表され、ジョブ x がジョブ y を開始する前に完了する必要がある場合(たとえば、洗濯をする場合、洗濯物を乾燥機に入れる前に洗濯機が終了する必要があります) には、x から y へのエッジがあります。そして、トポロジカルソートは、ジョブを実行する順序を与えます。トポロジカルソートアルゴリズムの密接に関連する応用は、1960 年代初頭にプロジェクト管理のスケジューリングのためのPERT手法の文脈で初めて研究されました。[ 1 ]この応用では、グラフの頂点はプロジェクトのマイルストーンを表し、エッジは、あるマイルストーンと別のマイルストーンの間で実行する必要があるタスクを表します。トポロジカルソートは、プロジェクトのクリティカルパス(プロジェクト全体のスケジュールの長さを制御する一連のマイルストーンとタスク) を見つけるための線形時間アルゴリズムの基礎を形成します。
コンピュータサイエンスにおいて、この種の応用例は、命令スケジューリング、スプレッドシートで数式値を再計算する際の数式セル評価順序、論理合成、メイクファイルで実行するコンパイルタスクの順序決定、データシリアライゼーション、リンカにおけるシンボル依存関係の解決などに見られます。また、データベースにおいて外部キーを持つテーブルをロードする順序を決定する際にも使用されます。
トポロジカルソートの通常のアルゴリズムは、漸近的にノード数とエッジ数の合計に対して線形な実行時間を持ちます。
これらのアルゴリズムの1つは、カーン(1962)によって最初に記述されたもので、最終的なトポロジカルソートと同じ順序で頂点を選択することによって機能します。[ 2 ]まず、入力エッジを持たない「開始ノード」のリストを見つけて、それらをセットSに挿入します。少なくとも1つのそのようなノードは、空でない(有限)非巡回グラフに存在しなければなりません。次に、
L ← ソートされた要素を含む空のリスト S ← 入力エッジを持たないすべてのノードの集合 S が空でない間 、ノードnをSから削除し、 nをLに 追加する。nからmへのエッジeを持つ各ノードmについて、 エッジeをグラフから 削除する。mに他の入力エッジがない場合、mをSに挿入 する。グラフに辺が存在する場合は エラー (グラフに少なくとも1つのサイクルが存在する)を返し、そうでない場合はL (トポロジカルにソートされた順序)を返す。
グラフがDAG(有向非巡回グラフ)である場合、解はリストLに含まれます(ただし、解は必ずしも一意ではありません)。そうでない場合、グラフには少なくとも1つのサイクルが存在するため、トポロジカルソートは不可能です。
結果として得られるソートの非一意性を反映して、構造 S は単なる集合、キュー、またはスタックのいずれかになります。集合 S からノード n が削除される順序に応じて、異なる解が生成されます。辞書式順序で同順位を解消する Kahn アルゴリズムの変種は、並列スケジューリングと階層型グラフ描画のためのCoffman–Graham アルゴリズムの重要な構成要素となっています。
トポロジカルソートの代替アルゴリズムとして、深さ優先探索に基づくものがあります。このアルゴリズムは、グラフの各ノードを任意の順序でループし、深さ優先探索を開始します。この探索は、トポロジカルソートの開始以降に既に訪問されたノードに到達するか、ノードに発信エッジがない場合(つまり、葉ノードの場合)に終了します。
L ← ソートされたノードを含む空のリスト。 永続的なマークのないノードが存在する間、 マークされていないノードnを選択します。visit ( n )function visit(node n ) もしnに永続的なマークが付いているなら、戻ります。もしnに一時的なマークが付いているなら、停止します(グラフには少なくとも1つのサイクルがあります)。nに一時的な印 を付ける nからmへのエッジを持つ各ノードmについて、 visit( m )を実行します。nに油性マーク を付けるLの先頭にn を追加する
各ノードnは、 nに依存する他のすべてのノード(グラフ内のnのすべての子孫) を考慮した後にのみ、出力リスト L の先頭に追加されます。具体的には、アルゴリズムがノードnを追加するとき、 nに依存するすべてのノードが既に出力リスト L に含まれていることが保証されます。これらのノードは、visit() の再帰呼び出しが visit nの呼び出しより前に終了したか、visit() の呼び出しが visit nの呼び出しよりさらに前に開始したかのいずれかによってL に追加されています。各エッジとノードは一度訪問されるため、アルゴリズムは線形時間で実行されます。この深さ優先探索に基づくアルゴリズムは、Cormen ら (2001) によって記述されています。 [ 3 ] 1976年に Tarjan によって初めて印刷物で記述されたようです。[ 4 ]
並列ランダムアクセスマシンでは、多項式数のプロセッサを使用してO ((log n ) 2 ) 時間でトポロジカル順序を構築でき、この問題は複雑性クラスNC 2に分類されます。[ 5 ] これを行う 1 つの方法は、最小化の代わりに最大化を使用したmin-plus 行列乗算を使用して、与えられたグラフの隣接行列を対数的に何度も繰り返し二乗することです。結果として得られる行列は、グラフ内の最長パス距離を表します。最長の入力パスの長さで頂点をソートすると、トポロジカル順序が生成されます。[ 6 ]
分散メモリマシン上での並列トポロジカルソートのためのアルゴリズムは、DAGに対するKahnのアルゴリズムを並列化する。[ 7 ]大まかに言うと、カーンのアルゴリズムは、入次数0の頂点を繰り返し削除し、削除した順序でトポロジカルソートに追加します。削除された頂点の出辺も削除されるため、入次数0の新しい頂点セットが生成され、頂点がなくなるまでこの手順が繰り返されます。このアルゴリズムは、ここで、DはGにおける最長パスである。各反復は並列化することができ、これが次のアルゴリズムの考え方である。
以下では、グラフの分割がp個の処理要素(PE)に格納されていると仮定し、それらの処理要素にはラベルが付けられる。各PE iは、ローカル頂点のセットを初期化します。入次数が0の場合、上側のインデックスは現在の反復を表します。ローカル セット内のすべての頂点は入次数が 0 である、つまり隣接していないため、有効なトポロジカルソートのために任意の順序で指定できます。各頂点にグローバルインデックスを割り当てるには、サイズにわたってプレフィックス和が計算されます。ということで、各ステップにはトポロジカルソートに頂点が追加されました。

最初のステップでは、PE jはインデックスを割り当てますローカル頂点へこれらの頂点は対応する出力エッジとともに削除されます。各出力エッジについて別のPEのエンドポイントvとメッセージPE lに投稿されます。すべての頂点が削除されると、投稿されたメッセージは対応する PE に送信されます。各メッセージローカル頂点vの入次数を更新します。入次数がゼロに低下した場合、vはそして次の反復処理が開始されます。
ステップkでは、PE jはインデックスを割り当てます、 どこ ステップ後の処理済み頂点の総数この手順は、処理すべき頂点がなくなるまで繰り返されます。以下に、このアルゴリズムの概要を示す、単一プログラム、複数データを用いた高レベルの擬似コードを示します。
ローカルオフセットのプレフィックス合計に注意してください並列処理で効率的に計算できる。
IDが0からp -1 までのp個の処理要素入力: G = (V, E) DAG、PEに分散、PEインデックスj = 0, ..., p-1 出力: Gのトポロジカルソート 関数traverseDAGDistributedδはローカル頂点V の入次数です。Q = { v ∈ V | δ[ v ] = 0} // 入次数が0のすべての頂点 処理された頂点数 = 0 グローバルビルドプレフィックスをQのサイズで合計する // このステップでオフセットと頂点の総数を取得する offset = nrOfVerticesProcessed + sum(Q i , i = 0 to j - 1) // jはプロセッサのインデックスです。 foreach u in Q localOrder[u] = index++; E の各要素(u,v) に対して、頂点vを所有する PE にメッセージ ( u, v ) を投稿します。nrOfVerticesProcessed += sum(|Q i |, i = 0 to p - 1) Q内の頂点の隣接頂点にすべてのメッセージを配信する ローカル頂点Vのメッセージを受信する Q内のすべての頂点を削除する 受信した各メッセージ ( u, v ) について: --δ[v] = 0の場合Qのグローバルサイズが0より大きい間、vをQ に 追加するreturn localOrder通信コストは、与えられたグラフ分割に大きく依存します。実行時間に関しては、定数時間でフェッチとデクリメントが可能なCRCW-PRAMモデルでは、このアルゴリズムはここで、DはGにおける最長パスであり、Δは最大次数である。[ 7 ]
トポロジカル順序付けは、重み付き有向非巡回グラフ内の最短経路を高速に計算するためにも使用できます。Vをそのようなグラフの頂点のリストとし、トポロジカル順序付けします。すると、次のアルゴリズムは、ある始点頂点sから他のすべての頂点への最短経路を計算します。 [ 3 ]
言い換えれば:
トポロジカルソートが、ソートされた順序で連続するすべての頂点のペアがエッジで接続されているという性質を持つ場合、これらのエッジはDAG内で有向ハミルトンパスを形成します。ハミルトンパスが存在する場合、トポロジカルソートの順序は一意です。他の順序ではパスのエッジは尊重されません。逆に、トポロジカルソートがハミルトンパスを形成しない場合、DAGには2つ以上の有効なトポロジカル順序が存在します。この場合、エッジで接続されていない2つの連続する頂点を交換することで、常に2番目の有効な順序を形成できるためです。したがって、より一般的な有向グラフ(つまり、巡回有向グラフ)のハミルトンパス問題がNP困難であるにもかかわらず、一意の順序が存在するかどうか、およびハミルトンパスが存在するかどうかを線形時間でテストすることが可能です。 [ 8 ]
位相順序は、数学における部分順序の線形拡張の概念とも密接に関連しています。部分順序集合とは、反射律(x ≤ x)、反対称律(x ≤ y かつy ≤ xならばx = y) 、推移律( x ≤ yかつy ≤ zならば x ≤ z )の公理を満たす「≤」不等式関係の定義を持つオブジェクトの集合です。全順序とは、集合内の任意の 2 つのオブジェクト x と y に対して、x ≤ y または y ≤ x のいずれかが成り立つ部分順序です。全順序は、比較ソートアルゴリズムを実行するために必要な比較演算子として、コンピュータサイエンスではよく知られています。有限集合の場合、全順序はオブジェクトの線形シーケンスと同一視できます。ここで、「≤」関係は、順序において最初のオブジェクトが 2 番目のオブジェクトより前に来る場合に常に真となります。このようにして、比較ソートアルゴリズムを使用して全順序をシーケンスに変換できます。半順序の線形拡張とは、半順序と互換性のある全順序のことである。つまり、半順序において x ≤ yであれば、全順序においてもx ≤ y となる。
任意のDAGから部分順序を定義するには、オブジェクトの集合をDAGの頂点とし、任意の2つの頂点xとyについて、 xからyへの有向パスが存在する場合、つまりyがxから到達可能な場合に、 x ≤ yが真であると定義します。これらの定義により、DAGのトポロジカル順序は、この部分順序の線形拡張と同じものになります。逆に、任意の部分順序は、DAGにおける到達可能性関係として定義できます。これを行う1つの方法は、部分順序集合内のすべてのオブジェクトに対応する頂点と、x ≤ yとなるオブジェクトのペアごとにエッジxyを持つDAGを定義することです。これを行う別の方法は、部分順序の推移的縮小を使用することです。一般に、これによりエッジの少ないDAGが生成されますが、これらのDAGにおける到達可能性関係は依然として同じ部分順序です。これらの構成を使用することで、トポロジカル順序アルゴリズムを使用して部分順序の線形拡張を見つけることができます。
定義上、先行グラフを含むスケジューリング問題の解は、トポロジカルソートの有効な解となります(マシンの数に関係なく)。しかし、トポロジカルソートだけでは、スケジューリング最適化問題を最適に解くには不十分です。Huのアルゴリズムは、先行グラフを必要とし、処理時間を含むスケジューリング問題を解決するためによく用いられる手法です(目標は、すべてのジョブの中で最大の完了時間を最小化することです)。トポロジカルソートと同様に、Huのアルゴリズムも一意ではなく、深さ優先探索(DFS)を使用して解くことができます(最長のパス長を見つけてジョブを割り当てる)。