1、问题描述
动态连通性问题。给定一组数,为0-N-1,然后给定一组整数对,每个整数对表明这两个整数是相连的,在接受整数对是,如果二者不相连,则输出该整数对。2与3相连,1与2相连,则1与3相连。
需要实现一个类,四个函数UF(int N);//初始化该类,有N个整数,0-N-1void union(int q , int p);//在p和q上建立一条连接int find(int p);//返回p所在的连通分量标识boolean connected(int p . int q);//如果p和q存在于同一个分量中则返回trueint count();//返回连通分量的个数
2、quick-find算法
保留一个数组,记录该整数对应的连通分量标识,初始时连通分量为自身,连接两个数时,如果两个数的连通分量不一样,需要把两个连通分量的所有数的标识统一。
package union_find;import java.util.Queue;/*** @author zwlstart* @create 2020-12-19 20:56*/public class QuickFind {//记录每个数的连通分量的标识int[] id;//记录连通分量的个数int count;public QuickFind() {}public QuickFind(int n) {id = new int[n];//连通分量个数初始化为ncount = n;//初始化每个整数的连通分量标识为自己for (int i = 0; i < n; i++) {id[i] = i;}}public int find(int p){//返回每个数的连通分量标识return id[p];}public void union(int p,int q){//将两个数连接起来,需要将两个连通分量所有的数标识统一//需要遍历整个数组if (find(p) == find(q)){return;}int pID = find(p);int qID = find(q);for (int i = 0; i < id.length; i++) {if (id[i] == pID){id[i] = qID;}}//每次连接都需要将连通分量数减一count--;}public boolean connected(int p , int q){//判断两个数是否连通return find(p) == find(q);}public int count(){return count;}}
该算法的主要问题是,每次建立新的连接都需要遍历数组,在极端情况下,当需要建立很多连接时,算法是n的平方级别,这对于大规模问题是不可接受的。
3、quick-union算法
可知,上述算法最主要的问题是每次建立连接时都需要遍历数组,造成了极大地时间开销。因此优化思路是降低建立连接时的开销。
具体思路是采用并查集,让一个连通分量中的所有数都指向一个共同的祖先,那样将建立连接时,只需要一个改动即可。数的组织形式在逻辑上是树形结构。
package union_find;/*** @author zwlstart* @create 2020-12-19 20:56*/public class QuickUnion {//记录每个数的连通分量的标识int[] id;//记录连通分量的个数int count;public QuickUnion() {}public QuickUnion(int n) {id = new int[n];//连通分量个数初始化为ncount = n;//初始化每个整数的连通分量标识为自己for (int i = 0; i < n; i++) {id[i] = i;}}public int find(int p){//返回每个数的连通分量标识//一个连通分量中的所有数都指向共同的祖先,即根节点//根节点指向自身while (p != id[p]){p = id[p];}return p;}public void union(int p,int q){//将两个数连接起来,需要将两个连通分量所有的数标识统一if (find(p) == find(q)){return;}int pID = find(p);int qID = find(q);//只需要将p的连通分量的根节点指向p的连通分量的根节点即可id[pID] = qID;//每次连接都需要将连通分量数减一count--;}public boolean connected(int p , int q){//判断两个数是否连通return find(p) == find(q);}public int count(){return count;}}
该算法与上一个算法相比性能有很大提升,每次插入所需要的时间消耗是类似的树从底部到根节点,即是树的深度。在极端情况下,为一个单项树时,时间复杂度也为n的平方级别。
3、加权quick-union算法
针对上一个问题,主要问题是每次建立连接时树的深度问题,如何降低树的深度是优化算法的关键,即采用加权插入的思想,即每次将小树插入到大树,那么树的深度就会很小,当有n个数时,采用这种插入方式,树的深度最多为log(n)。
package union_find;/*** @author zwlstart* @create 2020-12-19 20:56*/public class WeightedQuickUnion {//记录每个数的连通分量的标识int[] id;//记录每个连通分量的大小int[] num;//记录连通分量的个数int count;public WeightedQuickUnion() {}public WeightedQuickUnion(int n) {id = new int[n];//连通分量个数初始化为ncount = n;//初始化每个整数的连通分量标识为自己for (int i = 0; i < n; i++) {id[i] = i;}//初始化每个连通分量的大小,初始值均为1num = new int[n];for (int i = 0; i < n; i++) {num[i] = 1;}}public int find(int p){//返回每个数的连通分量标识//一个连通分量中的所有数都指向共同的祖先,即根节点//根节点指向自身while (p != id[p]){p = id[p];}return p;}public void union(int p,int q){//将两个数连接起来,需要将两个连通分量所有的数标识统一if (find(p) == find(q)){return;}int pID = find(p);int qID = find(q);//每次将小树插入大树if (num[pID] < num[qID]){id[pID] = qID;num[qID] += num[pID];}else{id[qID] = pID;num[pID] += num[qID];}//每次连接都需要将连通分量数减一count--;}public boolean connected(int p , int q){//判断两个数是否连通return find(p) == find(q);}public int count(){return count;}}
加权插入算法已经是非常优秀的算法了,可以保证在任何输入下,每次的建立连接的时间在log(n)级别。
4、最优算法,路径压缩算法
优化的思路是尽量将每次建立连接的时间为常数级别,但这是一件做不到的事情,路径压缩算法的思想是每次遍历的时候,直接将该节点指向根节点。
只需要修改find方法
package union_find;/*** @author zwlstart* @create 2020-12-19 20:56*/public class CompressionWeightedQuickUnion {//记录每个数的连通分量的标识int[] id;//记录每个连通分量的大小int[] num;//记录连通分量的个数int count;public CompressionWeightedQuickUnion() {}public CompressionWeightedQuickUnion(int n) {id = new int[n];//连通分量个数初始化为ncount = n;//初始化每个整数的连通分量标识为自己for (int i = 0; i < n; i++) {id[i] = i;}//初始化每个连通分量的大小,初始值均为1num = new int[n];for (int i = 0; i < n; i++) {num[i] = 1;}}public int find(int p){//返回每个数的连通分量标识//一个连通分量中的所有数都指向共同的祖先,即根节点//根节点指向自身while (p != id[p]){//进行路径压缩id[p] = id[id[p]];p = id[p];}return p;}public void union(int p,int q){//将两个数连接起来,需要将两个连通分量所有的数标识统一if (find(p) == find(q)){return;}int pID = find(p);int qID = find(q);//每次将小树插入大树if (num[pID] < num[qID]){id[pID] = qID;num[qID] += num[pID];}else{id[qID] = pID;num[pID] += num[qID];}//每次连接都需要将连通分量数减一count--;}public boolean connected(int p , int q){//判断两个数是否连通return find(p) == find(q);}public int count(){return count;}}
