深さ優先探索で順列列挙(ABC 054 C問題)
今回は無向グラフの問題を題材に深さ優先探索(Depth First Search)を勉強します。
AtCoder Begginer Contest 054のC問題です。問題文の概要は、
「N頂点の無向グラフが与えられたとき、頂点1を始点として全頂点を一度だけ通るパスは何通りあるか」
というものです。
本問ではN≦8なので、頂点1を始点とした順列をすべて列挙しても7!=5040通りとなり、これらの順列についてパスが有効か(各頂点が辺で接続されているか)を調べる方法でも十分間に合います。そしてこのような順列を列挙するには、next_permutationという関数を使うと楽らしいのですが、ここでは深さ優先探索を実装してみます。
与えられた無向グラフを表現するには、隣接行列というものを用いると便利です。
下図のような二次元配列を用意して、頂点同士が辺で接続されていればtrueを、そうでなければfalseを入れていきます。今回は重みなしグラフなのでtrueまたはfalseだけですが、重み付きグラフの場合はその重みを各成分に代入すればOKです。(例えば頂点iから頂点jにコストwの辺が伸びている場合は、(i, j)成分=wとします。)
さて、頂点の接続関係を実装出来たら、頂点1からスタートして遷移できなくなるまで辺で結ばれた頂点へ移っていき、遷移できなくなったら一つ前の状態に戻る、という手法(深さ優先探索)で有効なパスを探していきます。コードはこんな感じになります。
AtCoder Begginer Contest 054のC問題です。問題文の概要は、
「N頂点の無向グラフが与えられたとき、頂点1を始点として全頂点を一度だけ通るパスは何通りあるか」
というものです。
本問ではN≦8なので、頂点1を始点とした順列をすべて列挙しても7!=5040通りとなり、これらの順列についてパスが有効か(各頂点が辺で接続されているか)を調べる方法でも十分間に合います。そしてこのような順列を列挙するには、next_permutationという関数を使うと楽らしいのですが、ここでは深さ優先探索を実装してみます。
与えられた無向グラフを表現するには、隣接行列というものを用いると便利です。
下図のような二次元配列を用意して、頂点同士が辺で接続されていればtrueを、そうでなければfalseを入れていきます。今回は重みなしグラフなのでtrueまたはfalseだけですが、重み付きグラフの場合はその重みを各成分に代入すればOKです。(例えば頂点iから頂点jにコストwの辺が伸びている場合は、(i, j)成分=wとします。)
さて、頂点の接続関係を実装出来たら、頂点1からスタートして遷移できなくなるまで辺で結ばれた頂点へ移っていき、遷移できなくなったら一つ前の状態に戻る、という手法(深さ優先探索)で有効なパスを探していきます。コードはこんな感じになります。
- const int nmax = 8;
- //接続状況を格納する隣接行列
- bool graph[nmax][nmax];
- //パスを全探索する関数
- // v:今いる頂点
- // N:頂点の数
- int dfs(int v, int N, bool visited[nmax]) {
- //すべての頂点に訪れたかどうかのフラグ
- bool all_visited = true;
- //一つでも訪れていない頂点があればfalseにする
- for (int i = 0; i < N; i++) {
- if (visited[i] == false)all_visited = false;
- }
- //すべての頂点に訪れていれば有効なパスなので1を返す
- if (all_visited) {
- return 1;
- }
- int ret = 0;
- //すべての遷移先について調べる
- for (int i = 0; i < N; i++) {
- //頂点iが接続されていない、あるいはすでに訪れていればi+1に移る
- if (graph[v][i] == false)continue;
- if (visited[i])continue;
- //iに遷移する
- visited[i] = true;
- //iからの遷移を試す
- ret += dfs(i, N, visited);
- //iのフラグを取り消す
- visited[i] = false;
- }
- return ret;
- }
- int main() {
- int N, M;
- cin >> N >> M;
- for (int i = 0; i < M; i++) {
- int A, B;
- cin >> A >> B;
- //接続状況の入力
- //無向グラフなので両方向からの接続をtrueにする
- graph[A - 1][B - 1] = graph[B - 1][A - 1] = true;
- }
- bool visited[nmax];
- //visitedをfalseで初期化する
- for (int i = 0; i < N; i++) {
- visited[i] = false;
- }
- visited[0] = true;
- //頂点0から始まるパスの数を出力する
- cout << dfs(0, N, visited) << endl;
- return 0;
- }