【Algorithms公开课学习笔记1】 Union-Find 合并查找

Union-Find 合并查找

动态连接性

判断连接性的关键

  • 等价关系模型:如果有(a,b),(b,c),那么也会有(a,c)。其中()表示有连接。

  • 连通分量:最大的可连通对象集合,有两个特点:1)连通分量内部任意两个对象都是相连通的;2)连通分量内部的对象不与外部对象相连通。

利用连通分量,可以方便地实现并查集的两个操作:查询请求合并命令

  • 查找:检查两个对象是否在相同的连通分量中

  • 合并:将两个对象的分享替换成其并集

快速查找

快速查找是基于贪心策略的一种算法,贪心策略是指在问题求解的时候,只找出当前最优解。

基于此,设计一种数据结构来存储实验对象。

  • 长度为N的整型数组

  • 如果p和q有相同的id,则表示他们有连接

因此,查找和合并操作就变为:

  • 查找:检查p和q是否具有相同的id,若相同则代表连通。如图id[1]=id[2],表示1和2相连通

  • 合并:在合并p和q对象时,将值为所有等于id[p]的值重新赋值为id[q]。如图合并0和1,需要将id[0]=id[5]=id[6]=0的值全部赋值为id[1]=1的值

具体算法

public class QuickFind {

	private int[] id;

	/**
	 * 构造函数,初始化数据结构的属性
	 * 
	 * @param N
	 *            数组额长度
	 */
	public QuickFind(int N) {
		// 初始化数据结构的值
		id = new int[N];
		for (int i = 0; i < N; i++) {
			id[i] = i;
		}
	}

	/**
	 * 查找操作,判断连通性
	 * 
	 * @param p
	 * @param q
	 * @return
	 */
	public boolean connected(int p, int q) {
		return id[p] == id[q];
	}

	/**
	 * 合并操作
	 * 
	 * @param p
	 * @param q
	 */
	public void union(int p, int q) {
		int pid = id[p];
		int qid = id[q];

		for (int i = 0; i < id.length; i++) {
			if (pid == id[i]) {
				id[i] = qid;
			}
		}
	}

	/* setter和getter */
	public int[] getId() {
		return id;
	}

	public void setId(int[] id) {
		this.id = id;
	}

}

时间复杂度分析

算法初始化合并(包含查找)查找
快速查找NN1

快速合并

快速合并是基于懒策略的一种算法,懒策略是指在问题求解时尽量避免计算,直到不得不进行计算。

基于此,设计另外一种该数据结构来存储实验对象。

  • 长度为N的整型数组

  • id[i]是i的父亲,从而构造成树的结构

  • 如果i=id[i],则表示id[i]是树根

因此,合并和查找操作就变为:

  • 查找:检查p和q是否具有相同的根,如有则代表连通。

  • 合并:在合并p和q对象时,将p的根的id(父亲)设成q的根。

具体算法

public class QuickUnion {

	public class QU {

		private int[] id;

		public QU(int N) {
			// 初始化属性值
			id = new int[N];
			for (int i = 0; i < N; i++) {
				id[i] = i;
			}
		}

		/**
		 * 查找指定值的根
		 * 
		 * @param i
		 *            待查找根的值
		 * @return
		 */
		public int root(int i) {
			// 当id[i]=i时就是根
			while (id[i] != i)
				i = id[i];
			return i;
		}

		/**
		 * 查找连通性
		 * 
		 * @param p
		 * @param q
		 * @return
		 */
		public boolean connected(int p, int q) {
			return root(p) == root(q);
		}

		/**
		 * 合并操作,将p的根的id(父亲)设成q的根
		 * 
		 * @param p
		 * @param q
		 */
		public void union(int p, int q) {
			int proot = root(p);
			int qroot = root(q);

			id[proot] = qroot;
		}

		/** getter和setter */
		public int[] getId() {
			return id;
		}

		public void setId(int[] id) {
			this.id = id;
		}
	}

时间复杂度分析

  • 查找:取决于对象p和q的深度(而树的深度有可能为N,线性级别)

  • 合并:常数级别,包含查找时就是查找的时间复杂度

算法初始化合并(包含查找)查找
快速查找NN1
快速合并NNN

带权的快速合并

上面所描述的快速合并中,存在两个缺点是:一是查找时间消耗过大,二是树的深度容易过大

针对上述缺点,引入带权的快速合并算法。该算法的特点:

  • 改进快速合并,避免生成过高的树

  • 追踪每个树的大小(对象的个数)

  • 在合并的时候,通过“将小树连接到大树的根”来达到平衡树高的效果

数据结构与“快速合并”相同,合并和查找操作为:

  • 查找:检查p和q是否具有相同的根,如有则代表连通(与快速合并相同)。

  • 合并:在合并p和q对象时,将小树的根的id设为大树的根(小树接入大树)。故需要额外维护数据size array来保存树的大小。

代码:修改union部分

/**
		 * 合并操作,将小树的根的id(父亲)设成大树的根
		 * 
		 * @param p
		 * @param q
		 */
		public void union(int p, int q) {
			int proot = root(p);
			int qroot = root(q);
			if(size[p]>size[q]){
				id[qroot] = proot;
				size[proot]+=size[qroot];
			}else{
				id[proot] = qroot;
				size[qroot]+=size[proot];
			}
		}

时间复杂度分析

  • 查找:取决于对象p和q的深度(而树的深度最多为lgN)

  • 合并:常数级别,包含查找时就是查找的时间复杂度

算法初始化合并(包含查找)查找
快速查找NN1
快速合并NNN
带权快速合并NlgNlgN

带压缩路径的快速合并

在做合并和查询找之前,将树的路径进行压缩,从而保持树的扁平化。

代码实现

	public int root(int i) {
			// 当id[i]=i时就是根
			while (id[i] != i){
				//将i的根指向其爷爷辈
				id[i]=id[id[i]];
				i = id[i];
			}
			return i;
		}

时间对复杂度比

对于在N个对象中有M个并查集的情况

算法时间
快速查找MN
快速合并MN
带权快速合并N+MlogN
带压缩路径快速合并N+MlogN

第1周作业的答案

Percolation

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值