[算法]union-find连通性算法/quick-find/quick-union/加权quick-union

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

	}

}








猜你喜欢

转载自blog.csdn.net/qq_38277033/article/details/79290980