在一个并查集中,主要强调两个方法,首先是判断两个集合是否是一个集合,也就是两个是否连通,第二个是把两个不同的集合合并为一个集合。
那么掌握了它的好处体现在哪里呢?想一下我们的最小生成树算法中的kruskal算法,不就是从小边开始拿,每次拿到一个边,判断边的两端是否从属于一个集合吗?如果属于,放弃这条边;如果不属于,要这条边。
下面来看代码👇
public class UnionFind {
// key 某一个节点, value key节点往上的节点
private HashMap<Node, Node> fatherMap;
// key 某一个集合的代表节点, value key所在集合的节点个数
private HashMap<Node, Integer> sizeMap;
//构造函数
public UnionFind() {
fatherMap = new HashMap<Node, Node>();
sizeMap = new HashMap<Node, Integer>();
}
//制作每个集合只有一个点的小集合
public void makeSets(Collection<Node> nodes) {
fatherMap.clear();
sizeMap.clear();
for (Node node : nodes) {
fatherMap.put(node, node);
sizeMap.put(node, 1);
}
}
//寻找某个点的最上一个节点,同时将这个father节点作为整个集合的代表节点
private Node findFather(Node node)
{
Stack<Node> stack = new Stack<Node>();
while(node != fatherMap.get(node))
{
stack.add(node);
node = fatherMap.get(node);
}
while(!stack.isEmpty())
{
//将这个father节点作为整个集合的代表节点
fatherMap.put(stack.pop(), node);
}
return node;
}
//第一个最重要的方法
public boolean isSameSet(Node a, Node b) {
return findFather(a) == findFather(b);
}
//第二个最重要方法:合成一个union
public void union(Node a, Node b) {
if (a == null || b == null) {
return;
}
Node aDai = findFather(a);
Node bDai = findFather(b);
if (aDai != bDai) {
int aSetSize = sizeMap.get(aDai);
int bSetSize = sizeMap.get(bDai);
//把小集合合并给大集合
if (aSetSize <= bSetSize) {
fatherMap.put(aDai, bDai);
sizeMap.put(bDai, aSetSize + bSetSize);
sizeMap.remove(aDai);
} else {
fatherMap.put(bDai, aDai);
sizeMap.put(aDai, aSetSize + bSetSize);
sizeMap.remove(bDai);
}
}
}
}
有了并查集的概念,理解最小生成树算法会变得非常容易,下面给出K算法的代码👇
//返回值是一个我选中的边的集合
public static Set<Edge> kruskalMST(Graph graph) {
UnionFind unionFind = new UnionFind();
unionFind.makeSets(graph.nodes.values());
// 从小的边到大的边,依次弹出,小根堆!
PriorityQueue<Edge> priorityQueue = new PriorityQueue<Edge>(10,new EdgeComparator());
for (Edge edge : graph.edges) { // M 条边
priorityQueue.add(edge); // O(logM)
}
Set<Edge> result = new HashSet<Edge>();
while (!priorityQueue.isEmpty()) { // M 条边
//取出堆顶的权重最小的边
Edge edge = priorityQueue.poll(); // O(logM)
//如果这条边的两个端点不属于同一个集合
if (!unionFind.isSameSet(edge.from, edge.to)) { // O(1)
//拿走这条边
result.add(edge);
//把这两个点所在的集合连起来
unionFind.union(edge.from, edge.to);
}
}
return result;
}
通过注释应该比较好了解上面的代码,生成树的算法比较重要,并查集也是一个非常有用的工具,在判断联通问题时会给我们带来很大的便捷。同时hashmap和hashset是非常常用、好用的工具,一定要多多练习,熟练掌握!