最佳完美匹配分两种:权值和最大的完美匹配、权值和最小的完美匹配。
时间复杂度都为:O(n^4) ;
1、权值和最大的完美匹配
用的算法是KM(匈牙利算法)。
下面分析一个这个算法的原理:
1、首先为每个点建立一个函数(可行顶标) , 使得对于任意的弧有l(x)+l(y)>=w[x][y] (弧有x、y组成 , w[x][y]是该弧的权值) , 假设这样的弧组成的图成为相等子图也就是原图的生成子图 , 如果这个相等子图存在最佳完美匹配 , 那么这个最佳完美匹配也就是原图的最佳完美匹配。
所以我们要做得就是寻找每个节点的函数(可行顶标)来建成存在最佳完美匹配的相等子图 。
2、构造可行顶标的思路:任意构造一个可行顶标(必须满足l(x)+l(y)>=w[x][y]) , 比如y结点顶标为0 , 而x结点的顶标为它出发所有边的最大权值
3、构成之后 , 再来求相等子图的最佳完美匹配 , 如果不存在 , 那么就改变可形顶标 , 来使更多的边进入相等子图 。
下面是代码:
#include
#include
#include
#include
#include
#include
using namespace std;
const int MAXN = 1000;
int w[MAXN][MAXN] , n , m; // 记录每条边的权值
int lx[MAXN] , ly[MAXN]; // 记录每个点的顶标
int pre[MAXN] ; // 记录和y中点匹配的点是哪个点
bool s[MAXN] , t[MAXN] ; // 标记x和y的点是否被访问过
void init()
{
memset(w , 0 , sizeof(w));
}
bool match(int i) //
寻找增广路
{
s[i] = true;
for(int j = 1; j <= n; j++)
if(lx[i]+lx[j] == w[i][j] && !t[j])
{
t[j] = true;
if(!pre[j] || match(pre[j]))
{
pre[j] = i;
return true;
}
}
return false;
}
void update()
{
int a = 1<<30 , i;
for(i = 1; i <= n; i++)
if(s[i])
//
如果x中的这个点已被标记
for(int j = 1; j <= n; j++)
if(!t[i]) //
如果这个点在y中没有被标记 , 和前面的判断 , 就可以寻找到飞匹配点。
a = min(a , lx[i]+ly[j]-w[i][j]);
//
找到最小的a , 因为这样才能保证是最佳完美匹配
for(i = 1; i <= n; i++)
{
if(s[i])
lx[i] -= a;
//
只有被标记的点 , 才修改其顶标
if(t[i])
ly[i] += a;
}
}
void km()
{
int i , j;
for(i = 1; i <= n; i++)
{
pre[i] = lx[i] = ly[i] = 0;
//
构造可行顶标
for(j = 1; j <= n; j++)
lx[i] = max(lx[i] , w[i][j]);
}
for(i = 1; i <= n; i++)
{
for(; ; )
{
for(j = 1; j <= n; j++)
s[j] = t[j] = 0;
//初始化x和y中点的状态
if(match(i)) break; // 寻找增广路
else update();
//
修改顶标
}
}
}
int main()
{
while(scanf("%d %d" , &n , &m) != EOF)
{
init();
int i , x , y , z;
for(i = 0 ; i < m; i++)
{
scanf("%d %d %d" , &x , &y , &z);
w[x][y] = z;
w[y][x] = z;
}
km();
for(i = 1; i <= n; i++)
if(t[i])
printf("%d %d\n" , pre[i] , i);
}
return 0;
}
2、权值最小的完美匹配
算法和前面的基本基本一样 , 就是在可行顶标上正好相反
bool match(int i)
{
s[i] = true;
for(int j = 1; j <= n; j++)
if(fabs(lx[i]+lx[j] - grap[i][j])<0.0001 && !t[j])
{
t[j] = true;
if(!pre[j] || match(pre[j]))
{
pre[j] = i;
return true;
}
}
return false;
}
void update()
{
double a = (1<<30)*1.0 ;
int i;
for(i = 1; i <= n; i++)
if(s[i])
for(int j = 1; j <= n; j++)
if(!t[i])
a = min(a , grap[i][j]-lx[i]-ly[j]);
for(i = 1; i <= n; i++)
{
if(s[i])
lx[i] -= a; //
因为一开始是最小的权值 , 所以如果要使子图中的边增加 , 那么就只能增加可行顶标的权值
if(t[i])
ly[i] += a;
}
}
void km()
{
int i , j;
memset(pre , 0 , sizeof(pre));
for(i = 1; i <= n; i++)
{
lx[i] = ly[i] = 1000000*1.0;
for(j = 1; j <= n; j++)
lx[i] = min(lx[i] , grap[i][j]);
//
这里是求权值最下的边(因为是要求总权值和最小)
}
for(i = 1; i <= n; i++)
{
for(; ;)
{
for(j = 1; j <= n; j++)
s[j] = t[j] = 0;
if(match(i)) break;
else update();
}
}
}
3、当顶点没有分x和y集合 , 而是都放在一起时
// 对于求最佳完美匹配 , 如果节点没有分成在t、s集合中 , 而是都放在一个图中没有区分时的求法
// 关键在于区分每个节点 , 看是属于t集合还是属于s集合 , 特别是在计算最小整数a时 。
#include
#include
#include
#include
#include
using namespace std;
const int MAXN = 1000;
vectoredge[MAXN] ;
int lx[MAXN] , ly[MAXN] , grap[MAXN][MAXN]; // 用lx和ly记录每个节点的可行顶标 , 之所以要两个数组 , 是为了区分点是在哪个集合中
int pre[MAXN]; // 记录点的匹配点
bool s[MAXN] , t[MAXN]; // 记录点是否被记录
int n, m;
void init()
{
for(int i = 0 ; i < n; i++)
edge[i].clear();
}
bool match(int i) // 求增广路
{
s[i] = true;
for(int j = 0; j < edge[i].size(); j++)
{
int v = edge[i][j] ;
if(!s[v] && !t[v] && (lx[i]+ly[v]) == grap[i][v])
// 这里不光要判断点是否在t中被标记 , 还必须要判断点是否被s标记
{
t[v] = true;
if(!pre[v] || match(pre[v]))
{
pre[v] = i;
return true;
}
}
}
return false;
}
void update()
{
int i , j , a = 1<<30;
for(i = 1; i <= n; i++)
if(s[i])
for(j= 0; j < edge[i].size(); j++)
{
int v = edge[i][j];
if(!t[v] && !s[v] && a > (lx[i]+ly[v] -grap[i][v])) //在这里求出最小的a , 必须是在s和t中都没有标记
a =lx[i] + ly[v] - grap[i][v];
}
//cout<<a<<endl;
for(i = 1; i <= n; i++)
{
if(s[i])
lx[i] -= a;
if(t[i])
ly[i] += a;
}
}
void ek()
{
int i , j;
for(i = 1; i <= n; i++)
{
lx[i] = ly[i] = pre[i] = 0;
for(j = 0; j < edge[i].size(); j++)
{
int v = edge[i][j];
if(grap[i][v] > lx[i])
lx[i] = grap[i][v];
}
}
for(i = 1; i <= n; i++)
{
if(pre[i])
continue ;
// 这里要判断该点是否是哪个点的匹配点 , 只有不是时才能对其进行求匹配点
for(; ;)
{
//cout<<i<<endl;
memset(s , 0 , sizeof(s));
memset(t , 0 , sizeof(t));
if(match(i))
break;
else update(); //
如果这个点没有找到匹配点时 , 修改可行顶标
//break;
}
}
}
int main()
{
while(scanf("%d %d" , &n , &m) != EOF)
{
init();
int i
, x , y , z;
for(i = 0 ; i < m; i++)
{
scanf("%d %d %d" ,&x , &y , &z);
grap[x][y] = grap[y][x] = z;
edge[x].push_back(y);
edge[y].push_back(x);
}
ek();
for(i = 1; i <= n; i++)
if(pre[i])
printf("%d %d\n" , pre[i] , i);
}
return 0;
}
时间复杂度都为:O(n^4) ;
1、权值和最大的完美匹配
用的算法是KM(匈牙利算法)。
下面分析一个这个算法的原理:
1、首先为每个点建立一个函数(可行顶标) , 使得对于任意的弧有l(x)+l(y)>=w[x][y] (弧有x、y组成 , w[x][y]是该弧的权值) , 假设这样的弧组成的图成为相等子图也就是原图的生成子图 , 如果这个相等子图存在最佳完美匹配 , 那么这个最佳完美匹配也就是原图的最佳完美匹配。
所以我们要做得就是寻找每个节点的函数(可行顶标)来建成存在最佳完美匹配的相等子图 。
2、构造可行顶标的思路:任意构造一个可行顶标(必须满足l(x)+l(y)>=w[x][y]) , 比如y结点顶标为0 , 而x结点的顶标为它出发所有边的最大权值
3、构成之后 , 再来求相等子图的最佳完美匹配 , 如果不存在 , 那么就改变可形顶标 , 来使更多的边进入相等子图 。
下面是代码:
#include
#include
#include
#include
#include
#include
using namespace std;
const int MAXN = 1000;
int w[MAXN][MAXN] , n , m; //
int lx[MAXN] , ly[MAXN]; //
int pre[MAXN] ;
bool s[MAXN] , t[MAXN] ; //
void init()
{
}
bool match(int i)
{
}
void update()
{
}
void km()
{
}
int main()
{
}
2、权值最小的完美匹配
算法和前面的基本基本一样 , 就是在可行顶标上正好相反
bool match(int i)
{
}
void update()
{
}
void km()
{
}
3、当顶点没有分x和y集合 , 而是都放在一起时
//
//
#include
#include
#include
#include
#include
using namespace std;
const int MAXN
vectoredge[MAXN] ;
int lx[MAXN] , ly[MAXN] , grap[MAXN][MAXN]; // 用lx和ly记录每个节点的可行顶标 , 之所以要两个数组 , 是为了区分点是在哪个集合中
int pre[MAXN];
bool s[MAXN] , t[MAXN]; // 记录点是否被记录
int n, m;
void init()
{
}
bool match(int i) // 求增广路
{
}
void update()
{
}
void ek()
{
}
int main()
{
}