线性规划 --配对方案问题

2020.01.27

题目描述

某公司存在分工问题,一定数量的女员工与男员工搭配,如何搭配才能实现人员的最大利益,即最大匹配问题。


思路解析

  1. 该问题最初如图,并且存在某些人不能搭配的问题,只考虑能搭配男女之间的搭配
    在这里插入图片描述在这里插入图片描述
  2. 其实该问题也相当于一个网络问题,类似容易想到该问题的解决方案,如下图,增加一个源点0和汇点10
    在这里插入图片描述
  3. 那么剩下的问题就是如何解决实现的问题,见下一步算法分析;

算法分析

  1. 那么存储结构如下:
int pre_num[Max_size];//前驱边
int current_num;//当前边的编号
int _floor[Max_size], g[Max_size];//记录每一个节点的高度,记录距离为_floor[]的节点个数
struct Vertex {
	int first;
}vertex[Max_size];
struct Edge {
	int start;//弧头
	int next;//下一条邻接边
	int contain, flow;//容量,流量
}edge[Max_size];
  1. 加边
void add_edge(int u, int v,int c)
{
	edge[current_num].start = v;//弧头
	edge[current_num].flow = 0;//流量
	edge[current_num].contain = c;//容量
	edge[current_num].next = vertex[u].first;
	vertex[u].first = current_num++;
	return;
}

void add(int u, int v)
{
	add_edge(u, v,1);//正向 
	add_edge(v, u,0);//逆向 
	return;
}
  1. 此处采用优化方案,ISAP实现,那么设置高度。为什么设置高度呢,其实为了避免不必要的搜索,高度为每一个节点到汇点的层数,即隔了几个顶点。便于每次查找的时候,每一层,我们只需要一个顶点即可,那么相同层次的顶点只使用一个,就可以避免其他不需要的顶点的搜索。注意定义第一层为0层
void set_height(int t, int n)
{
	queue<int> q;
	int current;//层次
	memset(_floor, -1, sizeof(_floor));
	memset(g, 0, sizeof(g));
	_floor[t] = 0;
	q.push(t);
	while (!q.empty())
	{
		current = q.front();//出队 
		q.pop();
		++g[_floor[current]];
		for (int i = vertex[current].first; i != -1; i = edge[i].next)
		{
			int u = edge[i].start;
			if (_floor[u] == -1)//初始化与之相邻但先前未被初始化的顶点的层数 
			{
				_floor[u] = _floor[current] + 1;
				q.push(u);//入队 
			}
		}
	}
	cout<<endl;
	cout << "初始化高度:" << endl;
	cout << "_floor[ ] =";
	for (int i = 1; i <= n; i++)
		cout << "  " << _floor[i];
	cout << endl;
	return;
}
  1. 路径搜索函数,每次从源点开始搜索,并且每一层只找一个顶点即可。当不能往前推进时,存在一下几种情况:
  • 存在邻接边时,当前节点的高度等于所有邻接点高度的最小值;
  • 不存在邻接边时,当前节点的高度等于节点数,即不用再考虑该顶点;
  • 不存在邻接边时,且该高度的顶点只有一个顶点时,结束程序;
int Isap(int s, int t,int n)
{
	set_height(t, n);//设置层数 
	int max_flow = 0, u = s;//源点
	int d = INF;
	while (_floor[s] < n)//当层数大于等于顶点数时 ,退出,因为最大层数是顶点数 ,即参数t 
	{
		int i = vertex[u].first;//顶点的第一条邻接边 
		for (; i != -1; i = edge[i].next)//与顶点i相邻的边 
		{
			int v = edge[i].start;
			if (edge[i].contain > edge[i].flow && _floor[u] == _floor[v] + 1)//存在可行流量且层数相邻 
			{
				u = v;
				pre_num[v] = i;//前驱边 
				d = min( d, edge[i].contain - edge[i].flow );//找最小流 
				if (u == t)//找到汇点时 
				{
					cout << endl;
					cout << "增广路径:" << t;
					while (u != s)
					{
						int j = pre_num[u];
						edge[j].flow += d;
						edge[j + 1].flow -= d;
						u = edge[j + 1].start;
						cout << "--" << u;
					}
					cout<<endl;
					cout << "增流:" << d << endl;
					max_flow += d;
					d = INF;
				}
				break;//因为每一次每一层只寻找一个顶点,达到每次只寻找一条路径的目的 
			}
		}
		if (i == -1)//当前节点无法行进时 
		{
			if (--g[_floor[u]] == 0)//只有一个顶点,退出 
				break;
			int hmin = t;//初始为层数最大值 ,或者·1没有·邻接边时,当前节点的高度等于节点数 
			for (int j = vertex[u].first; j != 0; j = edge[j].next)//当前节点高度等于所有与之邻接点高度的最小值+1
				if (edge[j].contain > edge[j].flow)
					hmin = min(hmin, _floor[edge[j].start]);
			_floor[u] = hmin + 1;
			cout<<endl;
			cout << "重贴标签后高度" << endl;
			cout << "_floor[ ] = ";
			for (int i = 1; i <= n; i++)
				cout << "  " << _floor[i];
			cout << endl << endl;
			++g[_floor[u]];//该层次的顶点数增加 
			if (u != s)
				u = edge[pre_num[u] + 1 ].start;
			else//源点时,从最初开始计算 
				d = INF; 
		}
	}
	return max_flow;
}

代码解析

#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;
constexpr auto Max_size = 100;
constexpr auto INF = 0x7fffffff;
int pre_num[Max_size];//前驱边
int current_num;//当前边的编号
int _floor[Max_size], g[Max_size];//记录每一个节点的高度,记录距离为_floor[]的节点个数
struct Vertex {
	int first;
}vertex[Max_size];
struct Edge {
	int start;//弧头
	int next;//下一条邻接边
	int contain, flow;//容量,流量
}edge[Max_size];
void add(int u, int v);//加边
void add_edge(int u, int v, int c);
void Print(int ple_total);//输出邻接网络
void Print_flow(int ple_total);////输出最终方案
int Isap(int s, int t,int n);
void set_height(int t, int n);
int main()
{
	int male_num, female_num,ple_total = 0,u,v;//男性数量,女性数量
	cout << "请输入male_num与female_num: ";
	cin >> male_num >> female_num;
	ple_total = female_num + male_num;//总人数
	memset(vertex, -1, sizeof(vertex));//初始化
	current_num = 0;
	for (int i = 1; i <= female_num; i++)//源点到女推销员
		add(0, i);
	for (int j = female_num + 1; j <= ple_total; j++)//男推销员到汇点
		add(j, ple_total + 1);
	cout << "请输入可以匹配的员工编(以 0 0 结束程序输入):" << endl;
	while (cin >> u >> v, u + v != 0)
		add(u, v);
	cout << endl;
	Print(ple_total + 2);//总顶点数,输出最初邻接网络
	cout << endl;
	cout << "最大匹配是:" << Isap(0, ple_total + 1, ple_total + 2) << endl;
	Print(ple_total + 2);//输出最终邻接网络
	Print_flow(female_num);//输出最终方案
	return 0;
}

void add_edge(int u, int v,int c)
{
	edge[current_num].start = v;//弧头
	edge[current_num].flow = 0;//流量
	edge[current_num].contain = c;//容量
	edge[current_num].next = vertex[u].first;
	vertex[u].first = current_num++;
	return;
}

void add(int u, int v)
{
	add_edge(u, v,1);//正向 
	add_edge(v, u,0);//逆向 
	return;
}

int Isap(int s, int t,int n)
{
	set_height(t, n);//设置层数 
	int max_flow = 0, u = s;
	int d = INF;
	while (_floor[s] < n)//当层数大于等于顶点数时 ,退出,因为最大层数是顶点数 ,即参数t 
	{
		int i = vertex[u].first;//顶点的第一条邻接边 
		for (; i != -1; i = edge[i].next)//与顶点i相邻的边 
		{
			int v = edge[i].start;
			if (edge[i].contain > edge[i].flow && _floor[u] == _floor[v] + 1)//存在可行流量且层数相邻 
			{
				u = v;
				pre_num[v] = i;//前驱边 
				d = min( d, edge[i].contain - edge[i].flow );//找最小流 
				if (u == t)//找到汇点时 
				{
					cout << endl;
					cout << "增广路径:" << t;
					while (u != s)
					{
						int j = pre_num[u];
						edge[j].flow += d;
						edge[j + 1].flow -= d;
						u = edge[j + 1].start;
						cout << "--" << u;
					}
					cout<<endl;
					cout << "增流:" << d << endl;
					max_flow += d;
					d = INF;
				}
				break;//因为每一次每一层只寻找一个顶点 
			}
		}
		if (i == -1)//当前节点无法行进时 
		{
			if (--g[_floor[u]] == 0)//只有一个顶点,退出 
				break;
			int hmin = t;//初始为层数最大值 ,或者·1没有·邻接边时,当前节点的高度等于节点数 
			for (int j = vertex[u].first; j != 0; j = edge[j].next)//当前节点高度等于所有与之邻接点高度的最小值+1
				if (edge[j].contain > edge[j].flow)
					hmin = min(hmin, _floor[edge[j].start]);
			_floor[u] = hmin + 1;
			cout<<endl;
			cout << "重贴标签后高度" << endl;
			cout << "_floor[ ] = ";
			for (int i = 1; i <= n; i++)
				cout << "  " << _floor[i];
			cout << endl << endl;
			++g[_floor[u]];//该层次的顶点数增加 
			if (u != s)
				u = edge[pre_num[u] + 1 ].start;
			else//源点时,从最初开始计算 
				d = INF; 
		}
	}
	return max_flow;
}

void set_height(int t, int n)
{
	queue<int> q;
	int current;//层次
	memset(_floor, -1, sizeof(_floor));
	memset(g, 0, sizeof(g));
	_floor[t] = 0;
	q.push(t);
	while (!q.empty())
	{
		current = q.front();//出队 
		q.pop();
		++g[_floor[current]];
		for (int i = vertex[current].first; i != -1; i = edge[i].next)
		{
			int u = edge[i].start;
			if (_floor[u] == -1)//初始化与之相邻但先前未被初始化的顶点的层数 
			{
				_floor[u] = _floor[current] + 1;
				q.push(u);//入队 
			}
		}
	}
	cout<<endl;
	cout << "初始化高度:" << endl;
	cout << "_floor[ ] =";
	for (int i = 1; i <= n; i++)
		cout << "  " << _floor[i];
	cout << endl;
	return;
}
void Print(int ple_total)
{
	cout << "网络连接图如下:" << endl;
	for (int i = 0; i <= ple_total; 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].next;
		cout << "]" << endl;
	}
	cout << endl;
	return;
}

void Print_flow(int ple_total)
{
	cout << "匹配方案如下:" << endl;
	for(int i = 1;i <= ple_total;i++)//女 
		for(int j = vertex[i].first;j != -1;j = edge[j].next)//男 
			if (edge[j].flow > 0)
			{
				cout << "女" << i << " -- " << "男" << edge[j].start << endl;
				break;
			}
	return;
}

运行结果

在这里插入图片描述
在这里插入图片描述


参考

实现参考《趣学算法》

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

猜你喜欢

转载自blog.csdn.net/qq_44116998/article/details/104091531