DFSを使って有向グラフが木(ツリー)かどうかを判定するC++プログラム
グラフは、サイクル(閉路)を一切含まない場合に「木(ツリー)」であるとみなされます。本記事では、DFS(深さ優先探索)を利用して、有向グラフが木かどうかを判定するC++プログラムを解説します。
アルゴリズムの概要
サイクルの検出には、「訪問済みフラグ」と「再帰スタック」の2つの配列を用います。ある頂点から探索を進めた際、現在の探索経路上(再帰スタック上)にすでに存在する頂点へ戻ってきた場合、そこにサイクルが存在すると判断できます。
Begin function cyclicUtil() : a) 現在のノードを「訪問済み」かつ「再帰スタックの一部」としてマークする b) この頂点に隣接するすべての頂点に対して再帰的に探索を行う c) 探索完了後、その頂点を再帰スタックから取り除く function cyclic() : a) すべての頂点を「未訪問」かつ「再帰スタック外」として初期化する b) 各頂点を起点に CyclicUtil() を呼び出し、サイクルの有無を検出する End
サンプルコード
#include<iostream>
#include <list>
#include <limits.h>
using namespace std;
class G {
int n;
list<int> *adj; // 隣接リストを保持
bool CyclicUtil(int v, bool visited[], bool *rs);
public:
G(int V); // コンストラクタ
void addEd(int v, int w);
bool cyclic();
};
G::G(int n) {
this->n = n;
adj = new list<int> [n];
}
void G::addEd(int v, int u) // グラフに辺を追加する
{
adj[v].push_back(u); // v の隣接リストに u を追加
}
bool G::CyclicUtil(int v, bool visited[], bool *recurS) {
if (visited[v] == false) {
visited[v] = true; // 現在のノードを訪問済み・再帰スタック入りとしてマーク
recurS[v] = true;
// この頂点に隣接するすべての頂点に対して再帰的に探索
list<int>::iterator i;
for (i = adj[v].begin(); i != adj[v].end(); ++i) {
if (!visited[*i] && CyclicUtil(*i, visited, recurS))
return true;
else if (recurS[*i])
return true;
}
}
recurS[v] = false; // 頂点を再帰スタックから取り除く
return false;
}
// グラフが木かどうかを判定する
bool G::cyclic() {
// すべての頂点を未訪問・再帰スタック外として初期化
bool *visited = new bool[n];
bool *recurS = new bool[n];
for (int i = 0; i < n; i++) {
visited[i] = false;
recurS[i] = false;
}
// 各頂点を起点に CyclicUtil() を呼び出してサイクルを検出
for (int i = 0; i < n; i++)
if (CyclicUtil(i, visited, recurS))
return true;
return false;
}
int main() {
G g(4);
g.addEd(0, 2);
g.addEd(1, 2);
g.addEd(2, 0);
g.addEd(3, 2);
if (g.cyclic())
cout << "有向グラフは木ではありません";
else
cout << "有向グラフは木です";
return 0;
}実行結果
有向グラフは木ではありません
プログラムのポイント
- 隣接リスト:
list<int>の配列で各頂点の隣接頂点を管理しています。 - 再帰スタックの活用: 単純な訪問済みチェックだけでは検出できない「探索中の経路への戻り」を、
recurS配列によって正確に捉えます。 - 計算量: 頂点数を V、辺数を E とすると、時間計算量は O(V + E) です。
このサンプルでは頂点 0 → 2 → 0 という閉路が存在するため、プログラムは「有向グラフは木ではありません」と出力します。閉路を取り除けば、同じコードで木であることを正しく判定できます。
-
C++で有向グラフの強連結成分を検出するプログラムの作成方法
有向グラフにおいて、ある成分内の任意の頂点ペア同士の間に経路が存在するとき、その成分は「強く接続されている(強連結)」といいます。このような成分のことを強連結成分(SCC: Strongly Connected Components)と呼びます。この問題を解くには、まずDFS(深さ優先探索)を使って各頂点の完了時刻(finish time)を求めます。次にグラフを転置し、完了時刻をもとに頂点を降順に並べる(トポロジカルソート)ことで、強連結成分を一つずつ取り出します。これは有名なKosarajuのアルゴリズムに基づいた手法です。入力: グラフの隣接行列001101000001000000010
-
DFS(深さ優先探索)による有向グラフの連結性チェック ― C++プログラム解説
グラフの連結性チェックの基本概念 グラフが連結しているかどうかを調べるには、何らかの探索アルゴリズムを用いてすべてのノードを巡回してみます。探索が完了した時点で、まだ一度も訪問されていないノードが残っていれば、そのグラフは連結ではないと判断できます。 有向グラフの場合のポイント 無向グラフと異なり、有向グラフの場合はすべてのノードを起点として探索を行う必要があります。理由は、あるエッジが外向きの辺しか持たず、内向きの辺を持たないケースが存在するためです。そのようなノードは、他のどのノードを出発点としても到達できない可能性があります。 本記事では、探索アルゴリズムとして再帰的なDFS(深さ優先