C++で依存関係からタスクの実行順序を見つける方法(トポロジカルソート)
問題概要
n個の異なるタスクがあるとします。各タスクには0からn-1までのラベルが付けられており、一部のタスクには前提条件(先に完了しておく必要のあるタスク)が存在します。例えば、タスク2を選択したい場合は、まずタスク1を完了していなければなりません。この関係はペア [2, 1] として表現されます。
タスクの総数と前提条件ペアのリストが与えられたとき、すべてのタスクを完了できるような実行順序を見つける必要があります。有効な順序が複数存在する場合は、そのうちのどれか1つを返せば構いません。また、与えられたすべてのタスクを完了することが不可能な場合(循環依存が存在する場合)は、空の配列を返します。
例えば、入力が n = 4、A = [[1, 0], [2, 0], [3, 2], [3, 1], [4, 2]] の場合、出力は [0, 2, 1, 4, 3] のようになります。
アルゴリズム:DFSによるトポロジカルソート
この問題は、グラフ理論における「トポロジカルソート」の手法で解くことができます。DFS(深さ優先探索)を用いて循環を検出しながら、依存関係に基づいたタスクの順序を構築していきます。
dfs()関数の手順
- dfs()関数を定義します。この関数は、グラフ、開始ノード start、onpath 配列、visited 配列、toposort 配列を引数として受け取ります。
- visited[start] がすでにマークされている場合は、false を返します。
- onpath[start] := true、visited[start] := true と設定します。
- graph[start] 内の各隣接ノード neighbor について、以下を確認します。
- onpath[neighbor] が true、または dfs(graph, neighbor, onpath, visited, toposort) の結果が true の場合は、true を返します(循環が検出されたことを意味します)。
- start を toposort の末尾に挿入します。
- onpath[start] := false として、false を返します。
メイン処理の流れ
- pre 配列に格納された辺を持つ、n 頂点のグラフを作成します。
- toposort 配列を定義します。
- サイズ n の onpath 配列を false で初期化します。
- サイズ n の visited 配列を false で初期化します。
- i := 0 から i < n まで、i を 1 ずつ増やしながら以下を繰り返します。
- visited[i] が false であり、かつ dfs(graph, i, onpath, visited, toposort) が true を返した場合は、空の配列を返します。
- toposort 配列を反転します。
- toposort を返します。
なお、このアルゴリズムの計算量は、頂点数を V、辺数を E とすると時間・空間ともに O(V + E) となり、非常に効率的です。
C++での実装例
以下の実装を見ると、より理解が深まるでしょう。
#include <bits/stdc++.h>
using namespace std;
vector<unordered_set<int> > create_graph(int n, vector<pair<int, int> >& pre) {
vector<unordered_set<int> > graph(n);
for (auto pre : pre)
graph[pre.second].insert(pre.first);
return graph;
}
bool dfs(vector<unordered_set<int> >& graph, int start,vector<bool>& onpath, vector<bool>& visited, vector<int>& toposort) {
if (visited[start])
return false;
onpath[start] = visited[start] = true;
for (int neigh : graph[start])
if (onpath[neigh] || dfs(graph, neigh, onpath, visited, toposort))
return true;
toposort.push_back(start);
return onpath[start] = false;
}
vector<int> get_order(int n, vector<pair<int, int> > &pre){
vector<unordered_set<int> > graph = create_graph(n, pre);
vector<int> toposort;
vector<bool> onpath(n, false), visited(n, false);
for (int i = 0; i < n; i++)
if (!visited[i] && dfs(graph, i, onpath, visited, toposort))
return {};
reverse(toposort.begin(), toposort.end());
return toposort;
}
int main() {
int n = 4;
vector<pair<int, int> > pre = {{1, 0}, {2, 0}, {3, 2}, {3, 1},{4,0}};
vector<int> v = get_order(n, pre);
for (int i = 0; i < v.size(); i++) {
cout << v[i] << " ";
}
}入力
4, {{1, 0}, {2, 0}, {3, 2}, {3, 1},{4,0}}出力
0 1 4 2 3
-
C++で与えられた点から作成できる四角形の数を求める方法
四角形とは? 四角形(クアドララテラル)とは、ユークリッド平面上で4つの頂点と4つの辺を持つ多角形のことを指します。「4-gon」という呼び方もあり、正方形や長方形なども四角形の一種に含まれます。 本記事では、与えられた点から作成できる四角形の数を求める手法について解説します。この問題では、直交座標系(XY平面)上に与えられた4つの点 (x, y) を用いて、いくつの四角形を構成できるかを求めます。まず、具体的な入力例と出力例を見てみましょう。 入力 : A( -2, 8 ), B( -2, 0 ), C( 6, -1 ), D( 0, 8 ) 出力 : 1 説明 : 作成できる四角形は1つだ
-
C++で指定した開始文字から最長の連続パスの長さを求める方法
異なる文字が格納された行列(マトリックス)が与えられます。ある文字を起点として、現在の文字より1つ大きい連続した文字(例:a→b→c→d)をたどりながら、最長のパスの長さを見つけることが課題です。移動は、縦・横・斜めを含む8方向の隣接セルに対して可能です。 例えば、下図のような行列が与えられ、開始文字を「E」とします。 この行列で開始文字「e」から探索すると、最長の連続パスの長さは5となります。 アルゴリズムの考え方 最長パスを見つけるには、深さ優先探索(DFS)アルゴリズムを使用します。DFSの実行中には、同じ部分問題が何度も発生することがあります。このような部分問題を繰り返し計算しないよ