由最小生成树(MST)到并查集(UF)

本文目录 [-点此收起]

背景

最小生成树(Minimum Spanning Tree)的算法中,克鲁斯卡尔算法(Kruskal‘s algorithm)是一种常用算法.

克鲁斯卡尔算法中的一个关键问题是如何判断图中的两个点是否形成环(cycle),那么一种高效的判断方式就是使用并查集技术(Union-Find).

代码

/**
 * Created by 浩然 on 4/19/15.
 * 快速并查集
 * 参考:普林斯顿大学 algorithms 4th edition
 *
 * 考虑一个问题:
 * 一个城市需要将所有的路连起来以保证城市的畅通
 * 在开始这个工程前,已经有一些路是连通的了,那么还需要在哪里修多少条路才能用最小的代价完成?
 * 由于解决这个问题,只需要知道有多少个连通组件(已经连通的路的集合),
 * 所以我们不关心每个连通组件内部的细节(某组件内部路线的布局)。
 * 所以我们用并查集(Union-Find)的技术来做.(如果关心细节,比如要给出连通组件的路径,就用DFS技术)
 *
 * 并查集就像它的名字所述,提供两个操作:
 * 1:将给定的点所在的连通组件找出来;
 * 2:将两个点连通
 *
 */
public class FastUnionFind {
	/**
	 * 父节点集
	 */
	private int parent[];
	/**
	 * 每个连通组件的大小
	 */
	private int treeSize[];
	/**
	 * 连通组件的个数
	 */
	private int componentsCount;

	public FastUnionFind(int n) {
		parent = new int[n];
		treeSize = new int[n];
		componentsCount = n;
		for(int i = 0; i < n; i ++) {
			parent[i] = i;
			treeSize[i] = 1;
		}
	}

	/**
	 * 连通组件的个数
	 * @return
	 */
	public int componentsCount() {
		return this.componentsCount;
	}

	/**
	 * 验证给定编号的界
	 * @param which
	 */
	private void validate(int which) {
		int n = this.parent.length;
		if (which < 0 || which >= n) {
			throw new IndexOutOfBoundsException("index which 越界!");
		}
	}

	/**
	 * 找到which属于哪个连通组件
	 * 意即找出它所在连通组件的代表
	 *
	 * @param which 索引或编号
	 * @return which所在的连通组件的代表
	 */
	public int find(int which) {
		validate(which);
		int root = which;
		//不断向根回溯
		while (root != parent[root]) {
			root = parent[root];
		}

		//全路径压缩-效率的关键
		//因为我们只关心给定的索引所在组件的根是谁
		//所以一个自然的想法就是让所有组件只需要一步就可以找到根
		//换句话说,就是让树的高度尽可能的为2
		//那么这里既然来到了处理find(which)的上下文,就对它所在的组件进行路径压缩
		while (which != root) {
			int newParent = parent[which];
			parent[which] = root;
			which = newParent;
		}
		return root;
	}

	/**
	 * 求a、b间是否连通
	 * @param a 其中一点
	 * @param b 另一个点
	 * @return 如果连通返回真,否则返回假
	 */
	public boolean isConnected(int a,int b) {
		//并查的重要意义之一就是测试两点之间是否具有连通性
		//自然的,就转化成为它们是否属于同一个连通组件
		//这里利用了连通的传递性 x->y,y->z,则x->z
		return find(a) == find(b);
	}

	/**
	 * 将a和b所在的连通组件合并
	 *
	 * 意即将a和b连通
	 *
	 * @param a 其中一点
	 * @param b 另一个点
	 */
	public void union(int a,int b) {
		validate(a);
		validate(b);
		int rootA = find(a);
		int rootB = find(b);
		//这里利用平衡的思想
		//将更小的树挂到更大的树上
		if (treeSize[rootA] < treeSize[rootB]) {
			parent[rootA] = rootB;
			treeSize[rootB] += treeSize[rootA];
		} else {
			parent[rootB] = rootA;
			treeSize[rootA] += treeSize[rootB];
		}
		//既然合并,连通组件就少了一个
		this.componentsCount--;
	}
}





posted @   foreach_break  阅读(707)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
点击右上角即可分享
微信分享提示
快速评论