2017华为软件精英挑战赛之求解最小费用最大流

本文介绍如何通过调整网络链路的流量方向来降低网络租用成本,并给出最小费用流算法的具体实现,包括如何求解最小费用及最大流,并展示如何通过代码实现数据流路径的输出。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

   两个网络节点之间最多仅存在一条链路,链路上下行方向的网络总带宽相互独立,并且上下行方向的总带宽与网络租用费相同。例如对于网络节点A与B之间的链路,该条链路上的总带宽为5,单位租用费为1,则表示A->B、B->A两个方向上的网络总带宽分别为5,并且租用费均为1。如果某条数据流在该链路A->B方向的占用带宽为3,那么该数据流在该链路的租用费为3,并且该链路A->B方向的剩余可用带宽为2。而B->A方向的剩余可用带宽不受该数据流的影响,仍为5。

1. 如图一,假设经过A与B有两条数据流,分别为:

C->A->B->E,流量flow = 3;

F->B->A->D,流量flow = 2;

图一:

那么在A与B之间的这条链路的花费为(3+2)*1 = 5;

再来看图二,图中有三条数据流,分别为:

C->A->D,流量flow = 2;

C->A->B->E,流量flow = 1;

F->B->E,流量flow = 2;

图二:


2与图1相比,效果一样,但在A与B之间链路的花费仅为1*1 = 1;

因此,如果链路的上下行方向都有流量,会增加成本,造成浪费。


2.问题来了,怎么避免两个方向都有流量呢。为方便说明,举个例子。

看下面内容之前,务必先了解一下最大流和最小费用的基本算法。


设A->B、B->A两个方向上容量都为5,并且单位流量费用均为1。总花费为zongcost。

A->B方向的初始值:ABflow = 0;ABcap = 5;ABcost = 1;

B->A方向的初始值:BAflow = 0;BAcap = 5;BAcost = 1;

第一步:A->B方向流过3个单位流量。

Zongcost = 3 * ABcost =3;

A->B方向:ABflow = 3;ABcap = 5;ABcost = 1;

ABcap - ABflow 等于2,说明A->B方向还可以流2个单位流量。

B->A方向:BAflow = -3;BAcap = 0;BAcost = -1;

   把BAcap 设为0,BAcap - BAflow 等于3,表示B->A方向可以流过3个单位流量,而且单价还是负的,这样,下次B->A方向有流量经过时就可以先抵消掉在A->B方向上流量花费的费用。

第二步:B->A方向流过3个单位的流量。

Zongcost = Zongcost + 3 * BAcost =0;

B->A方向:BAflow = -3+3 = 0;BAcap = 5;BAcost = 1;

可以发现这时B->A方向和初始状态的值时一样的,可以流过5个单位的流量,单价为1.

A->B方向:ABflow = 3 - 3 = 0;ABcap = 5;ABcost = 1;

也初始状态一样。

第三步:B->A方向流过1个单位的流量。

Zongcost = Zongcost + 1 * BAcost =1;

B->A方向:BAflow = 0+1 = 0;BAcap = 5;BAcost = 1;

A->B方向:ABflow = 0- 1 = -1;ABcap = 0;ABcost = -1;

通过总结以上三个步骤可以发现规律:

if(flow<0)   {cap = 0; cost = 负;}

else  { cap = 5; cost = 正}

表格的形式看的更直观一些。

 

A->B

B->A

 

ABflow

ABcap

ABcost

BAflow

BAcap

BAcost

初始值

0

5

1

0

5

1

第一步后

3

5

1

-3

0

-1

第二步后

0

5

1

0

5

1

第三步后

-1

0

-1

1

5

1


3.了解了上面的内容,就可以结合最小费用流算法求出最小费用,代码如下:

#include <string>
#include <iostream>
#include <stack>
#include <vector>
#include <queue>

using namespace std;

const int INF = 0x3f3f3f3f;
int maxData = 0x3fffffff;

stack<int> st; //为了显示输出路径
queue<int> myqueue;
int pre[MaxNum];//标记路径上当前节点的前驱

//边的结构体
struct ArcNodeType
{
    int adjvex;
    int cap;
    int flow;
    int cost;
    int fixcap;
    int fixcost;
    ArcNodeType *nextarc;
};

void AlterFlow(int s,int t,int increase) {
    while (!st.empty())       //栈清空
        st.pop();

    int k = t;          //利用前驱寻找路径
    st.push(k);
    ArcNodeType *p;
    while (k != s) {
        int last = pre[k];
        //改变正向边的流量
        p = VexList[last].firstarc;
        while (p->adjvex != k)//直到p->data为key为止
            p = p->nextarc;//扫描下一结点
        p->flow += increase;
        if (p->flow < 0) {
            p->cap= 0;
            p->cost = -(p->fixcost);
        }
        else {
            p->cap= p->fixcap;
            p->cost = p->fixcost;
        }
        //改变反向边的流量,反向边不一定存在
        p = VexList[k].firstarc;
        while (p && p->adjvex != last)
            p = p->nextarc;//扫描下一结点
        if (p) //  存在并找到
        {
            p->flow -= increase;
            if (p->flow < 0) {
                p->cap= 0;
                p->cost = -(p->fixcost);
            }
            else {
                p->cap = p->fixcap;
                p->cost = p->fixcost;
            }

        }

        k = last;
        st.push(k);
    }
}

int BellmanFord(int src, int des,int maxData,long &cost)
{
    int d[MaxNum];//Bellman-Ford
    int a[MaxNum];//可改进量
    int inq[MaxNum];//是否在队列中

    while (!myqueue.empty())       //队列清空
        myqueue.pop();
    for (int i = 0; i<1502; i++)
    {d[i] = INF; inq[i] = 0; }         //本来就很大
    a[src] = maxData;
    d[src] = 0;
    inq[src] = 1;
    myqueue.push(src);
    while (!myqueue.empty())
    {
        int index = myqueue.front();
        myqueue.pop();  //zk 遍历过就出栈不占地方
        inq[index] = 0;       //不在了
        ArcNodeType *p=VexList[index].firstarc;
        while(p)
        {
            int adjvex=p->adjvex;
            if(d[adjvex] > d[index] + p->cost && p->cap > p->flow)  //松弛操作
            {
                d[adjvex] = d[index] + p->cost;
                pre[adjvex] = index; //记录前驱
                a[adjvex] = min(p->cap - p->flow, a[index]);//更新可改
                if (!inq[adjvex])
                {
                    myqueue.push(adjvex);   //防止该点遍历两次
                    inq[adjvex] = 1;
                }

            }
            p=p->nextarc;
        }

    }
    if (d[des] == INF)//仍为初始时候的INF,s-t不连通,失败退出
        return 0;
    cost += (long)d[des] * a[des];//d[t]一方面代表了最短路长度,另一方面代表了这条最短路的单位费用的大小
    return a[des];

}
//调用该函数就可以得到最小花费cost
Min_Cost(int s,int t,long &cost)
{
    cost = 0;
    int f = 0;
    for(;;)
    {
        for(int i=0;i<1502;i++) pre[i]=-1;
        pre[s]=0;  //0也代表遍历到
        f=BellmanFord(s,t,INF,cost);
        if(f==0)
            break;
        AlterFlow(s,t,f);
    }
}

4.算出了最小费用我们还要求最大流,并输出数据流路径,可以用dfs深度遍历搜索,代码如下:

stack<int> st; //为了显示输出路径
int pre[MaxNum];//标记在这条路径上当前节点的前驱

// 用来搜索路径
int dfs(int s,int t,int f)
{

    if(s==t)
    {
        return f;
    }

    ArcNodeType *p=VexList[s].firstarc;
    while(p)
    {
        int adjvex=p->adjvex;
        if(pre[adjvex] == -1 && p->flow>0)
        {
            pre[adjvex] = s; //记录前驱
            int d= dfs(adjvex,t,min(f,p->flow));

            if(d>0)
            {
                p->flow-=d;
                return d;
            }
        }
        p=p->nextarc;
    }
    return 0;
}

void DisplayRoad(int s,int t)
{
    while (!st.empty())       //栈清空
        st.pop();
    int k = t;          //利用前驱寻找路径
    st.push(k);
    ArcNodeType *p;
    while (k != s) {
        int last = pre[k];
        k = last;
        st.push(k);
    }
    int j = st.size();
    for (int i = 0; i < j; i++)
    {
        if (st.top() == s || st.top() == t)
        {
            st.pop();
            continue;
        }
        else
        {
            cout<<st.top()<<" ";
        }
        st.pop();
    }
}
int max_flow(int s,int t)
{
    int flow=0;
    roadnum = 0; //每次路径数归零
    int f = 0;
    for(;;)
    {
        for (int i = 0; i < 1502; i++) pre[i] = -1;
        pre[s] = 0;  //0也代表遍历到
        f = dfs(s, t, INF);
        if (f == 0)
            return flow;
        flow += f;
        roadnum++;
        DisplayRoad(s,t);
    }
}
如果想获得更完整的代码,可以到此处下载。

http://download.youkuaiyun.com/download/lzhf1122/9808461


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值