union-find连通性算法
通俗讲就是寻找两点之间的连通,调用connected():如果某一对整数中两个触点已经连通,就继续处理下一对,如果不连通,则调用union()并打印这对触点。
模板
package union_find; import edu.princeton.cs.algs4.StdIn; import edu.princeton.cs.algs4.StdOut; public class UF { private int[] id;// 分量id(以触电为索引) private int count;// 分量数量 // 初始化分量id的数量 public UF(int N) {// 以整数标识初始化N个触点 count = N; id = new int[N]; for (int i = 0; i < N; i++) { id[i] = i; } } // 连通分量的数量 public int count() { return count; } // 如果p和q存在同一个分量中则返回true public boolean connected(int p, int q) { return find(p) == find(q); } // p(0~N-1)所在的分量的标识符 public int find(int p) { return 0; } // 在p和q之间添加一条连接 public void union(int p, int q) { } public static void main(String[] args) { // 解决由StdIn得到的动态连通性问题 int N = StdIn.readInt();// 读取触电的数量 UF uf = new UF(N);// 初始化N个分量 while (!StdIn.isEmpty()) { int p = StdIn.readInt(); int q = StdIn.readInt();// 读取整数对 if (uf.connected(p, q)) continue;// 如果已经连通则忽略 uf.union(p, q);// 归并分量 StdOut.println(p + " " + q);// 打印连接 } StdOut.println(uf.count() + "componets"); } }
实现
quick-find算法(无法处理大型问题)
实质就是同一个连接量中共用一个id,这个id一开始取触点本身(如果id不存在),再连接后变成后者的id
如
union(5,0)两个触点的id都为0
union(6,5)取后者的id,则6,5,0的id都是取0的id,即0
实现
package union_find; import edu.princeton.cs.algs4.StdIn; import edu.princeton.cs.algs4.StdOut; public class quick_find { private int[] id;// 分量id(以触电为索引) private int count;// 分量数量 // 初始化分量id的数量 public quick_find(int N) {// 以整数标识初始化N个触点 count = N; id = new int[N]; for (int i = 0; i < N; i++) { id[i] = i; } } // 连通分量的数量 public int count() { return count; } // 如果p和q存在同一个分量中则返回true public boolean connected(int p, int q) { return find(p) == find(q); } // p(0~N-1)所在的分量的标识符 public int find(int p) { return id[p]; } // 在p和q之间添加一条连接 public void union(int p, int q) { // 将p和q归并到相同的分量中 // 实质上就是篡改id int pID = find(p); int qID = find(q); // 如果p和q已经在相同的分量之中则不需要采取任何行动 if (pID == qID) { return; } // 将p的分量重命名为q的名称(篡改id) for (int i = 0; i < id.length; i++) {// 遍历id,id已经被赋值过0~9了 if (id[i] == pID) {//找到需要更改的id索引 id[i] = qID; // 关键是后者的id,统一为要后者的id } } count--;// 执行一次union要递减一次,不要放错了 } public static void main(String[] args) { // 解决由StdIn得到的动态连通性问题 int N = StdIn.readInt();// 读取触电的数量 quick_find uf = new quick_find(N);// 初始化N个分量 while (!StdIn.isEmpty()) { int p = StdIn.readInt(); int q = StdIn.readInt();// 读取整数对 if (uf.connected(p, q)) continue;// 如果已经连通则忽略 uf.union(p, q);// 归并分量 StdOut.println(p + " " + q);// 打印连接 } StdOut.println(uf.count() + "componets"); } }
quick-union(find的代价可能会太高了,树太高并不是一件好事情)
简而言之此算法就是构造树,每一对分量后者变为前者的根,如果后者的跟已存在,那前者就选择成为后者的根的子叶。
每个find都会去寻找根节点,结束的标志是唯一的——即i=id[i],每个索引指向了自己
3的根节点是9,4的根节点也是9,5的根节点是6,如果根节点一样,就代表可以连通。
寻找的过程为find(3),3的父节点是4,4的父节点是9,find(3)==id[id[id[3]]]=====直到i=id[i]停止
union(9,6),只需要9的根节点变为6即可,只需要改变一个id值不需要遍历就可以实现
实现代码
package union_find; import edu.princeton.cs.algs4.StdIn; import edu.princeton.cs.algs4.StdOut; public class quick_union { private int[] id;// 分量id(以触电为索引) private int count;// 分量数量 // 初始化分量id的数量 public quick_union(int N) {// 以整数标识初始化N个触点 count = N; id = new int[N]; for (int i = 0; i < N; i++) { id[i] = i; } } // 连通分量的数量 public int count() { return count; } // 如果p和q存在同一个分量中则返回true public boolean connected(int p, int q) { return find(p) == find(q); } // p(0~N-1)所在的分量的标识符 private int find(int i) { // 找出分量的名称 while (i != id[i]) {//寻找父节点直到根节点 i = id[i];//这个必然存在的触点(这个触点的索引指向自己) } return i; } // 在p和q之间添加一条连接 public void union(int p, int q) { // 将p和q的根节点统一 int pRoot = find(p); int qRoot = find(q); if (pRoot == qRoot) { return; } id[pRoot] = qRoot; count--; } public static void main(String[] args) { // 解决由StdIn得到的动态连通性问题 int N = StdIn.readInt();// 读取触电的数量 quick_union uf = new quick_union(N);// 初始化N个分量 while (!StdIn.isEmpty()) { int p = StdIn.readInt(); int q = StdIn.readInt();// 读取整数对 if (uf.connected(p, q)) continue;// 如果已经连通则忽略 uf.union(p, q);// 归并分量 StdOut.println(p + " " + q);// 打印连接 } StdOut.println(uf.count() + "componets"); } }
加权quick-union
此算法将不再只是寻找根节点然后连接,而是记录一颗树的大小(默认全部为1),当需要连接时,总是将较小的树连接到较大的树,避免树过高。
注意就是单个触点也被当成一颗树。
代码
package union_find; import edu.princeton.cs.algs4.StdIn; import edu.princeton.cs.algs4.StdOut; public class weightedQuickUnion { private int[] id;// 分量id(以触电为索引) private int count;// 分量数量 private int[] size;// 树的大小,默认每个索引的对应值都是1 // 初始化分量id的数量 public weightedQuickUnion(int N) {// 以整数标识初始化N个触点 count = N; id = new int[N]; for (int i = 0; i < N; i++) { id[i] = i; } size = new int[N]; for (int i = 0; i < N; i++) { size[i] = 1; } } // 连通分量的数量 public int count() { return count; } // 如果p和q存在同一个分量中则返回true public boolean connected(int p, int q) { return find(p) == find(q); } // p(0~N-1)所在的分量的标识符 public int find(int i) { // 跟随链接(索引)找到根节点 while (i != id[i]) { i = id[i]; } return i; } // 在p和q之间添加一条连接 public void union(int p, int q) { int i = find(p); int j = find(q); if (i == j) { return; } // 将小树的根节点连接到大树的根节点 if (size[i] < size[j]) {//比较树的大小 id[i] = j;//把id篡改为大树的根节点的值 size[j] += size[i];// size要变大,sz[j]=sz[i]+sz[j] } else { size[j] = i; size[i] += size[j]; } count--; } public static void main(String[] args) { // 解决由StdIn得到的动态连通性问题 int N = StdIn.readInt();// 读取触电的数量 weightedQuickUnion uf = new weightedQuickUnion(N);// 初始化N个分量 while (!StdIn.isEmpty()) { int p = StdIn.readInt(); int q = StdIn.readInt();// 读取整数对 if (uf.connected(p, q)) continue;// 如果已经连通则忽略 uf.union(p, q);// 归并分量 StdOut.println(p + " " + q);// 打印连接 } StdOut.println(uf.count() + "componets"); } }