先看先人的总结:
一、并查集简单总结
1、数据
parent 集合代表,rank集合层级,data 元素的数值
2、三个操作
初始化:
将所有元素的集合代表设置成自己,rank层级设置成0,
查找:
查找一个元素的集合代表,原理是指针循环操作
合并集合:
将两个集合的集合代表设置成一个。一般根据rank层级选择层级大的做代表。目的是使得树的分部尽量均匀
二、题目示例
547. Friend Circles
给定一个矩阵,由0和1组成,1代表横纵坐标的两个人是朋友,朋友关系具有传递性。求有多少个朋友集合。
思路1:DFS
可以直接使用DFS。建立一个标记数组,已经遍历过的小孩不需要做操作。这样的时间复杂度并不高,因为每个小孩只走访一次。如果只有几个小朋友,那么dfs的递归调用只有几次。
class Solution {
public:
void dfs(vector<vector<int>>& M,vector<int>& visited,int laoda)
{
for(int j=0;j<M.size();j++)
{
if(M[laoda][j]==1&&visited[j]==0)//当前节点还没有走访,而且是当前阵营
{
visited[j]=1;//设置成已走访
dfs(M,visited,j);//当前这个小鹏有的朋友全部走访
}
}
}
int findCircleNum(vector<vector<int>>& M) {
int n=M.size();//小朋友的数目
vector<int> visited(n,0);//初始化走访数组,0表示还没有走访到
int count=0;
for(int i=0;i<n;i++)
{
if(visited[i]==0)//还没有走访,说明这是一个新的朋友圈,因为深度优先遍历保证一个朋友圈的所有小朋友都一次性的走访一遍
{
count++;
dfs(M,visited,i);//深度优先遍历将这个新的朋友圈的小朋友全部走访一遍
}
}
return count;
}
};
上面方法的缺点是,每一层级都要重新从第一个小朋友开始判断,虽然已经走访过的孩子不需要在走访,但是当朋友圈很多时,还是很复杂。
思路2:采用并查集
class Solution {
public:
//查询操作
int find(int i, vector<int>& parent){
while(parent[i]!=i)//当没到根节点时
{
parent[i]=parent[parent[i]];//路径压缩,跨级提升。原来是县对市,现在县变成了地级市,直接找到省委,市直接找到
i=parent[i];
}
return i;//最后找到代表节点
}
//合并操作
void unionFriend(vector<int>& parent,vector<int>& rank,int& i,int& j)
{
int iFind=find(i,parent);//寻找根代表
int jFind=find(j,parent);
if(iFind==jFind) return ;
if(rank[iFind]>rank[jFind])
{
parent[jFind]=iFind;
}
else
{
parent[iFind]=jFind;
if(rank[iFind]==rank[jFind])
rank[jFind]++;
}
count--;
}
int findCircleNum(vector<vector<int>>& M) {
int n=M.size();
count=n;
//初始化过程
vector<int> parent(n,0);
vector<int> rank(n,0);//层级都设置成0
for(int i=0;i<n;i++)
{
parent[i]=i;//每个节点的根节点都设置成自己
}
for(int i=0;i<n-1;i++)
for(int j=i+1;j<n;j++)//这种遍历没有重复
{
if(M[i][j])//两个人是朋友
{
unionFriend(parent,rank,i,j);//把两个人合并起来
}
}
return count;
}
private:
int count=0;
};
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:
11110
11010
11000
00000
Answer: 1
Example 2:
11000
11000
00100
00011
Answer: 3
思路1:DFS
方式与上题相同,设置访问数组,记录已经访问过的元素
class Solution {
public:
void dfs(vector<vector<int>>& visited,vector<vector<char>>& grid,int i,int j,int&row,int& col)
{
int oren[4][2]={{0,1},{0,-1},{-1,0},{1,0}};
visited[i][j]=1;
for(int k=0;k<4;k++)
{
int curi=i+oren[k][0];
int curj=j+oren[k][1];
if(curi>=0&&curi<row&&curj>=0&&curj<col&&visited[curi][curj]==0&&grid[curi][curj]=='1')
dfs(visited,grid,curi,curj,row,col);
}
}
int numIslands(vector<vector<char>>& grid) {
int row=grid.size();
if(row==0) return 0;
int col=grid[0].size();
int count=0;
vector<vector<int>> visited(row,vector<int>(col,0));//访问数组
for(int i=0;i<row;i++)
{
for(int j=0;j<col;j++)
{
if(grid[i][j]=='1'&&visited[i][j]==0)
{
count++;
dfs(visited,grid,i,j,row,col);
}
}
}
return count;
}
};
其他人的方法:省略visited数组,将原来的1改成0,节省空间。意义不是很大。
public class Solution {
private int n;
private int m;
public int numIslands(char[][] grid) {
int count = 0;
n = grid.length;
if (n == 0) return 0;
m = grid[0].length;
for (int i = 0; i < n; i++){
for (int j = 0; j < m; j++)
if (grid[i][j] == '1') {
DFSMarking(grid, i, j);
++count;
}
}
return count;
}
private void DFSMarking(char[][] grid, int i, int j) {
if (i < 0 || j < 0 || i >= n || j >= m || grid[i][j] != '1') return;
grid[i][j] = '0';
DFSMarking(grid, i + 1, j);
DFSMarking(grid, i - 1, j);
DFSMarking(grid, i, j + 1);
DFSMarking(grid, i, j - 1);
}