2020.01.27
题目描述
某公司存在分工问题,一定数量的女员工与男员工搭配,如何搭配才能实现人员的最大利益,即最大匹配问题。
思路解析
- 该问题最初如图,并且存在某些人不能搭配的问题,只考虑能搭配男女之间的搭配
- 其实该问题也相当于一个网络问题,类似容易想到该问题的解决方案,如下图,增加一个源点0和汇点10
- 那么剩下的问题就是如何解决实现的问题,见下一步算法分析;
算法分析
- 那么存储结构如下:
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_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;
}
- 此处采用优化方案,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;
}
- 路径搜索函数,每次从源点开始搜索,并且每一层只找一个顶点即可。当不能往前推进时,存在一下几种情况:
- 存在邻接边时,当前节点的高度等于所有邻接点高度的最小值;
- 不存在邻接边时,当前节点的高度等于节点数,即不用再考虑该顶点;
- 不存在邻接边时,且该高度的顶点只有一个顶点时,结束程序;
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;
}
运行结果
参考
实现参考《趣学算法》