基本思想:每次从剩下的边中选择一条权重最小并且不会产生环路的边加入到已选择的边集当中。(连树成森林)
图源:yizhe-shi(LeetCode)
链接: 顺便可以看看这位大佬的题解.
难点1 : 如何找到权重最小边? —— 使用最小堆
难点2 : 如何判断不产生回路?—— 每个顶点都是一个并查集,先从最小堆中选出一条权重最小的边,如果该边的两个顶点位于两个并查集中,即说明不会产生回路。每次添加成功一条边后,需要把两个顶点所在的并查集合并。
需要使用的数据结构:用于存储图的边的数组、用于提取边的小根堆、用于存储最小生成树的边的数组、用于表示子图的并查集。
由于Java没有自带的并查集,咱们先自己实现一个:
链接: 这个大佬讲得并查集真的很有意思.
实现并查集:
public class UnionFind
{
private int[] id;
private int count;
public UnionFind(int n)
{
count = n;
id = new int[n];
for(int i = 0; i < n; i++) id[i] = i;
}
public int getCount()
{
return count;
}
public boolean connected(int p, int q)
{
return find[p] == find[q];
}
public int find(int p)
{
return id[p];
}
public void union(int p, int q)
{
int pRoot = find(p);
int qRoot = find(q);
if(pRoot == qRoot) return;
for(int i = 0; i < id.length; i++)
{
if(id[i] == pRoot) id[i] = qRoot;
}
count--;
}
}
子图的表示:使用并查集。
判断边(u,v)是否产生环:判断u,v是否处在同一个并查集。
加入一条边:两个并查集合并。
开始表演:
public ArrayList<weightedEdge> kruskal()
{
int n = numberOfVertices();
int e = numberOfEdges();
ArrayList<weightedEdge> res = new ArrayList<>();//用于存储结果
//将边存入一个数组中
int k = 0;
weightedEdge[] edges = new weightedEdge[e];
for(int i = 0; i < n; i++)
{
for(int j = i + 1; j <= n; j++)
{
if(edgeExist(i, j))
edges[k++] = new weightedEdge(edge(i, j));
}
}
//使用小根堆存储数组中的边
PriorityQueue<weightedEdge> minHeap = new PriorityQueue<>(e, new Comparator<weightedEdge>()
{
public int compare(weightedEdge i, weightedEdge j)
{
return i.weight - j.weight;
}
});
for(weightEdge e : edges)
{
minHeap.offer(e);
}
//使用并查集合并子图
k = 0;//计数
UnionFind uf = new UnionFind(n);//n个顶点n个子图
while(e > 0 && k < n - 1)
{
weightedEdge we = minHeap.poll();
e--;
int a = we.vertex1();
int b = we.vertex2();
if(!uf.connected(a, b))//选出的该边的两个顶点在不同并查集中
{
res.add(we);
k++;
uf.unite(a, b);
}
}
return res;
}
这里再推荐一道练手题:
链接: LeetCode 1584.连接所有点的最小费用.
以下代码是我完全按照上述格式写出来的题解:
class Solution {
public int minCostConnectPoints(int[][] points) {
int len = points.length;
if(len == 1) return 0;
WeightedEdge[] edges = new WeightedEdge[len*(len-1)/2];
int k = 0;
int sum = 0;
ArrayList<WeightedEdge> res = new ArrayList<>(len);
for(int i = 0; i < len; i++)
{
for(int j = i + 1; j < len; j++)
{
int w = Math.abs(points[i][0]-points[j][0]) + Math.abs(points[i][1]-points[j][1]);
edges[k++] = new WeightedEdge(i, j, w);
}
}
PriorityQueue<WeightedEdge> minHeap = new PriorityQueue<>(len*(len-1)/2, new Comparator<WeightedEdge>()
{
public int compare(WeightedEdge i, WeightedEdge j){
return i.weight - j.weight;
}
});
for(WeightedEdge edge : edges) minHeap.offer(edge);
UnionFind uf = new UnionFind(len);
k = 0;
while(k < len-1)
{
WeightedEdge we = minHeap.poll();
int a = uf.find(we.vertex1);
int b = uf.find(we.vertex2);
if(a != b)
{
res.add(we);
k++;
uf.union(we.vertex1, we.vertex2);
}
}
for(WeightedEdge every : res) sum += every.weight;
return sum;
}
}
class WeightedEdge{
public int vertex1, vertex2, weight;
WeightedEdge(int i, int j, int w)
{
vertex1 = i;
vertex2 = j;
weight = w;
}
}
class UnionFind{
private int[] id;
private int count;
UnionFind(int n)
{
id = new int[n];
for(int i = 0; i < n; i++) id[i] = i;
count = n;
}
public int find(int p)
{
return id[p];
}
public void union(int p, int q)
{
int a = find(p);
int b = find(q);
if(a != b)
{
for(int i = 0; i < id.length; i++)
{
if(id[i] == a) id[i] = b;
}
count--;
}
}
}