C++
 Computer >> コンピューター >  >> プログラミング >> C++

有向グラフにオイラー閉路が含まれているかどうかを判定するC++プログラム


オイラー閉路(オイラー回路)とは、グラフ上のすべての辺をちょうど1回ずつ通過できる経路のことです。このとき、同じ頂点を何度通っても構いません。オイラー閉路はオイラー路(Euler Path)の特別な形であり、オイラー路の始点がそのまま終点ともつながっている場合を指します。

有向グラフにオイラー閉路が含まれているかどうかを判定するC++プログラム

ある有向グラフがオイラー閉路を持つかどうかを判定するには、次の2つの条件を満たしている必要があります。

  • グラフが連結であること(任意の頂点から他のすべての頂点へ到達できること)。

  • すべての頂点において、入次数と出次数が等しいこと。

入力 − グラフの隣接行列

01000
00100
00011
10000
00100

出力 − オイラー閉路が見つかりました(Euler Circuit Found)

アルゴリズム

traverse(u, visited)

入力 − 探索の起点となるノード u、および訪問済みノードを記録するための配列 visited。

出力 − u から到達可能なすべての頂点を探索します。

u を訪問済みとしてマークする
u に隣接するすべての頂点 v について、
   v が未訪問であれば
      traverse(v, visited) を呼び出す
終了

isConnected(graph)

入力 − 判定対象のグラフ。

出力 − グラフが連結であれば true、そうでなければ false。

visited 配列を用意する
グラフ内のすべての頂点 u について、
   すべてのノードを未訪問に初期化する
   traverse(u, visited) を実行する
   未訪問のノードが1つでも残っていれば
      false を返す
すべての起点から全ノードに到達できれば true を返す
終了

isEulerCircuit(Graph)

入力 − 判定対象のグラフ。

出力 − オイラー閉路が存在すれば true。

isConnected() の結果が false であれば
   false を返す
各ノードの入辺数・出辺数を格納するリストを用意する
グラフ内のすべての頂点 i について、
   sum := 0
   i につながっているすべての頂点 j について、
      頂点 i の入辺数を +1 する
      sum を +1 する
   頂点 i の出辺数として sum を記録する
入辺リストと出辺リストが一致すれば
   true を返す
そうでなければ false を返す
終了

C++による実装例

#include<iostream>
#include<vector>
#define NODE 5
using namespace std;

int graph[NODE][NODE] = {{0, 1, 0, 0, 0},
                         {0, 0, 1, 0, 0},
                         {0, 0, 0, 1, 1},
                         {1, 0, 0, 0, 0},
                         {0, 0, 1, 0, 0}};

// u から到達可能な頂点をすべて探索する
void traverse(int u, bool visited[]) {
   visited[u] = true;              // u を訪問済みとしてマーク
   for (int v = 0; v < NODE; v++) {
      if (graph[u][v]) {
         if (!visited[v])
            traverse(v, visited);
      }
   }
}

// グラフが連結かどうかを判定する
bool isConnected() {
   bool *vis = new bool[NODE];
   // すべての頂点 u を起点として、全ノードに到達できるか確認する
   for (int u = 0; u < NODE; u++) {
      for (int i = 0; i < NODE; i++)
         vis[i] = false;           // 初期状態ではすべて未訪問
      traverse(u, vis);
      for (int i = 0; i < NODE; i++) {
         if (!vis[i])              // 到達できないノードがあれば非連結
            return false;
      }
   }
   return true;
}

// オイラー閉路の存在を判定する
bool isEulerCircuit() {
   if (isConnected() == false) {   // グラフが非連結な場合
      return false;
   }
   vector<int> inward(NODE, 0), outward(NODE, 0);
   for (int i = 0; i < NODE; i++) {
      int sum = 0;
      for (int j = 0; j < NODE; j++) {
         if (graph[i][j]) {
            inward[j]++;           // 行き先 j の入辺数をカウント
            sum++;                 // 頂点 i の出辺数をカウント
         }
      }
      outward[i] = sum;
   }
   // すべての頂点で入辺数と出辺数が一致していれば true
   if (inward == outward)
      return true;
   return false;
}

int main() {
   if (isEulerCircuit())
      cout << "Euler Circuit Found.";
   else
      cout << "There is no Euler Circuit.";
}

このコードでは、まず isConnected() によってグラフの連結性を確認したうえで、各頂点の入次数と出次数を比較しています。この2つの条件がそろった場合のみ、その有向グラフはオイラー閉路を持つと判定されます。

実行結果

Euler Circuit Found.

まとめ

有向グラフのオイラー閉路の判定は、「グラフが連結であること」と「すべての頂点で入次数と出次数が一致すること」という2つの条件に帰着できます。本実装のように隣接行列を用いた場合、連結性の確認のために各頂点から探索を実行するため、全体の計算量は O(V³)(Vは頂点数)となります。小規模なグラフであれば十分実用的なアプローチです。


  1. C++で有向グラフの強連結成分を検出するプログラムの作成方法

    有向グラフにおいて、ある成分内の任意の頂点ペア同士の間に経路が存在するとき、その成分は「強く接続されている(強連結)」といいます。このような成分のことを強連結成分(SCC: Strongly Connected Components)と呼びます。この問題を解くには、まずDFS(深さ優先探索)を使って各頂点の完了時刻(finish time)を求めます。次にグラフを転置し、完了時刻をもとに頂点を降順に並べる(トポロジカルソート)ことで、強連結成分を一つずつ取り出します。これは有名なKosarajuのアルゴリズムに基づいた手法です。入力: グラフの隣接行列001101000001000000010

  2. DFS(深さ優先探索)による有向グラフの連結性チェック ― C++プログラム解説

    グラフの連結性チェックの基本概念 グラフが連結しているかどうかを調べるには、何らかの探索アルゴリズムを用いてすべてのノードを巡回してみます。探索が完了した時点で、まだ一度も訪問されていないノードが残っていれば、そのグラフは連結ではないと判断できます。 有向グラフの場合のポイント 無向グラフと異なり、有向グラフの場合はすべてのノードを起点として探索を行う必要があります。理由は、あるエッジが外向きの辺しか持たず、内向きの辺を持たないケースが存在するためです。そのようなノードは、他のどのノードを出発点としても到達できない可能性があります。 本記事では、探索アルゴリズムとして再帰的なDFS(深さ優先