并查集原理
一般可以用数组来表示并查集,数据的下标就是每个数据的编号,对应的值如果是负数,那么就代表它自成一个集合,也就是一个根结点。如图,初始值一般都是负数,也就是每个元素都自成一个集合,如果一个元素是根,那么负数的绝对值则代表这个树有多少结点;如果不是负数,那么则说明这个元素不是根,且对应的值存的是父亲结点的下标。
实际应用中也可以通过加入哈希表来设置更多的映射关系,这样的话可以将数组的下标作为key值,然后可以存人名这些。
所以并查集其实也是多棵树组成的森林。
用并查集表示就是
元素之间可以合并,并且可以制定合并规则。集合与集合之间也可以合并。
综上,我们可以知道并查集可以解决以下问题:
1.查找某个元素属于哪个集合。沿着数组表示的树形关系向上可以一直找到根。
2.查看两个元素是否属于同一个集合。可以看找到的根是否相同,以此来判断是否属于同一个集合。
3.可以将两个集合归并成一个集合。
4.求集合的个数。其实也就是根的个数,可以遍历数组,记录下标为负数的元素即是集合的个数。
并查集简单实现
#pragma once#include <vector>//可以设计成模板,加入哈希表来存放更多的映射关系class UnionFindSet
{
public:UnionFindSet(int size):_set(size, -1){}~UnionFindSet(){}size_t FindRoot(int x){int root = x;while (_set[root] >= 0)root = _set[root]; // 向上找到根while (_set[x] >= 0) // 向上遍历,依次修改父亲的父亲结点,使其变为根结点的儿子。// 路径压缩{int parent = _set[x];_set[x] = root;x = parent;}return root;}void Union(int x1, int x2){int root1 = FindRoot(x1);int root2 = FindRoot(x2);if (abs(_set[root1]) < abs(_set[root2])) // 小优化,每次合并都合并到结点更多的树中,可以减少层数std::swap(_set[root1], _set[root2]);if (root1 != root2) // 如果相等说明在一个集合内,没必要合并{_set[root1] += _set[root2];_set[root2] = root1; // 这里默认合并到root1}}int SetCount() // 找根结点个数{size_t count = 0;for (size_t i = 0; i < _set.size(); ++i){if (_set[i] < 0)count++;}return count;}
private:std::vector<int> _set;
};void TestUFS()
{UnionFindSet u(10);u.Union(0, 6);u.Union(7, 6);u.Union(7, 8);u.Union(1, 4);u.Union(4, 9);u.Union(2, 3);u.Union(2, 5);std::cout << u.SetCount() << std::endl;
}
另外说下压缩路径的问题,当并查集的数据非常大时,我们要找到这个元素的根,可能就需要向上找很久,有一种解决方案就是,在每次查找的时候,如果它的父亲结点不是根结点的话,就将它放到根结点的儿子结点。这样就能减少遍历的次数。
并查集虽然是一种数据结构,但是有时候又可以是一种解决问题的思路。
比如这道题,可以直接手搓一个简单的并查集,很容易就秒掉。
class Solution {
public:int findCircleNum(vector<vector<int>>& isConnected) {int n = isConnected.size();vector<int> set(n,-1);auto findRoot = [&set](int x){while(set[x] >= 0)x = set[x];return x;};for(int i = 0; i < n; i++){for(int j = 0; j < n; j++){if(isConnected[i][j] == 1){int root1 = findRoot(i);int root2 = findRoot(j);if(root1 != root2){set[root1] += set[root2];set[root2] = root1;}}}}int count = 0;for(auto &x : set){if(x < 0)count++;}return count;}
};
总结并查集的特点:
1.一个位置的值是负数,那么它就是树的根,这个负数的绝对值就是这个树结点的个数。
2.一个位置的值是正数,那么这个正数就是它父亲结点的下标。