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");
}
}