zoukankan      html  css  js  c++  java
  • 关于图中的环相关问题

    判断无向图是否有环:

    无向图中当顶点的数量和边的数量很大的时候,使用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

     求有向图的最小环花费:

    floyd算法 O (N3) (可以求边带权的)

    对于每个i 比较 dist[ i ][ i ] 的大小即可。

    #include <iostream>
    #include <cstring>
    #include <cstdio>
    #include <queue>
    #define mmin(a,b) a<b? a:b
    using namespace std;
    const int INF=0x7ffffff;
     
    int mp[105][105];
    int n,m;
    int floyd()
    {
        int i,j,k;
        for(k=0; k<n; k++)
            for(i=0; i<n; i++)
                for(j=0; j<n; j++)
                    if(mp[i][j] > mp[i][k]+mp[k][j])
                        mp[i][j]=mp[i][k]+mp[k][j];
    }
     
    int main()
    {
       // printf("%d\n",INF);
        int cases;
        scanf("%d",&cases);
        while(cases--)
        {
            int a,b,c;
            scanf("%d %d",&n,&m);
            for(int i=0; i<n; i++)
                for(int j=0; j<n; j++)
                    mp[i][j]=INF;
     
     
            for(int i=0; i<m; i++)
            {
                scanf("%d %d %d",&a,&b,&c);
                if(c<mp[a][b])
                    mp[a][b]=c;
            }
            floyd();
            int result=INF;
     
           /* for(int i=0; i<n; i++)
            {
                for(int j=0; j<n; j++)
                   printf("%d ",mp[i][j]);
                printf("\n");
            }*/
     
            for(int i=0; i<n; i++)
            {
              //  printf("%d\n",mp[i][i]);
                if(result>mp[i][i])
                    result=mp[i][i];
            }
            if(result == INF)
                printf("-1\n");
            else
                printf("%d\n",result);
        }
        return 0;
    }
    View Code

    带权并查集 O(N)(当n比较大时,且边权为1)

    #include<cstdio>
    #include<iostream>
    using namespace std;
    int f[200002],d[200002],n,minn,last;   //f保存祖先节点,d保存到其祖先节点的路径长。 
    int fa(int x)
    {
        if (f[x]!=x)                       //查找时沿途更新祖先节点和路径长。 
        {
            int last=f[x];                 //记录父节点(会在递归中被更新)。 
            f[x]=fa(f[x]);                 //更新祖先节点。 
            d[x]+=d[last];                 //更新路径长(原来连在父节点上)。 
        }
        return f[x];
    }
    void check(int a,int b)
    {
        int x=fa(a),y=fa(b);               //查找祖先节点。 
        if (x!=y) {f[x]=y; d[a]=d[b]+1;}   //若不相连,则连接两点,更新父节点和路径长。 
        else minn=min(minn,d[a]+d[b]+1);   //若已连接,则更新最小环长度。 
        return;
    }
    int main()
    {
        int i,t;
        scanf("%d",&n);
        for (i=1;i<=n;i++) f[i]=i;         //祖先节点初始化为自己,路径长为0。 
        minn=0x7777777;
        for (i=1;i<=n;i++)
        {
            scanf("%d",&t);
            check(i,t);                    //检查当前两点是否已有边相连接。 
        }
        printf("%d",minn);
        return 0;
    }
    View Code

    拓扑排序 O(N+M) + DFS (可能会爆)(这只是我的口胡而已)

    具体做法就是先跑一边拓扑标记,之后跑dfs就好,注意dfs找到环的时候不能直接结束整个dfs,因为需要统计所有环的大小(边权为1

    如果带权的话,那么也是一样的,只不过把层数改成了目前经过的权值。

    (上述算法应该仅限于每个点只有一条出边的题目。)

     spfa 题目链接:https://blog.csdn.net/qq_43408238/article/details/102641472

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<map>
    #include<cstdio>
    #include<queue>
    #include<stack>
     
    using namespace std;
     
    const int INF = 0x3f3f3f3f;
     
    int cost[305][305];
    int dis[305];
    int n;
    bool vis[305];
     
    void spfa( int start ){
        stack<int> Q;
     
        for( int i = 1; i <= n; i++ ){
            dis[i] = cost[start][i];
            if( i != start ){
                Q.push( i );
                vis[i] = true;
            }
            else{
                vis[i] = false;
            }
        }
        dis[start] = INF;
     
        while( !Q.empty() ){
            int x = Q.top(); Q.pop(); vis[x] = false;
     
            for( int y = 1; y <= n; y++ ){
                if( x == y ) continue;
                if( dis[x] + cost[x][y] < dis[y] ){
                    dis[y] = dis[x] + cost[x][y];
                    if( !vis[y] ){
                        vis[y] = true;
                        Q.push( y );
                    }
                }
            }
        }
    }
     
    int main(){
        ios::sync_with_stdio( false );
     
        while( cin >> n ){
            for( int i = 1; i <= n; i++ ){
                for( int j = 1; j <= n; j++ ){
                    cin >> cost[i][j];
                }
            }
     
            int ans, c1, cn;
            spfa( 1 );
            ans = dis[n];
            c1 = dis[1];
            spfa( n );
            cn = dis[n];
     
     
            cout << min( ans, c1 + cn ) << endl;
        }
     
        return 0;
    }
    View Code

     (上述算法应该仅限于每个点只有一条出边的题目。)

    求所有环的数量(口胡)

    好像只能用暴力dfs,不能用tajan因为不是求强联通分量。

    有向图就是裸dfs, 无向图就是需要不能访问fa节点。

    O(n(n+m)log)

  • 相关阅读:
    简单的三栏,文字多行居中效果 css原生
    目录
    HttpRunner使用
    测试职能
    缺陷
    SQL操作数据
    jmeter使用
    接口自动化理论引入
    接口自动化框架(Pytest,Allure,Yaml)
    jmeter 登陆--查询存在否-->新建客户-->查询存在否 + 压测
  • 原文地址:https://www.cnblogs.com/Vikyanite/p/13697581.html
Copyright © 2011-2022 走看看