1. 程式人生 > 實用技巧 >關於圖中的環相關問題

關於圖中的環相關問題

判斷無向圖是否有環:

無向圖中當頂點的數量和邊的數量很大的時候,使用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
     
    const
int 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; }
View Code

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)