线性规划--最小费用最大流

假期 2020.01.24

题目描述

在网络布线的工程中,有许多电缆,而电缆的粗细不同,流量与费用也不一样,那么如何安排才能获得费用最小且流量最大的网络呢?


思路分析

因为要满足两个条件,那么我们可从两大方面入手:

  1. 费用最小,我们可以先找到费用最小的网路线,然后在该路径上面增加流量到最大值即可。
  2. 也可以先寻找最大流量,然后找负值的圈,因为这样子可以在一定程度上减少费用,直到减少到最小即可,即不再存在无关的负值圈,都是剩下的必要的路径了。

算法分析

  1. 存储结构:
int expense_choice[Max_size], pre_choice[Max_size];//最短距离,前驱
int visited[Max_size],visited_count[Max_size];//标记访问数组,入队次数
int max_flow;//最大流
int num;//编号
struct Vertex {//邻接表头节点
	int first;
}vertex[Max_size];
struct Edge {
	int start,next;//弧尾,下一条邻接边
	int contain, flow, expense;//边的最大容量,边的流量,边的花费
}edge[Max_size]; 
  1. 初始化,其中添加边的时候,是添加双向边,输入时需要注意,主体流向都是从顶点小的编号指向顶点大的编号的边,否则输出结果不正确,因为,我们要确定一个流向。
	memset(vertex, -1, sizeof(vertex));//邻接表表头初始化为-1
	cout << "请输入两个节点(v,w)和两者之间的容量与花费(每项共4个数据):" << endl;
	for (int i = 1; i <= edge_count; i++)
	{
		cin >> v >> w >> we >> e;
		add(v, w, we, e);
	}
	cout << endl;
	max_flow = 0;
	num = 0;

添加边,num作为全部边的编号,从0开始

void add_edge(int v, int w, int we, int e)//加边,赋值
{
	edge[num].start = w;//弧头
	edge[num].contain = we;//容量
	edge[num].expense = e;//花费
	edge[num].flow = 0;//流量
	edge[num].next = vertex[v].first;//下一条边序号
	vertex[v].first = num ++;//边的序号编号
	return ;
}
  1. int Search_flow(int vertex_count);//寻找路径函数;仍然利用bfs算法实现,其中判断条件是该边的容量仍然大于当前的流量值,并且当当前这条弧的弧头的花费存在更优值,就更新到该点的花费并该点入队。
    而判断负值圈的意义在于,因为存在负值边,而我们所求的是最小值,那么因为存在负值边,会使程序在此处循环无解。注意前驱数组存的是每一个顶点的前面一条弧的编号,而不是顶点编号。
if (edge[i].contain > edge[i].flow && expense_choice[cur] > expense_choice[temp] + edge[i].expense)//存在可行解
			{
				expense_choice[cur] = expense_choice[temp] + edge[i].expense;//更新到cur点的花费值
				pre_choice[cur] = i;//cur的前驱更新
				if (visited[cur] == 0)
				{
					visited_count[cur]++;//访问次数增加一
					q.push(cur);//入队
					visited[cur] = 1;//标记
					if (visited_count[cur] > vertex_count)//存在负环
						return 0;
				}
			}
  1. int best_expaflo(int i, int vertex_count);//处理路径函数;其中使用i^1(或 i + 1) 的目的是因为我们在存储的时候采取的是正向为i,那么逆向是i+1,,因此利用这个特点就可以知道某边的两个顶点编号。
for (i = pre_choice[vertex_count]; i != -1; i = pre_choice[edge[i ^ 1].start])//使用i^1的目的是因为我们在存储的时候采取的是正向为i,那么逆向是i+1
{
	current_cost = min(current_cost, edge[i].contain - edge[i].flow);
	cout << "--" << edge[i ^ 1].start;
}

代码解析

#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;
constexpr int INF = 0x7fffffff;
constexpr int Max_size = 100;
int expense_choice[Max_size], pre_choice[Max_size];//最短距离,前驱
int visited[Max_size],visited_count[Max_size];//标记访问数组,入队次数
int max_flow;//最大流
int num;//编号
struct Vertex {//邻接表头节点
	int first;
}vertex[Max_size];
struct Edge {
	int start,next;//弧尾,下一条邻接边
	int contain, flow, expense;//边的最大容量,边的流量,边的花费
}edge[Max_size]; 
void Print(int vertex_count);//输出初始邻接表
void Print_flow(int vertex_count);//输出实际网络流路径
void add_edge(int v, int w, int we, int e);//加边,赋值
void add(int v, int w,int we,int e);//因为是网络混合图,即双向流动,所以两者都需定义
int Search_flow(int vertex_count);//寻找路径
int best_expaflo(int i, int vertex_count);//处理路径
int main()
{
	int vertex_count,edge_count,v,w,we,e;
	cout << "请输入邻接点个数:";
	cin >> vertex_count; 
	cout << "请输入邻接边数:";
	cin >> edge_count;
	memset(vertex, -1, sizeof(vertex));//邻接表表头初始化为-1
	cout << "请输入两个节点(v,w)和两者之间的容量与花费(每项共4个数据):" << endl;
	for (int i = 1; i <= edge_count; i++)
	{
		cin >> v >> w >> we >> e;
		add(v, w, we, e);
	}
	cout << endl;
	max_flow = 0;
	num = 0;
	Print(vertex_count);//输出初始邻接表
	cout << "网络的最小花费是: " << best_expaflo(1, vertex_count) << endl;
	cout << "网络的最大流是:" << max_flow << endl;
	Print(vertex_count);//输出最后邻接表
	Print_flow(vertex_count);
	return 0;
}
int Search_flow(int vertex_count)
{
	int i, temp,cur;
	queue<int> q;//创建队列
	/*初始化*/
	memset(visited, 0, sizeof(visited));
	memset(pre_choice, -1, sizeof(pre_choice));
	memset(visited_count, 0, sizeof(visited_count));
	for (i = 1; i <= vertex_count; i++)
		expense_choice[i] = INF;
	/*首节点处理*/
	visited[1] = 1;
	visited_count[1] ++;
	expense_choice[1] = 0;
	q.push(1);
	while (!q.empty())//队列为空时停止
	{
		temp = q.front();//出队首元素
		q.pop();
		visited[temp] = 0;//访问数组复原
		for (i = vertex[temp].first; i != -1; i = edge[i].next)//与temp点相连的边的分析
		{
			cur = edge[i].start; //弧头,如u-->v,其中v叫弧头,u叫弧尾
			if (edge[i].contain > edge[i].flow && expense_choice[cur] > expense_choice[temp] + edge[i].expense)//存在可行解
			{
				expense_choice[cur] = expense_choice[temp] + edge[i].expense;//更新到cur点的花费值
				pre_choice[cur] = i;//cur的前驱更新
				if (visited[cur] == 0)
				{
					visited_count[cur]++;//访问次数增加一
					q.push(cur);//入队
					visited[cur] = 1;//标记
					if (visited_count[cur] > vertex_count)//存在负环
						return 0;
				}
			}
		}
	}
	/*输出每次查找到的最优解*/
	cout << "最短路数组:" << endl;
	cout << " expense_choice [ ] = ";
	for (i = 1; i <= vertex_count; i++)
		cout << "  " << expense_choice[i];
	cout << endl;
	if (expense_choice[vertex_count] == INF)//到目的地的花费为无穷,即表示无法达到目的地,则结束整个程序
		return 0;
	return 1;
}
int best_expaflo(int i, int vertex_count)
{
	int current_cost,min_cost = 0;//当前花费,最小花费
	while (Search_flow(vertex_count))//存在解时,继续搜索
	{
		current_cost = INF;
		cout << endl;
		cout << "增广路径:" << vertex_count;
		for (i = pre_choice[vertex_count]; i != -1; i = pre_choice[edge[i ^ 1].start])//使用i^1的目的是因为我们在存储的时候采取的是正向为i,那么逆向是i+1
		{
			current_cost = min(current_cost, edge[i].contain - edge[i].flow);
			cout << "--" << edge[i ^ 1].start;
		}
		cout << "增流:" << current_cost << endl << endl;
		max_flow += current_cost;
		for (i = pre_choice[vertex_count]; i != -1; i = pre_choice[edge[i ^ 1].start])
		{
			edge[i].flow += current_cost;//正向增加
			edge[i ^ 1].flow -= current_cost;//反向减少
		}
		min_cost += expense_choice[vertex_count] * current_cost;//计算当前流去汇点的费用
	}
	return min_cost;
}

void Print(int vertex_count)//输出初始邻接表
{
	cout << "网络连接表如下:" << endl;
	for (int i = 1; i <= vertex_count; i++)
	{
		cout << "v" << i << "  [" << vertex[i].first;
		for (int j = vertex[i].first; j != -1; j = edge[j].next)
			cout << "] -- [" << edge[j].start << "  " << edge[j].contain << "  " << edge[j].flow << "  " << edge[j].expense << "  " << edge[j].next;
		cout << "]" << endl;
	}
	cout << endl;
	return ;
}
void Print_flow(int vertex_count)
{
	cout << "实流边如下:" << endl;
	for(int i = 1;i <= vertex_count;i++)
		for (int j = vertex[i].first; j != -1; j = edge[j].next)
		{
			if (edge[j].flow > 0)
			{
				cout << "v" << i << "--" << "v" << edge[j].start << "  " << edge[j].flow << "  " << edge[j].expense;
				cout << endl;
			}
		}
	return;
}
void add_edge(int v, int w, int we, int e)//加边,赋值
{
	edge[num].start = w;//弧头
	edge[num].contain = we;//容量
	edge[num].expense = e;//花费
	edge[num].flow = 0;//流量
	edge[num].next = vertex[v].first;//下一条边序号
	vertex[v].first = num ++;//边的序号编号
	return ;
}
void add(int v, int w,int we,int e)//因为是网络混合图,即双向流动,所以两者都需定义
{
	add_edge(v, w, we, e);//正向,价格为正
	add_edge(w, v, 0, -e);//逆向,价格为负
	return ;
}

测试数据

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

运行结果

在这里插入图片描述


参考

实现借鉴《趣学算法》

发布了166 篇原创文章 · 获赞 45 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_44116998/article/details/104085546
今日推荐