最小生成树问题
简介
最小生成树是一副连通加权无向图中一棵权值最小的生成树。
最小生成树是图论算法中比较经典的问题,在现实生活中也有非常多的应用。有两种比较经典的算法,都是使用了贪心的思想解决:
-
Prim算法(普利姆算法)
Prim算法的每一步都会为一棵生长中的树添加一条边,该树最开始只有一个顶点,然后会添加{V-1}个边。每次总是添加生长中的树和树中除该生长的树以外的部分形成的切分的具有最小权值的横切边。Prim算法的时间复杂度为{O(E + VlogV)}。
-
Kruskal算法(克鲁斯克尔算法)
按照边的权重顺序(从小到大)将边加入生成树中,但是若加入该边会与生成树形成环则不加入该边。直到树中含有{V-1}条边为止。这些边组成的就是该图的最小生成树。Kruskal算法的时间复杂度为{ElogE}
值得注意的是,在判断生成树是否有环时使用了并查集这样的数据结构
算法实现
Prim
class Solution {
public int minCostConnectPoints(int[][] points) {
return prim(points);
}
/**
* 图论中的最小生成树Prim算法
* @param points
* @return
*/
public int prim(int[][] points){
int res = 0;
//1.构造邻接矩阵
int len = points.length;
int[][] dp = new int[len][len]; // dp[0][1] 表示下标为0 到 下标为1 的 曼哈顿值
for (int i = 0; i < len; i++) {
for (int j = i; j < len; j++) {
if(i == j){
dp[i][j] = 0;
}else {
dp[i][j] = computeConst(points[i][0],points[i][1],points[j][0],points[j][1]);
dp[j][i] = computeConst(points[i][0],points[i][1],points[j][0],points[j][1]);
}
}
}
// v_new 表示图中节点的访问情况,最开始全部为-1,表示未加入到v_new中,若某节点加入到了v_new中, 则将其置为0
int[] v_new = new int[len];
// lowcost 保存每个节点离v_new中所有节点的最短距离。初始化为Integer.MAX_VALUE,如果节点已经加入到了v_new中,则置为-1
int[] lowcost = new int[len];
for (int i = 0; i < len; i++) {
v_new[i] = -1;
lowcost[i] = Integer.MAX_VALUE;
}
//2.随机选取一个节点,默认为第一个节点,并且更新lowcost里面的值
v_new[0] = 0;
for (int i = 0; i < len; i++) {
if(i == 0){
continue;
}else {
lowcost[i] = dp[0][i];
}
}
//3. 遍历未放入v_new 的剩余的节点,
for (int i = 1; i < len; i++) {
// 找到图中离 v_new 最近的点
int minIdx = -1; // minIdx 表示找到节点的下标
int minVal = Integer.MAX_VALUE; // minVal 表示找到节点的下标对应的值
for (int j = 0; j < len; j++) {
if(lowcost[j] < minVal){
minIdx = j;
minVal = lowcost[j];
}
}
//更新 v_new 里面的值
res += minVal;
v_new[minIdx] = 0;
lowcost[minIdx] = Integer.MAX_VALUE;
//更新 lowcost 里面的值
for (int j = 0; j < len; j++) {
if (v_new[j] == -1 && dp[j][minIdx] < lowcost[j]){
lowcost[j] = dp[j][minIdx];
}
}
}
return res;
}
private int computeConst(int x1, int y1, int x2,int y2){
return Math.abs(x1 - x2) + Math.abs(y1 - y2);
}
}
Kruskal
class Solution {
// Kruskal
public int minCostConnectPoints(int[][] points) {
List<Edge> edges = new ArrayList<>();
for(int i = 0; i < points.length; i++) {
for(int j = i; j < points.length; j++) {
edges.add(new Edge(i, j, getLength(points, i, j)));
}
}
Collections.sort(edges, (a, b) -> (
a.len - b.len
));
int ans = 0, edgeNum = 0;
DSU dsu = new DSU(points.length);
for(Edge edge : edges) {
if(dsu.union(edge.point1, edge.point2)){
ans += edge.len;
edgeNum++;
}
if(edgeNum == points.length - 1) break;
}
return ans;
}
public int getLength(int[][] points, int point1, int point2) {
return Math.abs(points[point1][0] - points[point2][0])
+ Math.abs(points[point1][1] - points[point2][1]);
}
private class Edge{
int point1;
int point2;
int len;
Edge(int point1, int point2, int len) {
this.point1 = point1;
this.point2 = point2;
this.len = len;
}
}
private class DSU {
int[] parents, rank;
public DSU(int N) {
parents = new int[N];
rank = new int[N];
for(int i = 0; i < N; i++) {
parents[i] = i;
rank[i] = 1;
}
}
public int find(int x) {
if(x != parents[x]) {
parents[x] = find(parents[x]);
}
return parents[x];
}
public boolean union(int x, int y) {
int xr = find(x);
int yr = find(y);
if(xr == yr) return false;
if(rank[xr] > rank[yr]) {
parents[yr] = xr;
} else if(rank[xr] < rank[yr]) {
parents[xr] = yr;
} else {
parents[xr] = yr;
rank[yr]++;
}
return true;
}
}
}