问题引入
一个有n个节点m条边(m>=n)且边有权值的无向图,若从这m条边中选出n-1条边,将这n个点连接起来组成一棵树,且所选边的权值和最小,那么这棵树称为该无向图的最小生成树(Minimum Spanning Tree,MST)。
拿实际问题来举例。假如有n个地点,现在要修路将这n个地点相互连通,每条路连通两个地点,但不同的路造价不一样。问如何修路,使得所有地点都能连通,但路的造价最低。
Kruskal算法
首先假设所有的边均未被选择,然后将所有的边按权值从小到大排序,并从权值最小的边开始逐个边检验:若该边加入到图中后未出现环,则该边保留到图中,否则就舍弃该边,然后再检验下一条边,直到选出n-1条边。
对于环的判断,可使用并查集进行处理。假设当前检验的边连接的两点存在相同的根节点,则说明这两点在加入该边前就已经连通,则加入此边后必定有环。
LeetCode 1584
题目链接:https://leetcode.com/problems/min-cost-to-connect-all-points/
给出n个点,现在要用一些边连接这些点,并令这些点相互连通,求需要的最短边长。连接两点 ( x i , y i ) (x_i, y_i) (xi,yi)和 ( x j , y j ) (x_j, y_j) (xj,yj)的边的长度为: ∣ x i − x j ∣ + ∣ y i − y j ∣ |x_i-x_j|+|y_i-y_j| ∣xi−xj∣+∣yi−yj∣
struct edge {
short i, j;
int dist;
};
int f[1005];
int Cmp(const void * a, const void * b) {
return (*(struct edge *)a).dist - (*(struct edge *)b).dist;
}
int FindRoot(int x) {
return x == f[x] ? x : (f[x] = FindRoot(f[x]));
}
int minCostConnectPoints(int** p, int n, int* pointsColSize){
struct edge allEdge[n*n];
int nEdge = 0, ans = 0, usedEdge = 0;
int num[n];
for (int i=0; i<n; i++) {
f[i] = i;
num[i] = 1;
}
for (int i=0; i<n; i++)
for (int j=i+1; j<n; j++) {
allEdge[nEdge].i = i;
allEdge[nEdge].j = j;
allEdge[nEdge].dist = abs(p[i][0]-p[j][0]) + abs( p[i][1]-p[j][1]);
nEdge++;
}
qsort(allEdge, nEdge, sizeof(allEdge[0]), Cmp);
//以树的节点数作为判断树大小的依据进行合并,同时进行路径压缩。
for (int i=0; i<nEdge; i++) {
int r1 = FindRoot(allEdge[i].i);
int r2 = FindRoot(allEdge[i].j);
if (r1 != r2) {
if (num[r1] > num[r2]) {
num[r1] += num[r2];
f[r2] = r1;
}
else {
num[r2] += num[r1];
f[r1] = r2;
}
ans += allEdge[i].dist;
usedEdge++;
if (usedEdge == n-1)
return ans;
}
}
return ans;
}
Prim算法
从任意一点开始,然后搜索与之相连的点中边权值最小的连接,然后对于这两个点都搜索相连的点,如果某个点连上后有环则放弃该点。若有权值相同的两点则任选一个(因为最后所有点都是要搜索其相邻点的)。