union find
并查集是用于管理元素分组情况的数据结构,注意并查集虽然可以进行合并操作但是无法进行分割操作。主要有下面两个操作:
- 查询两个元素是否在同一组
- 合并两个元素所在的组
并查集操作
并查集使用树作为数据结构(不一定是二叉树)
合并
从一个组的根向另一个组的根连一条边。这里一定要注意,合并的时候是把两个节点所在的树合并到一起,而不仅仅是将一个节点连到另一个节点上。
查询
查询两个元素是否在同一组内,只需查找它们是否在同一个树内(从两个节点向上走看是否能走到同一个根)。
在具体实现中我们还有如下技巧:
一是避免退化(即根的子树在高度上有较大差异),对于每棵树我们记录高度,在合并时从高度小的树向高度大的树连边(让高度较小的树称为高度较大的树的子树)
而是路径压缩,当我们查询节点时,一旦这个节点走到了根节点我们就将该节点连接到根节点上,同时将查询过程中的所有节点全部连接到根节点上。
牛刀小试
本题思路十分简单,可以将所有学生看成节点,若任意两位学生之间是朋友关系,则进行合并操作,开始时所有节点各自独立,朋友圈的个数为所有学生人数,每发生一次合并操作,就减掉一个朋友圈,遍历整个矩阵即可得到结果。
class Solution {
const int MAX_N = 1000;
int par[MAX_N];
int height[MAX_N];
public:
int findCircleNum(vector<vector<int>>& M) {
int total = M.size();
init(total);
int sum = total;
for (int i = 0; i < total; i++) {
for (int j = 0; j < total; j++) {
if(i==j){
continue;
}
if (M[i][j] == 1) {
unite(i, j, &sum);
// M[j][i] = -1;
}
}
}
return sum;
}
void init(int total)
{
for (int i = 0; i < total; i++) {
par[i] = i;
height[i] = 0;
}
}
int find(int value) //寻找value的根节点
{
int res;
if (par[value] == value) {
res = value;
}
else {
res = find(par[value]);
}
par[value] = res;
return res;
}
void unite(int x, int y,int *sum)
{
x = find(x);
y = find(y);
if (x == y) {
return;
}
(*sum)--;
if (height[x] > height[y]) {
par[y] = x; //将y的父节点连到x上
}
else {
par[x] = y;
if (height[x] == height[y]) {
height[x]++;
}
}
}
};
时间复杂度为 o ( n 2 ) o(n^2) o(n2),对二维数组进行了遍历。