關於圖中的環相關問題
判斷無向圖是否有環:
無向圖中當頂點的數量和邊的數量很大的時候,使用dfs存在大量的遞迴,會導致棧溢位。使用下面的方法可以有效的避免。
判斷無向圖中是否存在迴路(環)的演算法描述
如果存在迴路,則必存在一個子圖,是一個環路。環路中所有頂點的度>=2。
演算法:
第一步:刪除所有度<=1的頂點及相關的邊,並將另外與這些邊相關的其它頂點的度減一。
第二步:將度數變為1的頂點排入佇列,並從該佇列中取出一個頂點重複步驟一。
如果最後還有未刪除頂點,則存在環,否則沒有環。
由於有m條邊,n個頂點。如果m>=n,則根據圖論知識可直接判斷存在環路。 (證明:如果沒有環路,則該圖必然是k棵樹 k演算法分析>=1。根據樹的性質,邊的數目m = n-k。k>=1,所以:m<n) 如果m<n 則按照上面的演算法每刪除一個度為0的頂點操作一次(最多n次),或每刪除一個度為1的頂點(同時刪一條邊)操作一次(最多m次)。這兩種操作的總數不會超過m+n。由於m<n,所以演算法複雜度為O(n)
判斷有向圖是否有環:
拓撲排序,將出隊元素存入陣列,判斷儲存數組裡的元素是否等於 n 。
求無向圖的最小環花費:
floyd演算法 O (N3)
給出一張無向圖,求一個最小環並輸出路徑。
說說我的感覺:
包含點 i 和點 j 的最小環,我們可以看成是 i 到 j 之間的最短路和次短路的組合,通過 floyd 可求任意兩點之間的最短距離,
那麼我們只要找到最短路徑外的一條最短路來保證 i 和 j 之間可達即可。在做 floyd 迴圈的同時,我們以 環權值 最小(最短路權值+次短路權值=最小環權值)為標準,
一直更新每個點的前驅,也就是記錄 i 到 j 的最短路徑,以及,能夠鬆弛 i 和 j 的點 k (k 不在 i 到 j 的最短路徑中)中代價最小的那個(也就是 i 到 j 之間的次短路),
然後按環的自然順序輸出即可。
#include<cstdio> #include<cstring> #define find_min(a,b) a<b?a:b constView Codeint N = 101; const int INF = 0x7ffffff; int mat[N][N],dist[N][N],pre[N][N],path[N],n; int main() { int i,j,k,m,a,b,c; int num; while(~scanf("%d%d",&n,&m)){ for(i=1;i<=n;i++){ for(j=1;j<=n;j++){ mat[i][j]=dist[i][j]=INF; pre[i][j]=i; } } while(m--){ scanf("%d%d%d",&a,&b,&c); mat[a][b]=mat[b][a]=dist[a][b]=dist[b][a]=find_min(mat[a][b],c); } int min=INF; for(k=1;k<=n;k++){//最短路徑外一點將最短路首尾連結,那麼就得到一個最小環 for(i=1;i<k;i++){ for(j=i+1;j<k;j++){ //求最小環不能用兩點間最短路鬆弛,因為(i,k)之間的最短路,(k,j)之間的最短路可能有重合的部分 //所以mat[][]其實是不更新的,這裡和單純的floyd最短路不一樣 //dist[i][j]儲存的是 i 到 j 的最短路權值和 int tmp=dist[i][j]+mat[i][k]+mat[k][j];//這裡 k 分別和 i 還有 j 在mat中直接相連 if(tmp<min){ min=tmp; num=0; int p=j; while(p!=i){//回溯 path[num++]=p; p=pre[i][p];//pre[i][j]表示 i 到 j 最短路徑上 j 前面的一個點 } path[num++]=i; path[num++]=k; } } } for(i=1;i<=n;i++){ for(j=1;j<=n;j++){ if(dist[i][j]>dist[i][k]+dist[k][j]){ dist[i][j]=dist[i][k]+dist[k][j];//dist[][]儲存兩點間最短距離 pre[i][j]=pre[k][j]; } } } } if(min==INF)puts("No solution."); else{ printf("%d",path[0]); for(i=1;i<num;i++) printf(" %d",path[i]); puts(""); } } return 0; }
Dijkstra演算法 O(M*Mlog)
任意一個環的權值,我們都可以看成兩個有邊相連的結點i、j的直接距離加上i、j間不包含邊(邊i->j)的最短路徑。求最短路徑我們第一個想到的就是Dijkstra演算法。
而Dijkstra所求的是一個點到所有點的最短距離。
用Dijkstra所求的i、j的最短距離一定是i、j的直接距離(如果i,j連通),所以我們需要先將i、j的邊從圖中刪除(若i,j不連通,則不用刪除),
再用Dijkstra求新圖中i、j的最短距離即可。所以我們每次在圖中選取一條邊,把它從圖中刪掉.
然後對刪掉的那條邊所對應的2點進行Dijkstra,也就是m次Dijkstra
給出題目:http://acm.hdu.edu.cn/showproblem.php?pid=6005
給你m條邊,每條邊給你兩個城市的座標,還有兩個城市道路之間有成本
讓你求一個最小成本的週期,至少包含三個城市。
#include <bits/stdc++.h> #define pii pair<int,int> using namespace std; const int inf = 0x3f3f3f3f; const int maxn = 1e4+15; const int maxm = 8e3+43; int t,cnt,m,x,y,w,u,v,p,res,head[maxm]; struct edge{ int to,nxt,w; }e[maxm<<1]; struct cmp{ bool operator()(const pii a,const pii b){ return a.second > b.second; } }; map<pii,int> vis; inline void add(int x,int y,int _w){ e[++cnt].to = y;e[cnt].w = _w; e[cnt].nxt = head[x];head[x] = cnt; } int dis[maxm]; void init(){ vis.clear(); cnt = 0;res = inf;p = 0; memset(head,0,sizeof(head)); } void dijkstra(int x,int y){ memset(dis,inf,sizeof(dis)); priority_queue<pii,vector<pii>,cmp> q; dis[x] = 0; q.push(pii(x,0)); while(!q.empty()){ pii now = q.top();q.pop(); int uu = now.first; if(uu == y || now.second > res) return ; //剪枝 if(dis[uu] < now.second) continue; for(int i = head[uu];i;i = e[i].nxt){ int vv = e[i].to; if((uu==x&&vv==y)||(uu==y&&vv==x)) continue; //這條邊是刪除的邊 if(dis[vv] > dis[uu]+e[i].w){ dis[vv] = dis[uu] + e[i].w; q.push(pii(vv,dis[vv])); } } } } int main(){ scanf("%d",&t); for(int Case = 1; Case <= t; ++Case){ init(); scanf("%d",&m); for(int i = 1;i <= m; ++i){ scanf("%d %d",&x,&y); if(!vis[pii(x,y)]) u = ++p,vis[pii(x,y)] = u; else u = vis[pii(x,y)]; scanf("%d %d %d",&x,&y,&w); if(!vis[pii(x,y)]) v = ++p,vis[pii(x,y)] = v; else v = vis[pii(x,y)]; add(u,v,w); add(v,u,w); } for(int i = 1;i <= p; ++i){ for(int j = head[i]; j ;j = e[j].nxt){ if(i >= e[j].to) continue; dijkstra(i,e[j].to); res = min(res,dis[e[j].to]+e[j].w); } } printf("Case #%d: %d\n",Case,res==inf ? 0:res); } return 0; }View Code
O(n(n+m)log