这个算法是个很有趣的算法,也很好用。引用acwing一句高赞评论:匈牙利算法准则:待字闺中,据为己有;名花有主,求他放手。还有来自人生导师y总的总结【doge】:一定要坚持不懈,就算前面有一个困难,也不要直接退缩,直接退缩是完全没有希望的。只有当尝试各种各样的方法都达不到后,我们才考虑放弃。
简单来说就是如果两个点(a, b)同时连到一个点 c 时,如果先前占据的点(原本是a占据b)有备选项的话,他就会让位占领另一个点(假设是d),让点b占据点c。
代码实现上,最巧妙的一部分就是st数组。每一次对一个点进行匹配都重新将st数组刷新为false。之所以这样就是为了好让先前的占据者(海王)让步,让他去重新搜索有没有其他可选择的点。所以这个st的true和false只是表示一个暂时的状态,并不代表永久的占据。
#include
#include
#include
using namespace std;
const int N =510, M = 100010;
int h[M], e[M], ne[M], idx;
int n1,n2, m;
int match[N];
bool st[N];
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
bool find(int x)
{
for(int i = h[x]; i != -1; i = ne[i])
{
int j = e[i];
if(!st[j])
{
st[j] = true;
// 若没人占据或先前占据的人有备选项
if( !match[j] || find(match[j]) )
{
match[j] = x;
return true;
}
}
}
return false;
}
int main()
{
memset(h, -1, sizeof h);
cin>>n1>>n2>>m;
while(m--)
{
int a,b;
cin>>a>>b;
add(a,b);
}
int res =0;
for(int i = 1; i <= n1; i ++)
{
memset(st, false, sizeof st); //刷新状态数组
if(find(i)) res ++;
}
cout<
实现思路:在prim中先选择一个点(比如1)作为最小生成树的开始,然后从这个点出发去找与它相连的边(假设有到2,3,4的边),在这些边中,找出权重最小的点(假设是3),和开始的点形成一个子树,这个子树就用当前的点3来代表,然后继续去找与这个子树(3)有连接的最小权重边,重复上述过程,直到所有点都被找完。
对比:代码的实现形式和Dijkstra很像,二者的本质都是用贪心的算法实现。区别就在于一个是用于求起点的点n的距离,一个是用于求最小生成子树到点的距离。在代码上的核心区别: Dijkstra dis[ j ] =dis[ t ] + g[ t ][ j ];Prim dis[ j ] = g[ t ][ j ];
#include
#include
#include
using namespace std;
const int N =510, M = 100010;
int g[N][N];
int dis[N];
int st[N];
int n, m, res;
void prim()
{
memset(dis, 0x3f, sizeof dis);
dis[1] = 0;
for(int i = 0; i < n; i ++)
{
int t = -1;
for(int j = 1; j <= n; j ++)
{
if(!st[j] && (t == -1 || dis[j] < dis[t] ))
t = j;
}
if(dis[t] >= 0x3f3f) //如果这个点没有被任何一个点连通,也就意味着不可能有最小生成树
{
cout<<"impossible";
return;
}
st[t] = 1; //标记:已被加到最小生成树中
res += dis[t]; //更新答案
for(int k = 1; k <= n; k ++)
{
if(dis[k] > g[t][k] && !st[k])
{
dis[k] = g[t][k];
}
}
}
cout<>n>>m;
while(m--)
{
int a,b,c;
cin>>a>>b>>c;
g[b][a] =g[a][b] = min(g[a][b],c); //处理重边,取最小值
}
prim();
}
当点数和边数相当时,就要用到这个算法,事实上也可以用Prim的堆优化算法来实现,但Kruskal算法更加简洁,清晰。所以就用Kruskal来处理。
思路:Kruskal实现的思路非常巧妙,先将所有边的权重排个序,然后从最短的边开始连接。这里就涉及到很关键的一步,就是判断连接的点之间是否成环了(这样就不符合最小生成树的定义)。我们这时就用到并查集里的函数,即如果相连的两个点有一个共同的祖宗节点,那就代表这两个点已经在一颗最小生成树里了,不能将这两条线连接起来。最后的判断就是连接边数cnt等于点数n-1
#include
#include
#include
using namespace std;
const int N = 100010, M = 200010;
struct E //只用逐一枚举每条边,所以直接用结构体存边信息
{
int a;
int b;
int c;
bool operator < (const E& rhs) //通过边长进行排序
{
return this->c < rhs.c;
}
}edge[N*2];
int p[N];
int n,m, cnt, res;
int find(int u)
{
if(p[u] != u) p[u] = find(p[u]);
return p[u];
}
void kruskal()
{
for(int i = 1; i <= m; i ++)
{
int pa = find(edge[i].a); //查找祖宗节点
int pb = find(edge[i].b);
if(pa != pb )
{
res += edge[i].c;
p[pa] = pb; //合并a,b
cnt ++;
}
}
}
int main()
{
cin>>n>>m;
for(int i = 1; i <= n; i ++ ) p[i] = i;
for(int i = 1; i <= m; i ++)
{
int a,b,c;
cin>>a>>b>>c;
edge[i] = {a, b, c };
}
sort(edge + 1, edge + m + 1 );
kruskal();
if(cnt != n-1)
{
cout<<"impossible";
return 0;
}
cout<