并查集与常见面试题总结

简介

参考链接
并查集是一种树形的数据结构,用于处理不相交集合的合并和查询问题。在判断连通图个数、朋友圈个数等有重要应用。

并查集主要涉及三个基本操作:

  • makeSet(n):初始化一个独立的集合,开始时每个元素的最高祖先为自身 parent[x]=x;
  • findSet(x):寻找x所在集合的最高祖先,通过递归的方式进行查询,并且在查询时进行路径压缩。例如:开始有一个集合中1->2->3->4这种关系,当执行findSet(1)时的步骤依次是findSet(1) = findSet(2) = findSet(3) = findSet(4) = 4,一遍执行完成后,1,2,3元素的祖先节点都指向4;
  • unionSet(x,y):将元素x和元素y所在的集合进行合并,如果x,y在一个集合中就不用合并了,如果不在一个集合中,只需要将x元素的祖先指向y元素的祖先即可。例如两个集合1->2,3->4,如果将两个集合合并则只需2->4即可。
    通过上面三个函数即可将所有有关系的节点连接在一起。下面是简单的代码实现。
// 并查集简单实现
import java.util.*;
public class UFSet {
    // 保存祖先节点
    int[] parent;

    public UFSet(int n){
        parent = new int[n+1];
    }
    // 初始化,每个元素是一个独立子集,祖先是自己
    public void makeSet(int n ){
        for(int i = 1; i <= n; i++){
            parent[i] = i;
        }
    }
    // 递归的方式找到x的祖先节点
    public int findSet(int x){
        if(parent[x] == x){
            return x;
        }
        parent[x] = findSet(parent[x]);
        return parent[x];
    }
    // 将两个节点所在的子集合并
    public void unionSet(int x1 , int x2){
        int ux =  findSet(x1);
        int uy = findSet(x2);
        if(ux != uy){
            parent[ux] = uy;
        }
    }
    // 获取独立子集个数
    public int getSets(int n){
        int count = 0;
        for(int i = 1; i <= n; i++){
            if(parent[i] == i){
                count++;
            }
        }
        return count;
    }

    public static void main(String[] args){
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        int m = in.nextInt();
        UFSet set = new UFSet(n);
        set.makeSet(n);
        for(int i = 0; i < m; i++){
            int x = in.nextInt();
            int y = in.nextInt();
            set.unionSet(x, y);
        }
        System.out.println(set.getSets(n));
    }

}

应用

1.朋友圈

有n个人和m对好友关系,如果两个人是直接或者间接好友关系则他们属于一个朋友圈。
输入:给定n,m,m对关系,求朋友圈的个数。

思路:建立并查集,对于每一对关系都执行合并操作,最后返回集合数量即可,上面代码即是该问题的简单实现。

2.畅通工程1

畅通工程为了使全省的任何两个城市之间都可以实现直接或者间接互通,问最少需要建设多少条道路。
输入:城市数量n,已经修建的道路条数m和已经修建的m条道路信息。

思路:建立并查集,对每一对关系执行合并操作,最后返回集合数-1即可。

3.畅通工程2

实现所有城市互通,求铺设的公路总长度最小的铺设方法。
输入:城市数目n,每两个城市之间的公路长度(1,2,3:表示1和2城市之间的公路长度为3个单位)
思路:先对公路的长度进行排序,优先从短公路修。其中需要实现获取最小公路长度的函数;

public int getMinCost(int m)
{
	sort(edge);//必须先对边排序(根据边的修建费用),这样才能贪心的形成最小花费  
	int sum = 0;
	for (int i = 0; i<m; i++)
	{
		int baseA = findSet(edge[i].acity);//找到城市a的祖先(要么是自身要么是城市b的编号)  
		int baseB = findSet(edge[i].bcity);
		if (baseA != baseB)
		{
			parent[baseA] = baseB;//将城市a的祖先设置成b的祖先这个式子等价于parent[edge[i].acity] = edge[i].bcity  
			sum += edge[i].cost;
		}
	}
	return sum;
}

4. 畅通工程3

给出两个城市之间的道路花费以及是否已经修通的状态,计算全省畅通需要的最小成本。
思路:如果两个城市已经修了路则直接执行合并操作。然后获取最小花费。

int sum()
{
	int n = 0;
	while (cin >> n, n > 0)
	{
		int m = n*(n - 1) / 2;
 
		UFSet uset(100);
		uset.makeSet(n);//初始化每个城市的祖先为自身
		for (int i = 0; i < m; i++)
		{
			cin>> edge[i].acity>> edge[i].bcity>> edge[i].cost>> edge[i].isBuild;
			if (edge[i].isBuild == 1)
				uset.unionSet(edge[i].acity, edge[i].bcity);//将已经建成的两个城市编号建立连接
		}
		int mincost = uset.getMinCost(m);
		cout << mincost << endl;
	}
 
	return 0;
}

5.连通图

给定一个无向图和所有边,判断这个图是否所有顶点都是联通的。
思路:对所有边执行合并操作,如果最后集合个数为1则联通,否则不连通。

6.欧拉回路

判断一个图是否存在欧拉回路
思路:所有点的度都是偶数并且全部节点联通则存在欧拉回路。

你可能感兴趣的:(数据结构与算法)