并查集介绍
看这篇介绍了解并查集:
酱懵静------并查集
冗余连接
/**
* 在本问题中, 树指的是一个连通且无环的无向图。
*
* 输入一个图,该图由一个有着N个节点 (节点值不重复1, 2, ..., N) 的树及一条附加的边构成。附加的边的两个顶点包含在1到N中间,这条附加的边不属于树中已存在的边。
*
* 结果图是一个以边组成的二维数组。每一个边的元素是一对[u, v] ,满足 u < v,表示连接顶点u 和v的无向图的边。
*
* 返回一条可以删去的边,使得结果图是一个有着N个节点的树。如果有多个答案,则返回二维数组中最后出现的边。答案边 [u, v] 应满足相同的格式 u < v。
*
* 示例 1:
*
* 输入: [[1,2], [1,3], [2,3]]
* 输出: [2,3]
* 解释: 给定的无向图为:
* 1
* / \
* 2 - 3
* 示例 2:
*
* 输入: [[1,2], [2,3], [3,4], [1,4], [1,5]]
* 输出: [1,4]
* 解释: 给定的无向图为:
* 5 - 1 - 2
* | |
* 4 - 3
*
*/
public static void main(String[] args) {
int[][] edges={{1,2}, {1,3}, {2,3}};
System.out.println(Arrays.toString(findRedundantConnection(edges)));
int[][] edges2={{1,2}, {2,3}, {3,4}, {1,4}, {1,5}};
System.out.println(Arrays.toString(findRedundantConnection(edges2)));
int[][] edges3={{1,2},{2,3},{1,5},{3,4},{1,4}};
System.out.println(Arrays.toString(findRedundantConnection(edges3)));
int[][] edges4={{4,5},{1,2},{2,4},{3,4},{2,3}};
System.out.println(Arrays.toString(findRedundantConnection(edges4)));
}
//这题考察并查集 可以通过并查集寻找附加的边。
//初始时,每个节点都属于不同的连通分量。遍历每一条边,判断这条边连接的两个顶点是否属于相同的连通分量。
// 如果两个顶点属于不同的连通分量,则说明在遍历到当前的边之前,这两个顶点之间不连通,因此当前的边不会导致环出现,合并这两个顶点的连通分量。
// 如果两个顶点属于相同的连通分量,则说明在遍历到当前的边之前,这两个顶点之间已经连通,因此当前的边导致环出现,为附加的边,将当前的边作为答案返回。
public static int[] findRedundantConnection(int[][] edges) {
int nodesCount=edges.length;
int[] parent=new int[nodesCount+1];
for (int i = 0; i <nodesCount ; i++) {
parent[i]=i;
}
for (int i = 0; i <nodesCount ; i++) {
int[] edge=edges[i];
int node1=edge[0],node2=edge[1];
if(find(parent,node1)!=find(parent,node2)){
union(parent,node1,node2);
}else {
return edge;
}
}
return new int[0];
}
//合并并查集
public static void union(int[] parent,int index1,int index2){
parent[find(parent,index1)]=find(parent,index2);
}
//查找源头
public static int find(int[] parent,int index){
if(parent[index]!=index){
parent[index]=find(parent,parent[index]);
}
return parent[index];
}
保证图可完全遍历
public class 保证图可完全遍历 {
/**
* Alice 和 Bob 共有一个无向图,其中包含 n 个节点和 3 种类型的边:
*
* 类型 1:只能由 Alice 遍历。
* 类型 2:只能由 Bob 遍历。
* 类型 3:Alice 和 Bob 都可以遍历。
* 给你一个数组 edges ,其中 edges[i] = [typei, ui, vi] 表示节点 ui 和 vi 之间存在类型为 typei 的双向边。
* 请你在保证图仍能够被 Alice和 Bob 完全遍历的前提下,找出可以删除的最大边数。
* 如果从任何节点开始,Alice 和 Bob 都可以到达所有其他节点,则认为图是可以完全遍历的。
*
* 返回可以删除的最大边数,如果 Alice 和 Bob 无法完全遍历图,则返回 -1 。
*
*/
public static void main(String[] args) {
保证图可完全遍历 t=new 保证图可完全遍历();
int[][] edges={{3,1,2},{3,2,3},{1,1,3},{1,2,4},{1,1,2},{2,3,4}};
System.out.println(t.maxNumEdgesToRemove(4,edges));
}
public int maxNumEdgesToRemove(int n, int[][] edges) {
int res=0;
UnionFind ufa=new UnionFind(n+1);
UnionFind ufb=new UnionFind(n+1);
//公共边
for (int[] edge :edges){
if(edge[0]==3){
boolean u1=ufa.union(edge[1],edge[2]);
boolean u2=ufb.union(edge[1],edge[2]);
if(!u1&&!u2){
res++; //删除该公共边
}
}
}
for (int[] edge :edges){
if(edge[0]==1){
//只能由 Alice 遍历
boolean u1=ufa.union(edge[1],edge[2]);
if(!u1) res++;
}else if (edge[0]==2){
//只能由 Bob 遍历
boolean u2=ufb.union(edge[1],edge[2]);
if(!u2) res++;
}
}
if(ufa.count!=1||ufb.count!=1){
return -1;
}
return res;
}
private class UnionFind {
private int[] parent;
private int count;
public int getCount() {
return count;
}
public UnionFind(int n) {
this.count = n-1;
this.parent = new int[n];
for (int i = 1; i < n; i++) {
parent[i] = i;
}
}
public int find(int x) {
while (x != parent[x]) {
parent[x] = parent[parent[x]];
x = parent[x];
}
return x;
}
public boolean union(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX == rootY) { //形成环
return false;
}
parent[rootX] = rootY;
count--;
return true;
}
}
}
账户合并
public class 账户合并 {
/**
* 给定一个列表 accounts,每个元素 accounts[i] 是一个字符串列表,
* 其中第一个元素 accounts[i][0] 是 名称 (name),其余元素是 emails 表示该账户的邮箱地址。
*
* 现在,我们想合并这些账户。如果两个账户都有一些共同的邮箱地址,则两个账户必定属于同一个人。请注意,即使两个账户具有相同的名称,
* 它们也可能属于不同的人,因为人们可能具有相同的名称。一个人最初可以拥有任意数量的账户,但其所有账户都具有相同的名称。
*
* 合并账户后,按以下格式返回账户:每个账户的第一个元素是名称,其余元素是按顺序排列的邮箱地址。账户本身可以以任意顺序返回。
*
* 示例 1:
*
* 输入:
* accounts = [["John", "johnsmith@mail.com", "john00@mail.com"], ["John", "johnnybravo@mail.com"],
* ["John", "johnsmith@mail.com", "john_newyork@mail.com"], ["Mary", "mary@mail.com"]]
* 输出:
* [["John", 'john00@mail.com', 'john_newyork@mail.com', 'johnsmith@mail.com'],
* ["John", "johnnybravo@mail.com"], ["Mary", "mary@mail.com"]]
*
* 解释:
* 第一个和第三个 John 是同一个人,因为他们有共同的邮箱地址 "johnsmith@mail.com"。
* 第二个 John 和 Mary 是不同的人,因为他们的邮箱地址没有被其他帐户使用。
* 可以以任何顺序返回这些列表,例如答案 [['Mary','mary@mail.com'],['John','johnnybravo@mail.com'],
* ['John','john00@mail.com','john_newyork@mail.com','johnsmith@mail.com']] 也是正确的。
*
*
* 提示:
* accounts的长度将在[1,1000]的范围内。
* accounts[i]的长度将在[1,10]的范围内。
* accounts[i][j]的长度将在[1,30]的范围内。
*
*/
public static void main(String[] args) {
账户合并 h=new 账户合并();
List<List<String>> accounts=new ArrayList<>();
List<String> list1=Arrays.asList(new String[]{"John", "johnsmith@mail.com", "john00@mail.com"});
List<String> list2=Arrays.asList(new String[]{"John", "johnnybravo@mail.com"});
List<String> list3=Arrays.asList(new String[]{"John", "johnsmith@mail.com", "john_newyork@mail.com"});
List<String> list4=Arrays.asList(new String[]{"Mary", "mary@mail.com"});
accounts.add(list1);
accounts.add(list2);
accounts.add(list3);
accounts.add(list4);
System.out.println(h.accountsMerge(accounts));
}
public List<List<String>> accountsMerge(List<List<String>> accounts) {
Map<String,Integer> email2Index=new HashMap<>();
Map<String,String> email2name=new HashMap<>();
int emailsCount = 0;
for (int i = 0; i < accounts.size(); i++) {
List<String> account=accounts.get(i);
String name=account.get(0);
for (int j = 1; j < account.size() ; j++) {
email2Index.put(account.get(j),emailsCount++);
email2name.put(account.get(j),name);
}
}
//并查集 合并index
UnionFind uf=new UnionFind(emailsCount);
for (List<String> account : accounts) {
String firstEmail = account.get(1);
int firstIndex=email2Index.get(firstEmail);
for (int i =2; i < account.size(); i++) {
int index2=email2Index.get(account.get(i));
uf.union(firstIndex,index2);
}
}
Map<Integer, List<String>> indexToEmails = new HashMap<Integer, List<String>>();
for (String email:email2Index.keySet()) {
int index=uf.find(email2Index.get(email));
List<String> account=indexToEmails.getOrDefault(index,new ArrayList<String>());
account.add(email);
indexToEmails.put(index,account);
}
//生成结果
List<List<String>> res=new ArrayList<>();
for (List<String> emails: indexToEmails.values()){
Collections.sort(emails);
String name=email2name.get(emails.get(0));
List<String> account=new ArrayList<>();
account.add(name);
account.addAll(emails);
res.add(account);
}
return res;
}
//并查集模板类
class UnionFind {
int[] parent;
public UnionFind(int n) {
parent = new int[n];
for (int i = 0; i < n; i++) {
parent[i] = i;
}
}
public void union(int index1, int index2) {
parent[find(index2)] = find(index1);
}
public int find(int index) {
if (parent[index] != index) {
parent[index] = find(parent[index]);
}
return parent[index];
}
}
}
连接所有点的最小费用
public class 连接所有点的最小费用 {
/**
* 给你一个points 数组,表示 2D 平面上的一些点,其中 points[i] = [xi, yi] 。
*
* 连接点 [xi, yi] 和点 [xj, yj] 的费用为它们之间的 曼哈顿距离 :|xi - xj| + |yi - yj| ,其中 |val| 表示 val 的绝对值。
*
* 请你返回将所有点连接的最小总费用。只有任意两点之间 有且仅有 一条简单路径时,才认为所有点都已连接。
*
* 1 <= points.length <= 1000
* -106 <= xi, yi <= 106
* 所有点 (xi, yi) 两两不同。
* @param args
*/
public static void main(String[] args) {
连接所有点的最小费用 t=new 连接所有点的最小费用();
int[][] points= {{3,12},{-2,5},{-4,1}};
System.out.println(t.minCostConnectPoints(points)); //18
int[][] points2= {{0,0},{1,1},{1,0},{-1,1}};
System.out.println(t.minCostConnectPoints(points2));
}
public int minCostConnectPoints(int[][] points) {
//先计算所有最短链接
List<List<Integer>> allConnectPoint=new ArrayList<>();
for (int i = 0; i <points.length ; i++) {
int[] p1=points[i];
for (int j = i+1; j <points.length ; j++) {
int[] p2=points[j];
//计算距离
int distance=Math.abs(p1[0]-p2[0])+Math.abs(p1[1]-p2[1]);
List<Integer> list=Arrays.asList(new Integer[]{distance,i,j});
allConnectPoint.add(list);
}
}
Collections.sort(allConnectPoint,(a,b)->a.get(0)-b.get(0));
int res=0;
int n=0;
UnionFind uf=new UnionFind(1001);
for (List<Integer> list:allConnectPoint){
int distance=list.get(0);
int x=list.get(1),y=list.get(2);
if(uf.union(x,y)){
res+=distance;
n++;
if(n==points.length-1) break;
}
}
return res;
}
//并查集模板类
class UnionFind {
int[] parent;
public UnionFind(int n) {
parent = new int[n];
for (int i = 0; i < n; i++) {
parent[i] = i;
}
}
public boolean union(int index1, int index2) {
int x= find(index1),y=find(index2);
if(x== y) return false;
parent[find(index2)] = find(index1);
return true;
}
public int find(int index) {
if (parent[index] != index) {
parent[index] = find(parent[index]);
}
return parent[index];
}
}
找到最小生成树里的关键边和伪关键边
public class 找到最小生成树里的关键边和伪关键边 {
public static void main(String[] args) {
找到最小生成树里的关键边和伪关键边 t = new 找到最小生成树里的关键边和伪关键边();
int[][] edges = {{0, 1, 1}, {1, 2, 1}, {2, 3, 2}, {0, 3, 2}, {0, 4, 3}, {3, 4, 3}, {1, 4, 6}};
System.out.println(t.findCriticalAndPseudoCriticalEdges(5, edges)); //[[0, 1], [2, 3, 4, 5]]
int[][] edges2 ={{0,1,1},{1,2,1},{2,3,1},{0,3,1}};
System.out.println(t.findCriticalAndPseudoCriticalEdges(5, edges2)); //[[], [0, 1, 2, 3]]
int[][] edges3 ={{0,1,1},{0,3,1},{0,2,1},{1,2,1},{1,3,1},{2,3,1}};
System.out.println(t.findCriticalAndPseudoCriticalEdges(4, edges3)); //[[], [0, 1, 2, 3, 4, 5]]
int[][] edges4={{0,1,2},{0,2,5},{2,3,5},{1,4,4},{2,5,5},{4,5,2}};
System.out.println(t.findCriticalAndPseudoCriticalEdges(4, edges4)); //[[0,2,3,5],[1,4]]
}
/**
* 先排序
* 使用克鲁斯卡尔算法 来构造最小生成数
* 先计算一次 保存最小权重
* 遍历 每次先确定一条边,然后计算每次获得的最小生成数的权重是否和最小权重相同。
* 不相同这表示先确定的这条边是关键边。
* 伪关键边:我们可以在计算最小生成树的过程中,最先考虑这条边,即最先将这条边的两个端点在并查集中合并。
* 设最终得到的最小生成树权值为 vv,如果 v = \textit{value}v=value,那么这条边就是伪关键边。
*/
class Edge{
int oldIIndex;
int wight;
public Edge(int oldIIndex, int wight) {
this.oldIIndex = oldIIndex;
this.wight = wight;
}
}
public List<List<Integer>> findCriticalAndPseudoCriticalEdges(int n, int[][] edges) {
List<List<Integer>> res = new ArrayList<>();
List<Integer> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
//按权重排序
List<Edge> edgeList=new ArrayList<>();
for (int i = 0; i < edges.length; i++) {
Edge edge=new Edge(i,edges[i][2]);
edgeList.add(edge);
}
Collections.sort(edgeList,(a, b) -> a.wight - b.wight);
Map<Integer,Integer> oldIndexMap =new HashMap<>();
for (int i = 0; i < edgeList.size(); i++) {
oldIndexMap.put(i,edgeList.get(i).oldIIndex);
}
Arrays.sort(edges, (a, b) -> a[2] - b[2]);
//先计算一次最小权值
int mixTreeWeight = getMixTreeWeight( edges, -1,false);
for (int i = 0; i < edges.length; i++) {
int nowWight = getMixTreeWeight( edges, i,false);
if (nowWight == mixTreeWeight) {
nowWight=getMixTreeWeight(edges,i,true);
if(nowWight==mixTreeWeight) list2.add(oldIndexMap.get(i));
}else {
list1.add(oldIndexMap.get(i));
}
}
Collections.sort(list1);
Collections.sort(list2);
res.add(list1);
res.add(list2);
return res;
}
public int getMixTreeWeight( int[][] edges, int index,Boolean fristAdd) {
int wight = 0;
UnionFind uf = new UnionFind(200);
if(fristAdd) {
uf.union(edges[index][0],edges[index][1]); //先加这条边
wight += edges[index][2];
}
for (int i = 0; i < edges.length; i++) {
if (i != index) { //排除一条边
int x = edges[i][0], y = edges[i][1];
if (uf.find(x) != uf.find(y)) {
uf.union(x, y);
wight += edges[i][2];
}
}
}
return wight;
}
//并查集模板类
class UnionFind {
int[] parent;
public UnionFind(int n) {
parent = new int[n];
for (int i = 0; i < n; i++) {
parent[i] = i;
}
}
public void union(int index1, int index2) {
parent[find(index2)] = find(index1);
}
public int find(int index) {
if (parent[index] != index) {
parent[index] = find(parent[index]);
}
return parent[index];
}
}
}