并查集
并查集主要有三个功能。
- 寻找根节点,函数:find(int u),也就是判断这个节点的祖先节点是哪个
- 将两个节点接入到同一个集合,函数:join(int u, int v),将两个节点连在同一个根节点上
- 判断两个节点是否在同一个集合,函数:isSame(int u, int v),就是判断两个节点是不是同一个根节点
并查集可以解决的问题:两个节点是否在一个集合,也可以将两个节点添加到一个集合中。
难点在于根的路径压缩的理解
寻找图中是否存在路径
1971. 寻找图中是否存在路径
有一个具有 n
个顶点的 双向 图,其中每个顶点标记从 0
到 n - 1
(包含 0
和 n - 1
)。图中的边用一个二维整数数组 edges
表示,其中 edges[i] = [ui, vi]
表示顶点 ui
和顶点 vi
之间的双向边。 每个顶点对由 最多一条 边连接,并且没有顶点存在与自身相连的边。
请你确定是否存在从顶点 source
开始,到顶点 destination
结束的 有效路径 。
给你数组 edges
和整数 n
、source
和 destination
,如果从 source
到 destination
存在 有效路径 ,则返回 true
,否则返回 false
。
class Solution {
public:bool validPath(int n, vector<vector<int>>& edges, int source, int destination) {/*深搜 / 广搜这里选择使用并查集进行实现使用并查集判断两个元素是否在同一个集合内部:step1: 使用join(u,v)把每条边加入到并查集step2: 使用 isSame(int u,int v) 判断是否是同一个根【即是否属于同一个集合】*/// step0: 并查集初始化init(n);// step1: 把每条边加入并查集for(vector<int> edge : edges) { // 每个元素就是一条边join(edge[0],edge[1]);}// step2: 使用 isSame(int u,int v) 判断是否是同一个根return isSame(source, destination);}
private:vector<int> father = vector<int>(200001,0) ; // 按照节点的大小定义数组长度void init(int n) { // 并查集初始化for(int i = 1; i <= n; i++) {father[i] = i; //初始化。每个元素都是自己的根}}// 并查集里寻找根的过程int find(int u) {return u== father[u] ? u : father[u] = find(father[u]);}// 判断 u 和 v 是否找到同一个根bool isSame(int u, int v) {u = find(u);v = find(v);return u == v;}// 把 v-> u 这条边加入并查集 father[v] = uvoid join(int u, int v) {// 先判断两个元素是否在同一个集合内部u = find(u);v = find(v);if(u == v) return;father[v] = u;}
};
冗余连接
684. 冗余连接
树可以看成是一个连通且 无环 的 无向 图。
给定往一棵 n
个节点 (节点值 1~n
) 的树中添加一条边后的图。添加的边的两个顶点包含在 1
到 n
中间,且这条附加的边不属于树中已存在的边。图的信息记录于长度为 n
的二维数组 edges
,edges[i] = [ai, bi]
表示图中在 ai
和 bi
之间存在一条边。
请找出一条可以删去的边,删除后可使得剩余部分是一个有着 n
个节点的树。如果有多个答案,则返回数组 edges
中最后出现的那个。
class Solution {public int[] findRedundantConnection(int[][] edges) {/**图论:删除相对于数来说的多余的一条边使用并查集的思想:把每条边都加入到其中,如果在加入的时候发现两个顶点已经同根;(即在一个并查集中)此时就说明这条边是一条冗余边,删除这条边即可*/int[] ans = null;init(edges.length);for(var edge : edges) {if(!join(edge[0],edge[1])) {ans = edge;break;}}return ans;}private int[] father;private void init(int vLen) { // 并查集的初始化 // 传入顶点数father = new int[vLen+1];for(int i=0; i < vLen; i++) {father[i] = i; // father[i] = i; 自身是自身的根,即刚开始所有节点都是单项的}}// 找到一个元素的根int find(int u) {return father[u] == u ? u: (father[u] = find(father[u]));}// 把 u->v 加入并查集private boolean join(int u, int v) {u = find(u);v = find(v);if(u == v) return false;father[u] = v;return true;}// 判断两个节点是否同根public boolean isSame(int u, int v) {u = find(u);v = find(v);return u == v;}
}
冗余连接Ⅱ
685. 冗余连接 II
在本问题中,有根树指满足以下条件的 有向 图。该树只有一个根节点,所有其他节点都是该根节点的后继。该树除了根节点之外的每一个节点都有且只有一个父节点,而根节点没有父节点。
输入一个有向图,该图由一个有着 n
个节点(节点值不重复,从 1
到 n
)的树及一条附加的有向边构成。附加的边包含在 1
到 n
中的两个不同顶点间,这条附加的边不属于树中已存在的边。
结果图是一个以边组成的二维数组 edges
。 每个元素是一对 [ui, vi]
,用以表示 有向 图中连接顶点 ui
和顶点 vi
的边,其中 ui
是 vi
的一个父节点。
返回一条能删除的边,使得剩下的图是有 n
个节点的有根树。若有多个答案,返回最后出现在给定二维数组的答案。
class Solution {
public:/* 算法分成三种情况:1:找到入度为2的节点,删除其中的一条边,要注意删除边后剩余的部分依然能构成一颗有向树情况2:如果没有入度为2的节点,则说明题目中有环,删除构成环的边即可 */vector<int> findRedundantDirectedConnection(vector<vector<int>>& edges) {// 顶点数 = 边数int n = edges.size();vector<int> inDegree(n+1,0); // step1: 先统计每个节点的入度for(vector<int> edge : edges) {inDegree[edge[1]] ++;}// for(int degree : inDegree) {// cout << degree << endl;// }// return inDegree;// 情况1和2:// 记录其中入度为2的边(若有的话就两条边)vector<int> edge;// 从后往前:因为优先要删除后面的那条边for(int i = n-1; i>=0; i--) {if(inDegree[edges[i][1]] == 2) { // 这条边后面的入度节点为2edge.push_back(i); // edge存入的是要删除边的下标}}// 考虑情况1与2if(edge.size() > 0){if(isTreeAfterRemoveEdge(edges,edge[0])) {return edges[edge[0]];}else{return edges[edge[1]];}}// 情况三 // 此时只有入度为1的顶点,即一定会存在有向环,需要找到构成环的边返回return getRemoveEdge(edges);}private: vector<int> father = vector<int>(1001,0);// 并查集void init(int n) {for(int i = 1; i <= n; ++i) {father[i] = i;}}// 寻找根int find(int u) {return u == father[u] ? u : father[u] = find(father[u]);}// 将 v->u 这条边加入并查集void join(int u,int v) {u = find(u);v = find(v);if(u == v) return;father[v] = u;}// 判断u与v是否找到同一个根(即是否在同一棵上)bool isSame(int u, int v) {u = find(u);v = find(v);return u == v;}// 删除一条边后判断是不是树bool isTreeAfterRemoveEdge(const vector<vector<int>>& edges, int delEdge) {int n = edges.size();init(n);for(int i=0; i<n ; i++) {if(i == delEdge) continue;if(isSame(edges[i][0],edges[i][1])) {return false; // 构成了有向环,则一定不是树}join(edges[i][0],edges[i][1]); // 两条边加入并查集}return true;}// 在已经是环的情况下找到删除的那边条// 如果说一条边的两端点已经在并查集中,那这条边不就是多余的吗vector<int> getRemoveEdge(const vector<vector<int>>& edges) {int n = edges.size();init(n);int ans = 0;for(int i=0; i<n; i++) {if(isSame(edges[i][0],edges[i][1])) { // 构成环了就是要找的边ans = i;break;}else {join(edges[i][0], edges[i][1]);}}return edges[ans];}
};