最小费用路算法

        在实际应用中,不仅要考虑流量,还要考虑费用。

        例如在网络布线工程中有很多中电缆,电缆的粗细不同,流量和费用也不同。如果全部使用较粗的电缆,则造价太高;如果全部使用较细的电缆,则流量满足不了要求。我们希望建立一个费用最小、流量最大的网络,即最小费用最大流。

        b57db76339e9493ea453da0f629b70c4.png

         求解最小费用最大流有两种思路:

        (1)先找最小费用路,在该路径上增流,增加到最大流,称为最小费用路算法。

        (2)先找最大流,然后找负费用圈,消减费用,减少到最小费用,称为消圈算法。

        最小费用路算法,是在残余网络上寻找从源点到汇点的最小费用路,即从源点到汇点的以单位费用为权的最短路,然后沿着最小费用路增流,直到找不到最小费用路为止。

        最短增广路算法中求最短增广路是去权值的最短路,而最小费用路是以单位费用为权值的最短路。

        (1)创建混合网络(见《趣学算法》7.3.6节)

        先初始化为零流,零流对应的混合网络中,正向边的容量为cap,流量为0,费用为cost,反向边容量为0,流量为0,费用为−cost。

512cbf9e3c054d919e6eabdd58484d67.png

         (2)找最小费用路

        先初始化每个结点的距离为无穷大,然后令源点的距离dist[v1]=0。在混合网络中,从源点出发,沿可行边(E[i].cap>E[i].flow)广度搜索每个邻接点,如果当前距离dist[v]>dist[u]+ E[i].cost,则更新为最短距离:dist[v]=dist[u]+E[i].cost,并记录前驱。

        根据前驱数组,找到一条最短费用路,增广路径:1—2—5—6,混合网络如图所示。

        bb4d05de372144c8b251e11848093f56.png

        (3)沿着增广路径正向增流d,反向减流d

         从汇点逆向找最小可增流量d=min(d, E[i].cap−E[i].flow),增流量d=3,产生的费用为mincost+=dist[v6]*d=8×3=24,如图所示。 

        ee7ee2d40d984474888d47882a7844cf.png

        继续找最小费用路,增流。直到找不到最小费用路为止。

        4d6f23f91fdd424a8d8beb8fc44862a9.png 

89e9edcde742402fb7ea92c2ef885cbc.png 

        (1)时间复杂度:从算法描述中可以看出,找到一条可增广路的时间是O(E),最多会执行O(VE)次,因为关键边的总数为O(VE),因此总的时间复杂度为O(VE2),其中V为结点个数,E为边的数量。

       (2)空间复杂度:使用了一些辅助数组,空间复杂度为O(V)。 

        

#include<iostream>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
const int inf=0x3f3f3f3f;
const int N=100;
const int M=10000;
int cnt;
int head[N],dist[N],pre[N];//dist[i]表示源点到点i最短距离,pre[i]记录前驱
bool vis[N];//标记数组
int maxflow,mincost;//最大流
struct Edge{
   int v,next;
   int cap,flow,cost;
}E[M<<1];

void init(){//初始化 
    memset(head,-1,sizeof(head));
    cnt=0;
}

void add(int u,int v,int c,int cost){
    E[cnt].v=v;
    E[cnt].cap=c;
    E[cnt].flow=0;
    E[cnt].cost=cost;
    E[cnt].next=head[u];
    head[u]=cnt++;
}

void adde(int u,int v,int c,int cost){
    add(u,v,c,cost);
    add(v,u,0,-cost);
}

bool SPFA(int s,int t,int n){//求最短费用路
    queue<int> q; //队列
    memset(vis,false,sizeof(vis));//访问标记初始化
    memset(pre,-1,sizeof(pre)); //前驱初始化
    memset(dist,0x3f,sizeof(dist));
    vis[s]=true; //标记入队
    dist[s]=0;
    q.push(s);
    while(!q.empty()){
        int u=q.front();
        q.pop();
        vis[u]=false;//队头元素出队,并且消除标记
        for(int i=head[u];~i;i=E[i].next){//访问u的所有邻接点
            int v=E[i].v;
            if(E[i].cap>E[i].flow&&dist[v]>dist[u]+E[i].cost){//松弛操作
                dist[v]=dist[u]+E[i].cost;
                pre[v]=i; //记录前驱边 
                if(!vis[v]){ //顶点v不在队内
                    q.push(v); //入队
                    vis[v]=true; //标记入队
                }
            }
        }
    }
	return pre[t]!=-1;
}

int MCMF(int s,int t,int n){ //minCostmaxFlow
    maxflow=mincost=0;//maxflow为最大流量,mincost为最小费用
    while(SPFA(s,t,n)){//表示找到了从s到t的最短路(可增广路) 
        int d=inf;
        for(int i=pre[t];~i;i=pre[E[i^1].v])//找最小可增流量
            d=min(d,E[i].cap-E[i].flow); 
        for(int i=pre[t];~i;i=pre[E[i^1].v]){//最小费用路逆向操作 
            E[i].flow+=d;//同向边增流 
            E[i^1].flow-=d;//反向边减流
        }
        maxflow+=d; //更新最大流
        mincost+=dist[t]*d; //dist[t]为该路径上单位流量费用之和,更新最小费用
	}
    return mincost;
}

int main(){
    int n,m,u,v,w,c;
    cout<<"请输入结点个数n和边数m:"<<endl;
    cin>>n>>m;
    init();//初始化
    cout<<"请输入两个结点u,v,边(u--v)的容量w,单位容量费用c:"<<endl;
    for(int i=1;i<=m;i++){
        cin>>u>>v>>w>>c;
        adde(u,v,w,c);
    }
    cout<<"网络的最小费用:"<<MCMF(1,n,n)<<endl;
    cout<<"网络的最大流值:"<<maxflow<<endl;
    return 0;
}
/*测试数据 
6 10
1 3 4 7
1 2 3 1
2 5 4 5
2 4 6 4
2 3 1 1
3 5 3 6
3 4 5 3
4 6 7 6
5 6 3 2
5 4 3 3
*/

 

#include<iostream>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
const int inf=0x3f3f3f3f;
const int N=100;
const int M=10000;
int cnt;
int head[N],dist[N],pre[N];//dist[i]表示源点到点i最短距离,pre[i]记录前驱
bool vis[N];//标记数组
int maxflow,mincost;//最大流
struct Edge{
   int v,next;
   int cap,flow,cost;
}E[M<<1];

void init(){//初始化 
    memset(head,-1,sizeof(head));
    cnt=0;
}

void add(int u,int v,int c,int cost){
    E[cnt].v=v;
    E[cnt].cap=c;
    E[cnt].flow=0;
    E[cnt].cost=cost;
    E[cnt].next=head[u];
    head[u]=cnt++;
}

void adde(int u,int v,int c,int cost){
    add(u,v,c,cost);
    add(v,u,0,-cost);
}

bool SPFA(int s,int t,int n){//求最短费用路的SPFA
    queue<int> q; //队列,STL实现
    memset(vis,false,sizeof(vis));//访问标记初始化
    memset(pre,-1,sizeof(pre)); //前驱初始化
    memset(dist,0x3f,sizeof(dist));
    vis[s]=true; //顶点入队vis要做标记
    dist[s]=0;
    q.push(s);
    while(!q.empty()){
        int u=q.front();
        q.pop();
        vis[u]=false;
        //队头元素出队,并且消除标记
        for(int i=head[u];~i;i=E[i].next){//遍历顶点u的邻接表
            int v=E[i].v;
            if(E[i].cap>E[i].flow&&dist[v]>dist[u]+E[i].cost){//松弛操作
                dist[v]=dist[u]+E[i].cost;
                pre[v]=i; //记录前驱
                if(!vis[v]){ //顶点v不在队内
                    q.push(v);   //入队
                    vis[v]=true; //标记
                }
            }
        }
    }
    cout<<"最短路数组"<<endl;
    cout<<"dist[ ]=";
    for(int i=1;i<=n;i++)
       cout<<"  "<<dist[i];
    cout<<endl;
	return pre[t]!=-1;
}

int MCMF(int s,int t,int n){ //minCostMaxFlow
    maxflow=mincost=0;//maxflow 当前最大流量,mincost当前最小费用
    while(SPFA(s,t,n)){//表示找到了从s到t的最短路
        int d=inf;
        cout<<endl;
        cout<<"增广路径:"<<t;
        for(int i=pre[t];~i;i=pre[E[i^1].v]){
            d=min(d,E[i].cap-E[i].flow); //找最小可增流量
            cout<<"--"<<E[i^1].v;
        }
        cout<<"增流:"<<d<<endl;
        cout<<endl;
        maxflow+=d; //更新最大流
        for(int i=pre[t];~i;i=pre[E[i^1].v]){//沿最小费用路增减流 
            E[i].flow+=d;
            E[i^1].flow-=d;
        }
        mincost+=dist[t]*d; //dist[t]为该路径上单位流量费用之和 ,最小费用更新
	}
    return mincost;
}

void printg(int n){//输出网络邻接表
	cout<<"----------网络邻接表如下:----------"<<endl;
	for(int i=1;i<=n;i++){
		cout<<"v"<<i<<"  ["<<head[i];
		for(int j=head[i];~j;j=E[j].next)
			cout<<"]--["<<E[j].v<<"   "<<E[j].cap<<"   "<<E[j].flow<<"   "<<E[j].cost<<"   "<<E[j].next;
		cout<<"]"<<endl;
	}
	cout<<endl;
}

void printflow(int n){//输出实流边
	cout<<"----------实流边如下:----------"<<endl;
	for(int i=1;i<=n;i++)
		for(int j=head[i];~j;j=E[j].next)
			if(E[j].flow>0){
				cout<<"v"<<i<<"--"<<"v"<<E[j].v<<"   "<<E[j].flow<<"   "<<E[j].cost;
				cout<<endl;
			}
}
/*
6 10
1 3 4 7
1 2 3 1
2 5 4 5
2 4 6 4
2 3 1 1
3 5 3 6
3 4 5 3
4 6 7 6
5 6 3 2
5 4 3 3
*/
int main(){
    int n,m;
    int u,v,w,c;
    cout<<"请输入结点个数n和边数m:"<<endl;
    cin>>n>>m;
    init();//初始化
    cout<<"请输入两个结点u,v,边(u--v)的容量w,单位容量费用c:"<<endl;
    for(int i=1;i<=m;i++){
        cin>>u>>v>>w>>c;
        adde(u,v,w,c);
    }
    cout<<endl;
    printg(n);//输出初始网络邻接表
    cout<<"网络的最小费用:"<<MCMF(1,n,n)<<endl;
    cout<<"网络的最大流值:"<<maxflow<<endl;
    cout<<endl;
    printg(n);//输出最终网络
    printflow(n);//输出实流边
    return 0;
}

 

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值