算法很重要,不知道为什么到现在才发现算法的重要性,最近在学习一本很有趣味的书——《算法的乐趣》。为了防止在看完书后就把知识还回去现象的发生,决定在学习的过程中养成每天一篇算法文的习惯。
还记得自己在学校开设的《算法设计与分析》课程中所学的知识,当时使用的教科书好像是一位阿拉伯作者所著,书中的代码均使用伪代码展示,当时觉得伪代码什么的真的是不切实际,到现在才发现,如果你不深入理解算法,那确实会产生一种伪代码不痛不痒的感觉,但是如果你把伪代码中的思路全部理解了之后然后去自己写出真正的代码后会发现伪代码是对代码精炼最好的东西。
闲话不多说了,切入正题。
首先,理清几个名词:匹配,最大匹配,完美匹配,稳定匹配:
都以女孩子找男朋友为例子,阐述上述的几个概念。首先假设男生有一个集合,女生有一个集合,且他们两个集合的数量是相等的。
①匹配:一个女生找到一个男朋友就是一个匹配,这个匹配由一个女生和一个男生构成。
②最大匹配:每个男生和女生只在匹配中出现至多一次,将男生和女生按照某种方法匹配了之后剩下的没有匹配的男生或者女生的数量最少,就说完成了一次最大匹配。
③完美匹配 : 每个男生和女生只在匹配中出现至多一次,且男生和女生正好匹配完了,就说完成了一次完美匹配。
④ 稳定匹配:如果匹配是完美匹配而且匹配中不存在不稳定因素,则称匹配为稳定匹配。不稳定因素的定义如下:比如一组数量相等的男生和女生配对去舞会跳舞,每个男生均有一个自己的喜爱列表,在列表中,他越中意的女生越排在列表的前面,同样,每个女生也有一个自己的偏爱列表,男生在其中的排名越靠前,表明女生越中意这个男生,当完成一次完美匹配后检查匹配的结果,如果存在两对匹配:男生1,他匹配的女伴是11,男生2,他匹配的舞伴是22。但是男生1明明更喜欢女生22,女生22同时也更喜欢男生1。这样的匹配对就称为不稳定因素。只有同时满足是完美匹配而且不存在不稳定因素的匹配才称之为稳定匹配。
匈牙利算法实施的对象是一个二分图,二分图的概念在算法课上也有所介绍,简而言之就是将图中的节点分为两个集合,图中的边的两个端点必须要在不同的集合中。
匈牙利算法的思想用语言描述就是:
假设二分图的节点分为平均分为两部分,分别存储在集合1和集合2中。对于集合1的节点1,先在第二部分找一个和他**有连线**的节点2,并**假设将该节点分配给节点1**,(即节点2被节点1占有了),进一步看节点2是否还是自由节点,当且仅当**结点2是自由节点**或**者虽然节点2不是自由节点,但是将当前匹配做稍许改动后可以将节点2变为自由节点时**,才将节点1与节点2相连。并将count做+1操作,表示完成了一对匹配。对集合1的所有节点进行这个操作,当最后的count值为集合1(或者集合2,因为两部分中节点个数相等)中节点的个数时,该匹配变成完美匹配,进而可以根据不确定因素的描述,对该完美匹配进行不确定因素的搜索,当不存在不确定因素时,此次匹配为稳定匹配。
也可以用增广路径的相关知识来解释,增光路径的一个好的例子我就照搬了:
https://www.cnblogs.com/logosG/p/logos.html
原作者讲的很清楚明了哇,自己差距还很大!
接下来上代码:
#include<iostream>
#include<string>
#include<string.h>
using namespace std;
const int UNIT_COUNT = 5; //二部图中X集合与Yj集合的节点个数
//定义二部图中节点数据结构结构
typedef struct tagPartner {
const char *name;
int current; //当前匹配的节点
int pCount; //与该节点相连的另一个二部图中节点集合中的节点的个数
int perfect[UNIT_COUNT]; //与之有连线的Y节点的编号
}PARTNER;
//定义表示二部图最大匹配的数据结构
typedef struct tagMaxMatch {
int edge[UNIT_COUNT][UNIT_COUNT];
bool supposed_on_path[UNIT_COUNT];
int real_on_path[UNIT_COUNT];
int max_match;
}MAX_MATCH;
PARTNER X[] = {
{"X1",-1, 1, {2} },
{"X2",-1, 2, {0,1} },
{"X3",-1, 3, {1,2,3} },
{"X4",-1, 2, {1,2} },
{"X5",-1, 3, {2,3,4} },
};
PARTNER Y[] = {
{"Y1",-1, 1, {1} },
{"Y2",-1, 3, {1,2,3} },
{"Y3",-1, 4, {0,2,3,4} },
{"Y4",-1, 2, {2,4} },
{"Y5",-1, 1, {4} },
};
bool FindAugmentPath(MAX_MATCH *match, int xi) {
for (int yj = 0; yj < UNIT_COUNT; yj++) {
if ((match->edge[xi][yj] == 1) && (!match->supposed_on_path[yj])) {
match->supposed_on_path[yj] = true;
if ((match->real_on_path[yj] == -1) || (FindAugmentPath(match, match->real_on_path[yj]))) {
match->real_on_path[yj] = xi;
return true;
}
}
}
return false;
}
void Clear_Supposed_On_Path_Sign(MAX_MATCH *match) {
for (int i = 0; i < UNIT_COUNT; i++) {
match->supposed_on_path[i] = false;
}
}
bool Hungry_Match(MAX_MATCH *match) {
for (int xi = 0; xi < UNIT_COUNT; xi++) {
if (FindAugmentPath(match, xi)) {
match->max_match++;
}
Clear_Supposed_On_Path_Sign(match);
}
return (match->max_match == UNIT_COUNT);
}
void InitGraph(MAX_MATCH *match, PARTNER *X, PARTNER *Y) {
match->max_match = 0;
memset(match->edge, 0, sizeof(int)*UNIT_COUNT*UNIT_COUNT);
for (int i = 0; i < UNIT_COUNT; i++) {
match->supposed_on_path[i] = false;
match->real_on_path[i] = -1;
for (int j = 0; j < X[i].pCount; j++) {
match->edge[i][X[i].perfect[j]] = 1;
}
}
}
void PrintResult(MAX_MATCH *match,PARTNER *X,PARTNER *Y) {
for (int i = 0; i < match->max_match; i++) {
cout << X[match->real_on_path[i]].name << " 与之相连的Y点是: " << Y[i].name << endl;
}
}
int main(){
MAX_MATCH match;
InitGraph(&match, X, Y);
if (Hungry_Match(&match)) {
PrintResult(&match, X, Y);
}
return 0;
}
/*
问题描述:有一个二部图,图中的节点分属于两个节点集合,求他们的一个最大匹配,使用匈牙利算法求解二分图的最大匹配问题
数据结构:首先使用PARTNER的数据结构存储一个节点的基本信息,比如节点名字,节点的边有多少条,这些边和另一个几何中的哪些点相连,
求最大匹配的时候将这个节点与哪个节点匹配了的信息。然后使用两个节点的集合来描述二部图中所有节点和边的信息。
再使用叫MAX_MATCH的数据结构来存储当前找到的最大匹配的信息,首先需要记录最大匹配中匹配对数的变量,需要存储边
信息的结构(以便在假设相连的过程中进行判定),需要一个 supposed_on_path的数组来在递归时随着递归的深入将Y节点分配出
去,需要一个real_on_path的数组在完成一次匹配时真正将Y节点与X节点匹配,所以real_on_path这个数组中存贮的其实是真正与
对应下标Y节点相连的X节点的下标。这样所有数据的存储问题就解决了。
算法描述:假设二分图中节点集合有X节点集合与Y节点集合两个集合,算法假设总是从X节点出发去寻找匹配,对于X节点集合中的
**每一个**xi节点,通过edge[][]数组找在Y集合中和他**每一个**相连的yj节点,并从中选出一个yj,先假设将yj节点分配给
当前xi节点,如果yj节点是自由节点,就把yj对应的real_on_path[]数组第j位设置为i,表示yj与xi相连。或者虽然当前的yj
已经通过前面的分配与X集合中的某个节点相连了,但是如果将前面已匹配成功的(xi,yj)对的匹配关系通过冗余的边稍微做改
变可以使它变成自由节点,就把yj对应的real_on_path[]数组第j位设置为i,表示yj与xi相连。每次完成一对匹配就将MAX_MATCH
中的max_match变量加1,(通过当前算法找到一对匹配)如果最后max_match值等于二分图中所有节点数量的一半(这里假设给出
的)图中两个集合中节点数量是相同的,就表明找到了一个完美匹配。(当最大匹配中没有剩余节点时,它将成为完美匹配)。
*/