首页 > 代码库 > 最大流最小割C++实现

最大流最小割C++实现

最大流量问题:寻找从源点到汇点的最大流量。

使用最短增益路径法(或先标记先扫描法)。这里标记意味着用两个记号来标记一个新的(为标记)的顶点:第一个标记指出从源点到被标记顶点还能增加多少流量;第二用来分别个标记指出另一个顶点的名字,就是从该顶点访问到被标记顶点的(对于源点来说这个标记可以不必指定)。方便起见,也可以为第二个标记加上“+”或“-”符号,用来分别指出该顶点是通过前向边还是后向边访问到的。因此,源点可被标记为∞,-。对于其他顶点,按照以下方法计算它的标记:

(1)如果为标记顶点j是由j到i的有向边和遍历队列中的前面顶点i相连接的,而且j具有大于0的未使用容量rij=uij-xij,其中uij为边的总容量,xij为当前正容量,那么顶点j就标记为lj,i+,其中lj=min{li,rij}。

(2)如果为标记顶点j是由j到i的有向边和遍历队列中的前面顶点i相连接的,而且j具有大于0的未使用容量xji,那么顶点j就标记为lj,i-,其中lj=min{li,xji}。

如果这种加入了标记的遍历结束于汇点,当前流量的增量就可以确定为汇点的第一个标记。我们从汇点开始,沿着第二个标记回溯到源点,来执行这一增量。在这条路径上,前向边的当前流量增加,后向边的当前流量减少。另一方面,如果遍历队列为空以后,汇点仍然没有被标记,该算法就把当前流量作为最大值返回并结束算法。

 

最小割

首先说明什么是割——我们可以把网络的顶点分成两个子集X和X‘,X包含源点,X‘是X的部,包含汇点。所有头在X‘,尾在X的边的集合称作,记作C(X,X‘)或C。

割C(X,X‘)容量,记作c(X,X‘),定义为构成割的边的容量和。最小割是具有最小容量的割。

最大流-最小割定理:网络中的最大流量值等于它的最小割的容量。

在最大流为问题的最后一次迭代时,所有已标记顶点到未标记顶点的边就构成了最小割。

 

注:以上文字摘自《算法设计与分析基础》,(美)Anany Levitin 著,潘彦 译。

 

#include<iostream>#include<queue>using namespace std;const int MAX=100;const int inf=1<<30;queue<int> Q;int ShortestAugmentingPath(int n,int source,int sink,int map[][MAX],int flow[][MAX],int pre_t[]){    /*  n为顶点数 ,source为源点,sink为汇点,map[][]记录对应边的最大容量,flow[][]记录对应边具有的正流量;    最后一次迭代的pre_t[]记录了最小割,pre_t[i] !=0 表示属于源点集合  */        if(source==sink)  //特殊情况处理        return inf;        int i,x;    int visited[MAX];    //visited[i]=1表示顶点i被标记    int rest[MAX][MAX];  //rest[i][j]表示顶点i到j的未使用流量    int L[MAX];   //L[i]记录一次迭代过程中节点i的最大增量    int pre[MAX]; //pre[i]=0表示顶点i未标记,pre[i] >0 表示前向边,pre[i] < 0表示后向边   //数组初始化    memset(rest,0,sizeof(rest));    memset(visited,0,sizeof(visited));    memset(flow,0,(n+1)*sizeof(flow[0]));    memset(L,0x7f,n*sizeof(L[0]));          memset(pre,0,sizeof(pre));           pre[source]=source+1;  //任意赋值为一个整数        while(!Q.empty())        Q.pop();    Q.push(source);  //最初,源点入队        while(!Q.empty())    {        visited[source]=1;  //源点被标记        i=Q.front();        Q.pop();                for(x=1;x<=n;x++) //前向边        {                                                  if(i!=x&&!visited[x]&&map[i][x]!=inf)            {                rest[i][x]=map[i][x]-flow[i][x];                                if( rest[i][x] > 0 )                {                    L[x]=L[i] < rest[i][x] ? L[i] : rest[i][x]; //L[x]=min(rest[i][x],L[i])                    Q.push(x);                    visited[x]=1;                    pre[x]=i;                }            }        }        for(x=1;x<=n;x++)  //后向边        {                                                    if(i!=x&&!visited[x]&&map[x][i]!=inf)            {                if(flow[x][i] > 0)                {                    L[x]=L[i] < flow[x][i] ? L[i] : flow[x][i];                    Q.push(x);                    visited[x]=1;                    pre[x]=-i;                }            }        }                for(int y=1;y<=n;y++)  //记录最后一次迭代结果的标记,即为最小割            pre_t[y]=pre[y];                if(visited[sink]==1) //汇点被标记了        {            x=sink;            while(x!=source) //从汇点开始用第二个标记反向移动            {                if(pre[x]>0)                     flow[i][x] += L[sink];                else                    flow[i][x] -= L[sink];                x=i;                                if(pre[i]>0 )                    i=pre[i];                else                    i=-pre[i];            }                        memset(visited,0,sizeof(visited)); //擦除标记            memset(L,0x7f,n*sizeof(L[0]));                        while(!Q.empty())                 Q.pop();            Q.push(source); //用源点对q重新初始化                        memset(pre,0,sizeof(pre));            pre[source]=1;        }    }        int maxflow=0;    for(i=1; i <= n;i++)        maxflow+=flow[i][sink];    return maxflow;}    int main(){    int map[MAX][MAX],flow[MAX][MAX],pre[MAX];    int n,i,x,w;        cout<<"输入顶点数:";    while(cin>>n && n)    {        for(i=1;i<=n;i++)        {            for(x=1;x<=n;x++)            map[i][x]=inf;        }                int m; //输入边数        cout<<"输入边数:  ";        cin>>m;                cout<<"分别输入每个有向边的起点,终点和容量:"<<endl;        while(m--)        {            cin>>i>>x>>w;            map[i][x]=w;        }        cout<<"最大流是:"<<ShortestAugmentingPath(n,1,n,map,flow,pre)<<endl;                cout<<"最小割为: 源点集合X={";        for(i=1;i<=n;i++)        {            if(pre[i]!=0 )                cout<<i<<" ";        }        cout<<"}, 汇点集合Y={";        for(i=1;i<=n;i++)        {            if(pre[i]==0)                cout<<i<<" ";        }        cout<<"}"<<endl;    }    return 0;}