算法基础10-并查集、图相关算法介绍

本文介绍了并查集和图相关算法,包括并查集的基本结构和操作,如isSameSet和union方法,以及优化策略。同时讲解了图的概念、表示方法,如邻接表和邻接矩阵,并详细阐述了图的遍历、拓扑排序、最小生成树算法(Kruskal和Prim)以及最短路径算法(Dijkstra和floyd)。
摘要由CSDN通过智能技术生成

1 并查集、图相关算法

1.1 并查集

1.1.1 并查集基本结构和操作

1、有若干个样本a、b、c、d…类型假设是V

2、在并查集中一开始认为每个样本都在单独的集合里

3、用户可以在任何时候调用如下两个方法:

boolean isSameSet(V x, V y):查询样本x和样本y是否属于一个集合

void union(V x, V y):把x和y各自所在集合的所有样本合并成一个集合

4、isSameSet和union方法的代价越低越好,最好O(1)

思路:isSameSet方法,我们设计为每个元素有一个指向自己的指针,成为代表点。判断两个元素是否在一个集合中,分别调用这两个元素的向上指针,两个元素最上方的指针如果内存地址相同,那么两个元素在一个集合中,反之不在

思路:union方法,例如将a所在的集合和e所在的集合合并成一个大的集合union(a,e)。a的代表点指针是a,e的代表点指针是e,我们拿较小的集合挂在大的集合下面,比如e小,那么e放在a的下面。链接的方式为小集合e头结点本来指向自己的代表节点,现在要指向a节点

并查集的优化点主要有两个,一个是合并的时候小的集合挂在大的集合下面,第二个优化是找某节点最上方的代表节点,把沿途节点全部拍平,下次再找该沿途节点,都变为O(1)。两种优化的目的都是为了更少的遍历节点。

由于我们加入了优化,如果N个节点,我们调用findFather越频繁,我们的时间复杂度越低,因为第一次调用我们加入了优化。如果findFather调用接近N次或者远远超过N次,我们并查集的时间复杂度就是O(1)。该复杂度只需要记住结论,证明无须掌握。该证明从1964年一直研究到1989年,整整25年才得出证明!算法导论23章,英文版接近50页的证明。

package class10;

import java.util.HashMap;
import java.util.List;
import java.util.Stack;

public class Code01_UnionFind {

        // 并查集结构中的节点类型
	public static class Node<V> {
		V value;

		public Node(V v) {
			value = v;
		}
	}

	public static class UnionSet<V> {
	        // 记录样本到样本代表点的关系
		public HashMap<V, Node<V>> nodes;
		// 记录某节点到父亲节点的关系。
		// 比如b指向a,c指向a,d指向a,a指向自身
		// map中保存的a->a b->a c->a d->a
		public HashMap<Node<V>, Node<V>> parents;
		// 只有当前点,他是代表点,会在sizeMap中记录该代表点的连通个数
		public HashMap<Node<V>, Integer> sizeMap;

                // 初始化构造一批样本
		public UnionSet(List<V> values) {
		        // 每个样本的V指向自身的代表节点
		        // 每个样本当前都是独立的,parent是自身
		        // 每个样本都是代表节点放入sizeMap
			for (V cur : values) {
				Node<V> node = new Node<>(cur);
				nodes.put(cur, node);
				parents.put(node, node);
				sizeMap.put(node, 1);
			}
		}

		// 从点cur开始,一直往上找,找到不能再往上的代表点,返回
		// 通过把路径上所有节点指向最上方的代表节点,目的是把findFather优化成O(1)的
		public Node<V> findFather(Node<V> cur) {
		        // 在找father的过程中,沿途所有节点加入当前容器,便于后面扁平化处理
			Stack<Node<V>> path = new Stack<>();
			// 当前节点的父亲不是指向自己,进行循环
			while (cur != parents.get(cur)) {
				path.push(cur);
				cur = parents.get(cur);
			}
			// 循环结束,cur是最上的代表节点
			// 把沿途所有节点拍平,都指向当前最上方的代表节点
			while (!path.isEmpty()) {
				parents.put(path.pop(), cur);
			}
			return cur;
		}

                // isSameSet方法
		public boolean isSameSet(V a, V b) {
		        // 先检查a和b有没有登记
			if (!nodes.containsKey(a) || !nodes.containsKey(b)) {
				return false;
			}
			// 比较a的最上的代表点和b最上的代表点
			return findFather(nodes.get(a)) == findFather(nodes.get(b));
		}

                // union方法
		public void union(V a, V b) {
		        // 先检查a和b有没有都登记过
			if (!nodes.containsKey(a) || !nodes.containsKey(b)) {
				return;
			}
			
			// 找到a的最上面的代表点
			Node<V> aHead = findFather(nodes.get(a));
			// 找到b的最上面的代表点
			Node<V> bHead = findFather(nodes.get(b));
			
			// 只有两个最上代表点内存地址不相同,需要union
			if (aHead != bHead) {
			
			        // 由于aHead和bHead都是代表点,那么在sizeMap里可以拿到大小
				int aSetSize = sizeMap.get(aHead);
				int bSetSize = sizeMap.get(bHead);
				
				// 哪个小,哪个挂在下面
				Node<V> big = aSetSize >= bSetSize ? aHead : bHead;
				Node<V> small = big == aHead ? bHead : aHead;
				// 把小集合直接挂到大集合的最上面的代表节点下面
				parents.put(small, big);
				// 大集合的代表节点的size要吸收掉小集合的size
				sizeMap.put(big, aSetSize + bSetSize);
				// 把小的记录删除
				sizeMap.remove(small);
			}
		}
	}

}

并查集用来处理连通性的问题特别方便

1.1.2 例题

学生实例有三个属性,身份证信息,B站ID,Github的Id。我们认为,任何两个学生实例,只要身份证一样,或者B站ID一样,或者Github的Id一样,我们都算一个人。给定一打拼学生实例,输出有实质有几个人?

思路:把实例的三个属性建立三张映射表,每个实例去对比,某个实例属性在表中能查的到,需要联通该实例到之前保存该实例属性的头结点下

package class10;

import java.util.HashMap;
import java.util.List;
import java.util.Stack;

public class Code07_MergeUsers {

	public static class Node<V> {
		V value;

		public Node(V v) {
			value = v;
		}
	}

	public static class UnionSet<V> {
		public HashMap<V, Node<V>> nodes;
		public HashMap<Node<V>, Node<V>> parents;
		public HashMap<Node<V>, Integer> sizeMap;

		public UnionSet(List<V> values) {
			for (V cur : values) {
				Node<V> node = new Node<>(cur);
				nodes.put(cur, node);
				parents.put(node, node);
				sizeMap.put(node, 1);
			}
		}

		// 从点cur开始,一直往上找,找到不能再往上的代表点,返回
		public Node<V> findFather(Node<V> cur) {
			Stack<Node<V>> path = new Stack<>();
			while (cur != parents.get(cur)) {
				path.push(cur);
				cur = parents.get(cur);
			}
			// cur头节点
			while (!path.isEmpty()) {
				parents.put(path.pop(), cur);
			}
			return cur;
		}

		public boolean isSameSet(V a, V b) {
			if (!nodes.containsKey(a) || !nodes.containsKey(b)) {
				return false;
			}
			return findFather(nodes.get(a)) == findFather(nodes.get(b));
		}

		public void union(V a, V b) {
			if (!nodes.containsKey(a) || !nodes.containsKey(b)) {
				return;
			}
			Node<V> aHead = findFather(nodes.get(a));
			Node<V> bHead = findFather(nodes.get(b));
			if (aHead != bHead) {
				int aSetSize = sizeMap.get(aHead);
				int bSetSize = sizeMap.get(bHead);
				Node<V> big = aSetSize >= bSetSize ? aHead : bHead;
				Node<V> small = big == aHead ? bHead : aHead;
				parents.put(small, big);
				sizeMap.put(big, aSetSize + bSetSize);
				sizeMap.remove(small);
			}
		}
		
		
		public int getSetNum() {
            //sizeMap 里面放着当前还有的集合以及对应的集合长度
			return sizeMap.size();
		}
		
	}

	public stat
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值