zoukankan      html  css  js  c++  java
  • 图论五:网络流

    参考文章一:https://blog.csdn.net/txl199106/article/details/64441994

    参考文章二:https://blog.csdn.net/ergedathouder/article/details/55001645

    一、基本概念

    1、源点:只进不出的点,通常称为S

    2、汇点:只出不进的点,通常称为T

    3、容量&流量:容量是每条边上最大能经过的值,流量是当前经过边的值。(流量一定小于容量)

    容量用C表示,流量用F表示。

    4、三个基本性质:

    (1)每条边的流量小于容量,即C<F

    (2)流量守恒:Σ F<v,x> = Σ F<x,u>

    (3)斜对称性:F<x,y> = - F<y,x>

    5、容量网络&流量网络&残留网络

    残留网路 = 容量网络 - 流量网络

    6、割集:(可以分为点割集和边割集)

    就是去掉这一部分图不连通的部分。

    (最小割集是能成为割集的最小集合)

    7、最大流最小割定理

    (1)任意一个流都小于一个割;

    (2)将满流边作为一个割,就构造出和最大流相等的割;

    (3)最大流等于最小割

    设相等的流和割分别为Fm,Cm,

    则有:F<=Fm=Cm<=C(C,F为任意的)。

    三个等价条件:

    对于一个图G=(V,E)有源点s和汇点t

    (1)F是图G最大流

    (2)残留网络GF不存在增广路径,

    (3)对于G的一个割(S,T),存在f=C(S,T)。

    二、算法实现

     1、增广路径算法(EK算法)

    (1)算法功能:求增广路径

    (2)算法思想:bfs

    (3)算法流程:

    首先建立邻接表,存储每条边的数据,pre数组记录路径,vis数组标记路径,接着对数据进行初始化;

    其次,bfs遍历寻找最小边,然后更新整个图,建立反向边,并求和,更新ans的值,之后一直重复此操作。

    最后,如果bfs遍历时找不到最后的节点,说明图中没有增广路径,算法结束。

    代码:

    (1)邻接数组实现

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<queue>
    using namespace std;
    const int maxn = 1200;
    const int INF = 0x3fff;
    int pre[maxn],p[maxn],vis[maxn],edge[maxn][maxn],m,n;
    int MIN(int x,int y){ return x<y?x:y; }
    void Init() { memset(edge,0,sizeof(edge)); } 
    int EK(int st,int ed) 
    {
        int ans=0,j,i,fg,mi;
        while(1)
        {
            fg=0;
            queue <int> q;
            q.push(st);
            memset(vis,0,sizeof(vis)); 
            memset(pre,0,sizeof(pre));
            p[st]=INF;
            while(!q.empty()) //寻找增广路径 
            {
                int top=q.front();
                q.pop();
                for(i=1;i<=n;i++)
                if(edge[top][i]&&vis[i]==0)
                {
                    vis[i]=1;
                    pre[i]=top; //记录增广路径 
                    if(i==ed){ fg=1;break; }
                    q.push(i);
                }
            }
            if(fg==0) return ans;
            mi=INF;
            for(i=ed;i!=st;i=pre[i]) mi=MIN(mi,edge[pre[i]][i]); //寻找最小值 
            for(i=ed;i!=st;i=pre[i]) edge[pre[i]][i]-=mi,edge[i][pre[i]]+=mi; //更新路径 
            ans+=mi;
        }
    } 
    int main(void)
    {
        int x,y,z;
        cin>>n>>m;
        Init();
        for(int i=1;i<=m;i++) cin>>x>>y>>z,edge[x][y]+=z;
        cout<<EK(1,n)<<endl;
        return 0;
    } 
    View Code

    (2)邻接表实现

    #include<iostream>
    #include<cstdio>
    #include<queue>
    #include<cstring>
    using namespace std;
    const int maxn = 1200;
    const int INF = 0x3fff;
    struct Edge{
        int v,next,w; //v表示边的终点,next表示下一个边的编号 
    }edge[maxn];
    struct Vertex{
        int v,id; //v表示下一个点,id表示当前的点 
    }pre[maxn];
    int n,m,cnt;
    int vis[maxn],head[maxn];
    int MIN(int x,int y){ return x<y?x:y; }
    void Init() //初始化 
    {
        cnt=0;
        memset(edge,0,sizeof(edge));
        memset(head,-1,sizeof(head));
    }
    void add(int u,int v,int w) //添加新边 
    {
        edge[cnt].v=v;
        edge[cnt].w=w;
        edge[cnt].next=head[u];
        head[u]=cnt++;
    }
    int EK(int st,int ed) //EK算法具体实现 
    {
        int i,j,fg,tmp,ans=0,mi;
        while(1)
        {
            fg=0;st=1;ed=n;
            memset(vis,0,sizeof(vis));
            memset(pre,-1,sizeof(pre));
            pre[st].v=st;vis[st]=1;
            queue <int> q;
            q.push(st);
            while(!q.empty())
            {
                int top=q.front();
                q.pop();
                for(i=head[top];i+1;i=edge[i].next)
                {
                    int v=edge[i].v;
                    if(vis[v]==0&&edge[i].w)
                    {
                        vis[v]=1;
                        pre[v].v=top;
                        pre[v].id=i;
                        if(v==ed) { fg=1;break; }
                        q.push(v);
                    }
                }
            }
            if(fg==0) return ans;
            mi=INF;
            for(i=ed;i!=st;i=pre[i].v) mi=MIN(mi,edge[pre[i].id].w); //正向路径更新 
            for(i=ed;i!=st;i=pre[i].v) edge[pre[i].id].w-=mi,edge[pre[i].id^1].w+=mi; //a^1表示取反,即pre[i].id的反向路径更新 
            ans+=mi;
        }
    }
    int main(void)
    {
        int x,y,z;
        cin>>n>>m;
        Init();
        for(int i=1;i<=m;i++)
        {
            cin>>x>>y>>z;
            add(x,y,z);add(y,x,0);
        }
        cout<<EK(1,n)<<endl;
        return 0;
    } 
    
    /*
    4 5
    1 2 2
    1 4 2
    2 4 3
    2 3 4
    4 3 4
    */
    View Code

    2、dinic算法

    算法流程:

    (1)初始化,计算出剩余图

    (2)根据剩余图计算层次图,若汇点不在层次图中,算法结束

    (3)层次图内用dfs计算增广路径

    (4)返回(2)

    代码:

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<queue>
    using namespace std;
    const int maxn = 1200;
    const int INF = 0x3ffffff;
    struct Node{
        int v,c,next;
    }edge[maxn*maxn<<1];
    int level[maxn],p[maxn],n,m,S,T,cnt;
    void Init()
    {
        memset(p,-1,sizeof(p));
        cnt=0;S=1;T=n;
    }
    int MIN(int x,int y)
    {
        return x<y?x:y;
    }
    void Insert(int u,int v,int c)
    {
        edge[cnt].v=v;
        edge[cnt].c=c;
        edge[cnt].next=p[u];
        p[u]=cnt++;
    }
    void addedge(int u,int v,int c)
    {
        Insert(u,v,c); 
        Insert(v,u,0);
    }
    bool bfs()
    {
        memset(level,-1,sizeof(level));
        level[S]=0;
        queue <int> q;
        q.push(S);
        while(!q.empty())
        {
            int u=q.front();
            q.pop();
            for(int i=p[u];i!=-1;i=edge[i].next)
            {
                int v=edge[i].v;
                if(edge[i].c>0&&level[v]==-1)
                {
                    level[v]=level[u]+1;
                    q.push(v);
                }
            }
        }
        return level[T]!=-1;
    }
    int dfs(int u,int cp)
    {
        if(u==T) return cp;
        int res=0;
        for(int i=p[u];i!=-1;i=edge[i].next)
        {
            int v=edge[i].v;
            if(level[u]+1==level[v]&&edge[i].c>0)
            {
                int t=dfs(v,MIN(cp,edge[i].c));
                cp-=t;
                res+=t;
                edge[i].c-=t;
                edge[i^1].c+=t;
                if(cp==0) break;
            }
        }
        if(res==0) level[u]=-1;
        return res;
    }
    int dinic()
    {
        int sum=0,tmp;
        while(bfs())
        sum+=dfs(S,INF);
        return sum;
    }
    int main(void)
    {
        int i,x,y,z;
        scanf("%d%d",&n,&m);
        Init();
        for(i=1;i<=m;i++)
        {
            scanf("%d%d%d",&x,&y,&z);
            addedge(x,y,z);
        }
        printf("%d
    ",dinic());
        return 0;
    }
    /*
    4 5
    1 2 2
    1 4 2
    2 4 3
    2 3 4
    4 3 4
    */
    View Code
  • 相关阅读:
    js代码的执行顺序及运算
    javascript讲解
    浏览器的差距
    标准流
    下拉列表
    单位
    滚动标签
    接着说一些有关排版的一些东西
    关于处理浏览器的兼容问题
    关于排版的技巧
  • 原文地址:https://www.cnblogs.com/2018zxy/p/10126716.html
Copyright © 2011-2022 走看看