無向グラフの場合[〜#〜] g [〜#〜]=([〜#〜] v [〜#〜]、[〜# 〜] e [〜#〜])with n vertices(| [〜#〜] v [〜#〜] | = n)、[〜#〜] o [〜#〜](n)にサイクルが含まれている場合、どのように見つけますか?
深さ優先検索で解決できると思います。探索されていないエッジが前にアクセスしたノードにつながる場合、グラフにはサイクルが含まれています。この条件では、O(n)になります。これは、trueに設定したり、未探索のエッジを残さずに最大n個のエッジを探索できるためです。
サイクルのない接続された無向グラフGはツリーです!どのツリーにも正確にn − 1のエッジがあるため、グラフのエッジリストを単純に走査してエッジをカウントできます。 n − 1個のエッジをカウントする場合は「yes」を返しますが、n番目のエッジに到達する場合は「no」を返します。最大でn個のエッジを見るため、これにはO(n)時間かかります。
ただし、グラフが接続されていない場合は、DFSを使用する必要があります。エッジを横断することができ、未探索のエッジが訪問先の頂点につながる場合、サイクルがあります。
DFSを使用して解決できます。時間の複雑さ:O(n)
アルゴリズムの本質は、接続されたコンポーネント/グラフにCYCLEが含まれていない場合、常にTREEになることです。 証明についてはこちらを参照してください
グラフにサイクルがない、つまりツリーであると仮定しましょう。そして、ツリーを見ると、ノードからの各エッジ:
1.どちらも、その1つ上のレベルである唯一の親に到達します。
2.またはその下位レベルの子に到達します。
したがって、ノードに上記の2つの中にないエッジが他にある場合、ノードは明らかにその親以外の祖先の1つに接続します。これにより、サイクルが形成されます。
事実が明確になったので、あなたがしなければならないのは、グラフのDFSを実行することです(グラフが接続されていることを考慮し、そうでない場合はすべての未訪問の頂点に対してそれを行います)親、次に私の友人がグラフにサイクルがあり、あなたは完了です。
近隣のDFSを実行するときに、単に親をパラメーターとして渡すことで、親を追跡できます。そして、nエッジのみを調べる必要があるため、時間の複雑さはO(n)になります。
答えが役に立てば幸いです。
ちなみに、たまたま接続されていることがわかっている場合は、|E|=|V|-1
。もちろん、それは少量の情報ではありません:)
答えは、実際には、幅優先検索(または深さ優先検索、それは実際には重要ではありません)です。詳細は分析にあります。
さて、アルゴリズムの速度はどれくらいですか?
まず、グラフにサイクルがないことを想像してください。エッジの数はO(V)になり、グラフは森になり、目標に達しました。
ここで、グラフにサイクルがあり、検索アルゴリズムが終了して最初の成功を報告すると想像してください。グラフは方向付けられていないため、アルゴリズムがエッジを検査する場合、2つの可能性があります。エッジのもう一方の端を訪れたか、このエッジが円を閉じます。そして、Edgeのもう一方の頂点が見つかると、その頂点は「検査」されるので、これらの操作はO(V)のみです。2番目のケースは実行中に1回だけ到達しますアルゴリズムの。
単純なDFSは、指定された無向グラフにサイクルがあるかどうかを確認する作業を行います。
上記のコードで使用されるideaは次のとおりです。
既に検出/訪問されたノードが再び検出され、親ノードではない場合、サイクルがあります。
これは、以下のように説明することもできます(@RafałDowgirdが言及)
探索されていないエッジが前にアクセスしたノードにつながる場合、グラフにはサイクルが含まれています。
ブーストグラフライブラリ および 循環依存関係 を使用できます。 back_Edge
関数でサイクルを見つけるためのソリューションがあります。
DFSがバックエッジを生成しない場合、無向グラフは非周期的(つまり、フォレスト)です。バックエッジは、頂点u
を深さ優先ツリーの祖先v
に接続するエッジ(u
、v
)であるため、バックエッジはありません。ツリーのエッジしかないため、サイクルはありません。したがって、単純にDFSを実行できます。バックエッジが見つかった場合、サイクルがあります。複雑さは、O(V)
ではなくO(E + V)
です。バックエッジがある場合、|V|
の異なるエッジを見る前に見つける必要があるためです。これは、非循環(無向)フォレストでは|E| ≤ |V| + 1
であるためです。
グラフが接続されているという仮定はほんの一握りになると思います。したがって、実行時間がO(| V |)であるという上記の証明を使用できます。そうでない場合、| E |> | V |。注意:DFSの実行時間はO(| V | + | E |)です。
最近グラフの勉強を始めました。 Javaでコードを作成しました。これは、グラフにサイクルがあるかどうかを判別できます。グラフのサイクルを見つけるためにDFTを使用しました。
スタックを使用した高レベルのDFTは、次の手順で行われます
Graphの各ノードからDFTを実行し、以前に訪れた頂点に遭遇した場合、トラバース中に、頂点のスタック深度が1より大きいかどうかを確認しました。また、ノードにそれ自体へのエッジがあるかどうか、ノード間に複数のエッジがあるかどうかも確認しました。私が最初に書いたスタックバージョンはあまりエレガントではありませんでした。再帰を使用してどのように実行できるかを示す擬似コードを読みましたが、すっきりしていました。 Javaの実装です。LinkedList配列はグラフを表します。各ノードとそれに隣接する頂点は、それぞれ配列のインデックスと各項目によって示されます。
class GFG {
Boolean isCyclic(int V, LinkedList<Integer>[] alist) {
List<Integer> visited = new ArrayList<Integer>();
for (int i = 0; i < V; i++) {
if (!visited.contains(i)) {
if (isCyclic(i, alist, visited, -1))
return true;
}
}
return false;
}
Boolean isCyclic(int vertex, LinkedList<Integer>[] alist, List<Integer> visited, int parent) {
visited.add(vertex);
for (Iterator<Integer> iterator = alist[vertex].iterator(); iterator.hasNext();) {
int element = iterator.next();
if (!visited.contains(element)) {
if (isCyclic(element, alist, visited, vertex))
return true;
} else if (element != parent)
return true;
}
return false;
}
}
これは、特定のグラフが接続/循環しているかどうかを調べるために、DFSに基づいてCで記述したコードです。最後にサンプル出力があります。お役に立てば幸いです:)
#include<stdio.h>
#include<stdlib.h>
/****Global Variables****/
int A[20][20],visited[20],v=0,count=0,n;
int seq[20],s=0,connected=1,acyclic=1;
/****DFS Function Declaration****/
void DFS();
/****DFSearch Function Declaration****/
void DFSearch(int cur);
/****Main Function****/
int main()
{
int i,j;
printf("\nEnter no of Vertices: ");
scanf("%d",&n);
printf("\nEnter the Adjacency Matrix(1/0):\n");
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
scanf("%d",&A[i][j]);
printf("\nThe Depth First Search Traversal:\n");
DFS();
for(i=1;i<=n;i++)
printf("%c,%d\t",'a'+seq[i]-1,i);
if(connected && acyclic) printf("\n\nIt is a Connected, Acyclic Graph!");
if(!connected && acyclic) printf("\n\nIt is a Not-Connected, Acyclic Graph!");
if(connected && !acyclic) printf("\n\nGraph is a Connected, Cyclic Graph!");
if(!connected && !acyclic) printf("\n\nIt is a Not-Connected, Cyclic Graph!");
printf("\n\n");
return 0;
}
/****DFS Function Definition****/
void DFS()
{
int i;
for(i=1;i<=n;i++)
if(!visited[i])
{
if(i>1) connected=0;
DFSearch(i);
}
}
/****DFSearch Function Definition****/
void DFSearch(int cur)
{
int i,j;
visited[cur]=++count;
seq[count]=cur;
for(i=1;i<count-1;i++)
if(A[cur][seq[i]])
acyclic=0;
for(i=1;i<=n;i++)
if(A[cur][i] && !visited[i])
DFSearch(i);
}
/ *サンプル出力:
majid@majid-K53SC:~/Desktop$ gcc BFS.c
majid@majid-K53SC:~/Desktop$ ./a.out
************************************
Enter no of Vertices: 10
Enter the Adjacency Matrix(1/0):
0 0 1 1 1 0 0 0 0 0
0 0 0 0 1 0 0 0 0 0
0 0 0 1 0 1 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 1 0 0 1 0 0 0 0 0
0 0 0 0 0 0 0 1 0 0
0 0 0 0 0 0 0 0 1 0
0 0 0 0 0 0 0 0 0 1
0 0 0 0 0 0 1 0 0 0
The Depdth First Search Traversal:
a,1 c,2 d,3 f,4 b,5 e,6 g,7 h,8 i,9 j,10
It is a Not-Connected, Cyclic Graph!
majid@majid-K53SC:~/Desktop$ ./a.out
************************************
Enter no of Vertices: 4
Enter the Adjacency Matrix(1/0):
0 0 1 1
0 0 1 0
1 1 0 0
0 0 0 1
The Depth First Search Traversal:
a,1 c,2 b,3 d,4
It is a Connected, Acyclic Graph!
majid@majid-K53SC:~/Desktop$ ./a.out
************************************
Enter no of Vertices: 5
Enter the Adjacency Matrix(1/0):
0 0 0 1 0
0 0 0 1 0
0 0 0 0 1
1 1 0 0 0
0 0 1 0 0
The Depth First Search Traversal:
a,1 d,2 b,3 c,4 e,5
It is a Not-Connected, Acyclic Graph!
*/
他の人が言及したように...深さ優先の検索はそれを解決します。一般に、深さ優先検索ではO(V + E)を使用しますが、この場合、グラフには最大でO(V)エッジがあります。 new Edgeはカウンターを増やしますカウンターがVに達したとき、グラフには確かにサイクルがあるので、続行する必要はありません。
DFSを正しく使用することは、コードでグラフをどのように表現するかにも依存すると考えています。たとえば、隣接ノードを追跡するために隣接リストを使用しており、グラフに2つの頂点と1つのエッジ(V = {1,2}およびE = {(1,2)})があるとします。この場合、頂点1から始まり、DFSはVISITEDとしてマークし、2をキューに入れます。その後、頂点2がポップされ、1が2に隣接し、1がVISITEDであるため、DFSはサイクルがあると結論付けます(これは間違っています)。つまり、無向グラフの(1,2)と(2,1)は同じエッジであり、DFSが異なるエッジを考慮しないようにコーディングする必要があります。訪問したノードごとに親ノードを保持すると、この状況に対処するのに役立ちます。