Time:2020.01.16
这是一篇以“工作记录——2”为基础的,实现匈牙利算法的工作记录。
程序是参照二分图匹配实现的,他的前提就是已经确定两个集合之间的吸引情况,即0或者1。但是我通过几何距离算法得到的是两个集合之间的距离,所以我主观的将最小的和次之的数设置为1,其余的设置为0。总感觉这种方法带入了主观判断,有一些缺陷,因此接下来要考虑对数组采用“指派法”或者“回溯法”实现。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <string.h>
using namespace std;
#define MAXN 990 //MAXN表示X集合和Y集合顶点个数的最大值
int nx,ny; //x和y集合中顶点的个数
int g[MAXN][MAXN]; //邻接矩阵,g[i][j]为1表示有连接
int cx[MAXN],cy[MAXN]; //cx[i],表示最终求得的最大匹配中,与x集合中元素Xi匹配的集合Y中顶点的索引
//cy[i],表示最终求得的最大匹配中,与y集合中元素Yi匹配的集合X中顶点的索引
//DFS算法中记录顶点访问状态的数据mk[i]=0表示未访问过,为1表示访问过
int mk[MAXN];
//从集合X中的定顶点u出发,用深度有限的策略寻找增广路
//这种增广路只能是当前的匹配数增加1
int path(int u){
for(int v=0;v<ny;++v){ //考虑所有Yi顶点v
if(g[u][v] && !mk[v]){ //Y中顶点v与u邻接,且没有访问过
mk[v]=1; //访问v
//如果v没有匹配,则直接将v匹配给u,如果v已经匹配了,但是从cy[v],也就是从v之前已经匹配的x出发,找到一条增广路,但是这里记住这里v已经记录访问过了
//如果第一个条件成立,则不会递归调用
if(cy[v]==-1 || path(cy[v])){
cx[u]=v; //把Y中v匹配给X中u
cy[v]=u; //把X中u匹配给Y中v
return 1;
}
}
}
return 0; //如果不存在从u出发的增广路,则返回0
}
int maxMatch(){ //求二分图最大匹配的匈牙利算法
int res=0;
memset(cx,-1,sizeof(cx)); //从0匹配开始增广,将cx和xy各元素都初始化为-1
memset(cy,-1,sizeof(cy));
for(int i=0;i<nx;++i){
if(cx[i]==-1){ //从X集合中每个没有匹配的点出发开始寻找增广路
memset(mk,0,sizeof(mk));
res+=path(i);
}
}
return res;
}
int main() {
nx=5;
ny=5;
int b=120,a=50;
vector<vector<double>> H(nx,vector<double>(ny));
// vector<vector<double>> g(nx,vector<double>(ny)); //这里与起始的g冲突了,因此一直实现错误,找不到可以匹配的对象,因此在这里把其注释掉,且其类型可改成int型,即使用起始位置定义的g[][]
double array[ny];
for(int i=0;i<nx;i++){
for(int j=0;j<ny;j++){
H[i][j]=0.01+(rand()%(b-a+1)+a);
cout<<"H["<<i<<"]"<<"["<<j<<"]"<<H[i][j]<<endl;
array[j]=H[i][j];
}
double m1,m2;//存储两个最小值
m1=9999;
m2=9999;
for(int k=0;k<ny;k++){
if(array[k]<m1){
m2=m1;
m1=array[k];
}
else if(array[k]<m2){
m2=array[k];
}
}
for(int l=0;l<ny;l++){
if(array[l]==m1){
//array[l]=1;
g[i][l]=1;
}
else if(array[l]==m2){
//array[l]=1;
g[i][l]=1;
}
else{
g[i][l]=0;
}
}
}
/* for(int m=0;m<nx;m++){
for(int n=0;n<ny;n++){
cout<<"g["<<m<<"]"<<"["<<n<<"]"<<g[m][n]<<endl;
}
}
//输出看一下g是否是自己想要的形式
*/
/*
g[0][0]=1; g[0][1]=1; g[0][2]=0;g[0][3]=0; g[0][4]=0;
g[1][0]=0; g[1][1]=1; g[1][2]=1;g[1][3]=0; g[1][4]=0;
g[2][0]=0; g[2][1]=1; g[2][2]=0;g[2][3]=1; g[2][4]=0;
g[3][0]=0; g[3][1]=0; g[3][2]=1;g[3][3]=1; g[3][4]=0;
g[4][0]=1; g[4][1]=0; g[4][2]=1;g[4][3]=0; g[4][4]=0;
g[5][0]=0; g[5][1]=1; g[5][2]=0;g[5][3]=1; g[5][4]=1;
*/
int num= maxMatch();
cout<<"num="<<num<<endl;
for(int num=0;num<ny;++num){
cout<<"cx["<<num+1<<"] -->> "<<cx[num]+1<<endl;
}
return 0;
}
后来根据算法原理,整理的程序。
#define MAXN 990 //MAXN表示X集合和Y集合顶点个数的最大值
int nx,ny; //x和y集合中顶点的个数
int g[MAXN][MAXN]; //邻接矩阵,g[i][j]=1表示有连接
int cx[MAXN],cy[MAXN]; //cx[i],表示最终求得的最大匹配中,与x集合中元素Xi匹配的集合Y中顶点的索引
//cy[i],表示最终求得的最大匹配中,与y集合中元素Yi匹配的集合X中顶点的索引
int mk[MAXN]; //DFS算法中记录顶点访问状态的数据mk[i]=0表示未访问过,为1表示访问过,标记的永远是二分图右边的顶点。
//从集合X中的定顶点u出发,用深度有限的策略寻找增广路
//这种增广路只能是当前的匹配数增加1
int path(int u){ //传入的是左侧的顶点
for(int v=0;v<ny;++v){ //考虑所有右侧顶点,从第一个开始扫描,依次向下
if(g[u][v] && !mk[v]){ //如果左侧有右侧某顶点相连接,并且这个右侧顶点没有被此时的左侧顶点访问过
mk[v]=1; //标记右侧顶点,访问v
//接下来,判断这个右侧顶点。
//如果v没有匹配,则直接将v匹配给u。或者,如果v已经匹配了,但是从cy[v],也就是从v之前已经匹配的x出发,找到一条增广路,但是这里记住这里v已经记录访问过了
//如果第一个条件成立,则不会递归调用
if(cy[v]==-1 || path(cy[v])){ //path()是一个递归更新的过程,其返回值是1或者0;
//如果满足上述条件,更新cx cy。
cx[u]=v; //把Y中v匹配给X中u
cy[v]=u; //把X中u匹配给Y中v
return 1; //找到左侧点的匹配值,那么将返回1
}
}
}
return 0; //如果不存在从u出发的增广路,则返回0
//具体的说:如果左侧点找不到一个匹配点,那么将会返回0,此时path(cy[v])=0,就不会将右侧点与之前匹配的点拆散,而是左侧点重新依次向下寻找右侧链接顶点
}
int maxMatch(){ //求二分图最大匹配的匈牙利算法;
int res=0;
memset(cx,-1,sizeof(cx)); //从0匹配开始增广,将cx和xy各元素都初始化为-1;
memset(cy,-1,sizeof(cy));
for(int i=0;i<nx;++i){ //开始依次从左侧顶点匹配;
if(cx[i]==-1){ //从X集合中每个没有匹配的点出发开始寻找增广路;
memset(mk,0,sizeof(mk)); //每个左侧顶点匹配之前都要初始化;
res+=path(i); //输出匹配成功个数,或者if(path(i)) res++
}
}
return res;
}