并查集(Union-find Set)

基本功
int[] pre=new int[1000];

int find(int x){
	int a=x;
	while(pre[a]!=a){
		a=pre[a];
	}//while 执行结束后,a就会到达它所属集合的顶级位置
	
	//路径压缩
	int b=x;
	int tmp=-1;
	while(b!=a){
		tmp=pre[b];
		pre[b]=a;//将b的上级改成顶级位置,加速下次查找
		b=tmp;
	}
	return a;
}

void union(int x,int y){
	int px=find(x),py=find(y);
	if(px!=py){//若px与py不等,则说明还不属于同一个集合
		pre[px]=py;
	}
}
并查集

算法导论第21章称作不相交集合。一些应用涉及将n个不同的元素分成一组不相交的集合。这些应用经常需要进行两种特别的操作:1.寻找包含给定元素的唯一集合(查),2.合并两个集合(并)

并查集数据结构

并查集这种数据结构由一个整数型的数组和两个函数构成。数组pre[]记录了每个元素的上级是谁,这个数组记录了所有不相交集合,函数find,函数union

int[] pre=new int[1000];

int find(int x){
	int a=x;
	while(pre[a]!=a){
		a=pre[a];
	}//while 执行结束后,a就会到达它所属集合的顶级位置
	
	//路径压缩
	int b=x;
	int tmp=-1;
	while(b!=a){
		tmp=pre[b];
		pre[b]=a;//将b的上级改成顶级位置,加速下次查找
		b=tmp;
	}
	return a;
}

void union(int x,int y){
	int px=find(x),py=find(y);
	if(px!=py){//若px与py不等,则说明还不属于同一个集合
		pre[px]=py;
	}
}
并查集的应用场景

算法导论里说到,并查集适合应用在需要将n个不同的元素分成一组不相交的集合。

题目
题目讲解
  • 200. Number of Islands
    Given a 2d grid map of '1’s (land) and '0’s (water), count the number of islands. An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically. You may assume all four edges of the grid are all surrounded by water.
Example 1:

Input:
11110
11010
11000
00000

Output: 1
Example 2:

Input:
11000
11000
00100
00011

Output: 3

由于题目给定是一个二维数组,所以我们需要将二维数组映射成一维数组,也就是给这个二维数组中的每个元素进行编号。
初始化并查集时,我们将每一个岛屿都初始化为一座孤立的岛屿(自己的上级是自己)。然后再,扫描其上左下右四个方向看有没有岛屿,若是有则将自己与上左下右方向上的岛屿合并。
最后再整体扫描一遍,看有多少座孤立的岛屿,即为答案。

public static int numIslands(char[][] grid) {
        if(grid.length==0) return 0;
        int row=grid.length,col=grid[0].length;
        int countIsland=0;
        int[] island=new int[row*col];
        Arrays.fill(island, -1);
        int[][] direction = new int[][]{{-1,0},{0,-1},{1,0},{0,1}};// 方向:上,左,下,右
        
        for(int i=0;i<row;i++) {
        	for(int j=0;j<col;j++) {
        		int id=i*col+j;
        		if(grid[i][j]=='1') {
        			island[id]=id;
        		}
        	}
        }
        
        for(int i=0;i<row;i++){
            for(int j=0;j<col;j++){
                if(grid[i][j]=='1') {//是一座岛
                	int id=i*col+j;
					for (int k = 0; k < 4; k++) {
						int x = i + direction[k][0], y = j + direction[k][1];
						if (x < 0 || x >= row || y < 0 || y >= col || grid[x][y] == '0')
							continue;// 是一座岛
						else {// 不是一座岛
							int num = x * col + y;
								union(island,num,id);
						}
					}
                }
                
            }
        }
        for(int i=0;i<island.length;i++) {
        	if(island[i]==i) countIsland++;
        }
        return countIsland;
    }
	
	private static int findFather(int[] island,int a) {
		while(island[a]!=a) {
			a=island[a];
		}
		return a;
	}
	
	private static void union(int[] island,int a,int b) {
		int pa=findFather(island,a);
		int pb=findFather(island,b);
		if(pa==pb) return;
		else island[pa]=pb;
	}

  • 547. Friend Circles
    There are N students in a class. Some of them are friends, while some are not. Their friendship is transitive in nature. For example, if A is a direct friend of B, and B is a direct friend of C, then A is an indirect friend of C. And we defined a friend circle is a group of students who are direct or indirect friends.

Given a N*N matrix M representing the friend relationship between students in the class. If M[i][j] = 1, then the ith and jth students are direct friends with each other, otherwise not. And you have to output the total number of friend circles among all the students.

Example 1:
Input: 
[[1,1,0],
 [1,1,0],
 [0,0,1]]
Output: 2
Explanation:The 0th and 1st students are direct friends, so they are in a friend circle. 
The 2nd student himself is in a friend circle. So return 2.

这个关系存储为图的矩阵存储。具有天然的方便(不用再像上题一样扫描上左下右四个方向,看有没有紧挨着的同学
图中的元素代表关系,而不代表元素本身。元素本身用矩阵的行号或者列号来表示(对称矩阵)
所以思路就是:初始化并查集为每一个同学自己就为一个不相交集合。再扫描矩阵,若是有关系则将两同学合并,最后扫描并查集看有多少个不相交的集合,即为答案。

public int findCircleNum(int[][] M) {
        int row=M.length,col=M[0].length;
        int[] friend = new int[row];
        int count=0;
        for(int i=0;i<friend.length;i++){//初始化每个同学为一个孤立的不相交集合
            friend[i]=i;
        }
        
        for(int i=0;i<row;i++){
            for(int j=0;j<col;j++){
                if(M[i][j]==1){//若有关系,则找出集合首进行合并
                    int a=i,b=j;
                    while(friend[a]!=a) a=friend[a];
                    while(friend[b]!=b) b=friend[b];
                    if(a!=b) friend[a]=b;
                }
            }
        }
        for(int i=0;i<friend.length;i++){
            if(friend[i]==i) count++;
        }
        return count;
    }

另外此题也可以用图的深度优先搜索来完成:

public int findCircleNum(int[][] M) {
        if(M.length==0) return 0;
        int count=0;
        boolean[] visit=new boolean[M.length];
        for(int i=0;i<M.length;i++){
            if(!visit[i]){
            DFS(i,M.length,visit,M);
            count++; 
            }
           
        }
        return count;
    }
    
    private void DFS(int i, int n, boolean[] visit, int[][] M){
        visit[i]=true;
        for(int j=0;j<n;j++){
            if(M[i][j]==1 && !visit[j]){
                DFS(j,n,visit,M);
            }
        }
    }

The given input is a graph that started as a tree with N nodes (with distinct values 1, 2, …, N), with one additional edge added. The added edge has two different vertices chosen from 1 to N, and was not an edge that already existed.

The resulting graph is given as a 2D-array of edges. Each element of edges is a pair [u, v] with u < v, that represents an undirected edge connecting nodes u and v.

Return an edge that can be removed so that the resulting graph is a tree of N nodes. If there are multiple answers, return the answer that occurs last in the given 2D-array. The answer edge [u, v] should be in the same format, with u < v.

Example 1:
Input: [[1,2], [1,3], [2,3]]
Output: [2,3]
Explanation: The given undirected graph will be like this:
  1
 / \
2 - 3
Example 2:
Input: [[1,2], [2,3], [3,4], [1,4], [1,5]]
Output: [1,4]
Explanation: The given undirected graph will be like this:
5 - 1 - 2
    |   |
    4 - 3

要想找出冗余的路,就得知道两个点的顶级的点有没有相连,若是之前就已经相连,则当前这两个点构成的边即为冗余。
第一步,先初始化每个顶点都为孤立的不相交集合。
第二步,读入每条路的两个顶点,若这两个顶点不通(集合不相交),则合并它们。(这里注意,所有的合并处理都必须要获取到顶级顶点才能进行)。若这两个顶点早已相通(顶级顶点为同一个),则这条路为多余的。

public int[] findRedundantConnection(int[][] edges) {
        int[] res=new int[2];
        int[] road = new int[edges.length+1];
        for(int i=1;i<road.length;i++){
            road[i]=i;
        }
        
        for(int i=0;i<edges.length;i++){
            int a=edges[i][0],b=edges[i][1];
        
            while(road[a]!=a) a=road[a];
            while(road[b]!=b) b=road[b];
            if(a==b){//若是两条路的最终点相同,则为冗余
                res=edges[i];
            }else{
                road[a]=b;
            }
        }
        return res;
    }
总结
  • 基本上所有的题目的第一步都是初始化一个并查集,里面的所有元素都是孤立的不相交的集合。
  • 所有的操作都需要获取到当前元素的顶级元素才能够合并,否则这个操作称不上的合并操作
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值