【算法训练营】 - ⑩ 并查集与图

  • 并查集
    • 并查集特征
    • 并查集的优化
    • 图结构的表达
    • 图的面试题如何搞定?
    • 图的数据结构
      • 生成图
    • 图算法
      • 广度优先遍历
      • 深度优先遍历
      • 图的拓扑排序算法
      • 最小生成树K算法
      • 最小生成树p算法
      • Dijkstra算法

并查集

  • 有若干个样本a、b、c、d…类型假设是V。
  • 在并查集中一开始认为每个样本都在单独的集合里。
  • 用户可以在任何时候调用如下两个方法:
    1. boolean isSameSet(V x, V y) : 查询样本x和样本y是否属于一个集合。
    2. void union(V x, V y) : 把x和y各自所在集合的所有样本合并成一个。
  • isSameSet和union方法的代价越低越好。(时间复杂度O(1))。

并查集特征

1)每个节点都有一条往上指的指针。
2)节点a往上找到的头节点,叫做a所在集合的代表节点。
3)查询x和y是否属于同一个集合,就是看看找到的代表节点是不是一个。
4)把x和y各自所在集合的所有点合并成一个集合,只需要小集合的代表点挂在大集合的代表点的下方即可。

并查集的优化

1)节点往上找代表点的过程,把沿途的链变成扁平的。
2)小集合挂在大集合的下面。
3)如果方法调用很频繁,那么单次调用的代价为O(1),两个方法都如此。

/*** 并查集* 提供两个方法:* 1、查询两个元素是否在同一个集合中  isSameSet* 2、把两个元素所在的集合合并成一个  union** 假定用户一次直接给出所有集合*/
public class UnionSearch<V> {// 节点位置表Map<V,Node> nodes;// 查找当前节点对应的父节点表Map<Node,Node> parents;// 作为代表元素表 k代表元素 v 集合中有几个元素Map<Node,Integer> size;public UnionSearch(List<V> list){nodes = new HashMap<>();parents = new HashMap<>();size = new HashMap<>();for(V v : list){// 出事时每个元素单独作为一个结合Node node = new Node(v);nodes.put(v,node);parents.put(node,node);size.put(node,1);}}public  boolean isSameSet(V v1,V v2){if(!nodes.containsKey(v1)|| !nodes.containsKey(v2)){return false;}Node node1 = findFather(v1);Node node2 = findFather(v2);if(node1 == node2){return true;}return false;}public void union(V v1,V v2){if(!nodes.containsKey(v1)|| !nodes.containsKey(v2)){return;}Node node1 = findFather(v1);Node node2 = findFather(v2);// 两个元素不在一个集合中才合并if(node1 != node2){// 看谁的元素多Node big = size.get(node1) > size.get(node2) ? node1 : node2;Node small = size.get(node1) <= size.get(node2) ? node1 : node2;// 小的挂在大的上面parents.put(small,big);size.put(big,size.get(big) + size.get(small));size.remove(small);}}// 调用此方法是一定保证集合中有此元素private Node findFather(V node){// 做一个扁平化优化,把从当前节点网上找的节点都加入容器中Stack<Node> stack = new Stack<>();Node cur = nodes.get(node);while (parents.get(cur) != cur){// 路径上经过的节点加入容器stack.push(cur);// 只要此时节点不等于父节点,说明不是代表节点就一直往上找cur = parents.get(cur);}// 把所有经过节点直接和代表节点相连,下次查找是O(1)while (!stack.isEmpty()){parents.put(stack.pop(),cur);}//跳出来说明已经找到了return cur;}public static void main(String[] args) {List<String> list = new ArrayList();list.add("a");list.add("b");list.add("c");list.add("d");list.add("e");list.add("f");list.add("g");UnionSearch<String> unionSearch = new UnionSearch<>(list);unionSearch.union("a","b");unionSearch.union("c","b");System.out.println(unionSearch.isSameSet("a", "g"));}
}

  1. 由点的集合和边的集合构成。
  2. 虽然存在有向图和无向图的概念,但实际上都可以用有向图来表达。
  3. 边上可能带有权值。

图结构的表达

  1. 邻接表法
  2. 邻接矩阵法
  3. 除此之外还有其他众多的方式

图的面试题如何搞定?

图的算法都不算难,只不过coding的代价比较高

  1. 先用自己最熟练的方式,实现图结构的表达
  2. 在自己熟悉的结构上,实现所有常用的图算法作为模板
  3. 把面试题提供的图结构转化为自己熟悉的图结构,再调用模板或改写即可

图的数据结构

// 点
public class Node {// 结点值public int value;// 入度public int in;// 出度public int out;// 邻居List<Node> nexts;// 存一份边List<Edge> edges;public Node(int value){this.value = value;// 新结点入度为0this.in = 0;// 新结点出度为0this.out = 0;// 新结点邻居为空this.nexts = new ArrayList<>();this.edges = new ArrayList<>();}
}

// 边
public class Edge {// 权重public int weight;// 起点边public Node from;// 终点边public Node to;public Edge(Node from,Node to,int weight){this.from = from;this.to = to;this.weight = weight;}
}

// 图
public class Graph {// 点的集合HashMap<Integer,Node> nodes;// 边的集合HashSet<Edge> edges;public Graph(){this.nodes = new HashMap<>();this.edges = new HashSet<>();}
}

生成图

public class GraphGenerator {// matrix 所有的边// N*3 的矩阵// [weight, from节点上面的值,to节点上面的值]public static Graph graphGenerator(Integer[][] matrix){Graph graph = new Graph();for (int i = 0;i < matrix.length;i++){// 每一行代表一个点// 权重int weight = matrix[i][0];// 起始点int from = matrix[i][1];// 终点int to = matrix[i][2];// 如果没有起始点就新建if(graph.nodes.containsKey(from)){graph.nodes.put(from,new Node(from));}if(graph.nodes.containsKey(to)){graph.nodes.put(to,new Node(to));}// 取出点构建图Node fromNode = graph.nodes.get(from);Node toNode = graph.nodes.get(to);// 构建边Edge edge = new Edge(fromNode,toNode,weight);// fromNode 几点出度加一,邻居加一fromNode.out ++;fromNode.nexts.add(toNode);fromNode.edges.add(edge);// toNode入度加一toNode.in ++;// 图中边集加一graph.edges.add(edge);}return graph;}
}

图算法

广度优先遍历

  1. 利用队列实现
  2. 从源节点开始依次按照宽度进队列,然后弹出
  3. 每弹出一个点,把该节点所有没有进过队列的邻接点放入队列
  4. 直到队列变空

注意要点,不能重复遍历,遍历过的点应该排除。

package class16;import java.util.HashSet;
import java.util.LinkedList;
import java.util.Queue;public class Code01_BFS {// 从node出发,进行宽度优先遍历public static void bfs(Node start) {if (start == null) {return;}Queue<Node> queue = new LinkedList<>();HashSet<Node> set = new HashSet<>();queue.add(start);set.add(start);while (!queue.isEmpty()) {Node cur = queue.poll();System.out.println(cur.value);for (Node next : cur.nexts) {if (!set.contains(next)) {set.add(next);queue.add(next);}}}}}

深度优先遍历

  1. 利用栈实现
  2. 从源节点开始把节点按照深度放入栈,然后弹出
  3. 每弹出一个点,把该节点下一个没有进过栈的邻接点放入栈
  4. 直到栈变空
package class16;import java.util.HashSet;
import java.util.Stack;public class Code02_DFS {public static void dfs(Node node) {if (node == null) {return;}Stack<Node> stack = new Stack<>();HashSet<Node> set = new HashSet<>();stack.add(node);set.add(node);System.out.println(node.value);while (!stack.isEmpty()) {Node cur = stack.pop();for (Node next : cur.nexts) {if (!set.contains(next)) {stack.push(cur);stack.push(next);set.add(next);System.out.println(next.value);break;}}}}
}

图的拓扑排序算法

  1. 在图中找到所有入度为0的点输出
  2. 把所有入度为0的点在图中删掉,继续找入度为0的点输出,周而复始
  3. 图的所有点都被删除后,依次输出的顺序就是拓扑排序

要求:有向图且其中没有环
应用:事件安排、编译顺序

package class16;import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;public class Code03_TopologySort {// directed graph and no looppublic static List<Node> sortedTopology(Graph graph) {// key 某个节点   value 剩余的入度HashMap<Node, Integer> inMap = new HashMap<>();// 只有剩余入度为0的点,才进入这个队列Queue<Node> zeroInQueue = new LinkedList<>();for (Node node : graph.nodes.values()) {inMap.put(node, node.in);if (node.in == 0) {zeroInQueue.add(node);}}List<Node> result = new ArrayList<>();while (!zeroInQueue.isEmpty()) {Node cur = zeroInQueue.poll();result.add(cur);for (Node next : cur.nexts) {inMap.put(next, inMap.get(next) - 1);if (inMap.get(next) == 0) {zeroInQueue.add(next);}}}return result;}
}

最小生成树K算法

/*** 最小生成树算法之Kruskal* 1)总是从权值最小的边开始考虑,依次考察权值依次变大的边(优先级队列)* 2)当前的边要么进入最小生成树的集合,要么丢弃* 3)如果当前的边进入最小生成树的集合中不会形成环,就要当前边* 4)如果当前的边进入最小生成树的集合中会形成环,就不要当前边* 5)考察完所有边之后,最小生成树的集合也得到了*/
public class Kruskal {public static Set<Edge> kruskal(Graph graph){if(graph == null){return null;}Set<Edge> res = new HashSet<>();// 先把所有点都加入并查集List<Node> nodeList = (List<Node>)graph.nodes.values();UnionSearch<Node> unionSearch = new UnionSearch<>(nodeList);// 获取所有边PriorityQueue<Edge> queue = new PriorityQueue<>(new MyComparator());for(Edge item : graph.edges){queue.offer(item);}while (!queue.isEmpty()){Edge cur = queue.poll();Node from = cur.from;Node to = cur.to;if(!unionSearch.isSameSet(from,to)){res.add(cur);// 加入并查集unionSearch.union(from,to);}}return res;}private void makeUnion(List<Node> list,UnionSearch<Node> unionSearch){for(Node item : list){}}static class MyComparator implements Comparator<Edge>{@Overridepublic int compare(Edge o1, Edge o2) {return o1.weight - o2.weight;}}static class UnionSearch<V> {// 节点位置表Map<V,Node2> nodes;// 查找当前节点对应的父节点表Map<Node2,Node2> parents;// 作为代表元素表 k代表元素 v 集合中有几个元素Map<Node2,Integer> size;public UnionSearch(List<V> list){nodes = new HashMap<>();parents = new HashMap<>();size = new HashMap<>();for(V v : list){// 出事时每个元素单独作为一个结合Node2 node = new Node2(v);nodes.put(v,node);parents.put(node,node);size.put(node,1);}}public  boolean isSameSet(V v1,V v2){if(!nodes.containsKey(v1)|| !nodes.containsKey(v2)){return false;}Node2 node1 = findFather(v1);Node2 node2 = findFather(v2);if(node1 == node2){return true;}return false;}public void union(V v1,V v2){if(!nodes.containsKey(v1)|| !nodes.containsKey(v2)){return;}Node2 node1 = findFather(v1);Node2 node2 = findFather(v2);// 两个元素不在一个集合中才合并if(node1 != node2){// 看谁的元素多Node2 big = size.get(node1) > size.get(node2) ? node1 : node2;Node2 small = size.get(node1) <= size.get(node2) ? node1 : node2;// 小的挂在大的上面parents.put(small,big);size.put(big,size.get(big) + size.get(small));size.remove(small);}}// 调用此方法是一定保证集合中有此元素private Node2 findFather(V node){// 做一个扁平化优化,把从当前节点网上找的节点都加入容器中Stack<Node2> stack = new Stack<>();Node2 cur = nodes.get(node);while (parents.get(cur) != cur){// 路径上经过的节点加入容器stack.push(cur);// 只要此时节点不等于父节点,说明不是代表节点就一直往上找cur = parents.get(cur);}// 把所有经过节点直接和代表节点相连,下次查找是O(1)while (!stack.isEmpty()){parents.put(stack.pop(),cur);}//跳出来说明已经找到了return cur;}}
}

最小生成树p算法

/*** 最小生成树,p算法* 1)可以从任意节点出发来寻找最小生成树* 2)某个点加入到被选取的点中后,解锁这个点出发的所有新的边* 3)在所有解锁的边中选最小的边,然后看看这个边会不会形成环* 4)如果会,不要当前边,继续考察剩下解锁的边中最小的边,重复3)* 5)如果不会,要当前边,将该边的指向点加入到被选取的点中,重复2)* 6)当所有点都被选取,最小生成树就得到了*/
public class Prim {public static Set<Edge> prim(Graph graph){if(graph == null){return null;}// 生成优先级队列PriorityQueue<Edge> queue = new PriorityQueue<>(new MyComparator());Set<Edge> edgeSet = new HashSet<>();Set<Node> nodeSet = new HashSet<>();Set<Edge> res = new HashSet<>();List<Node> nodeList = (List<Node>)graph.nodes.values();for(Node node : nodeList){ // 防止森林,如果明确没有森林可不要此循环。if(!nodeSet.contains(node)){// 解锁点的所有边nodeSet.add(node);for(Edge temp : node.edges){if(!edgeSet.contains(temp)){queue.offer(temp);edgeSet.add(temp);}}while (!queue.isEmpty()){// 权重最小的边Edge cur = queue.poll();// 此时from节点已经在nodeSet结合中解锁出来了,只需判断to节点是否解锁了if(!nodeSet.contains(cur.to)){// 要这条边res.add(cur);// 加入点的集合nodeSet.add(cur.to);// 解锁cur.to的所有边for (Edge e : cur.to.edges){if(!edgeSet.contains(e)){queue.offer(e);edgeSet.add(e);}}}}}}return res;}static class MyComparator implements Comparator<Edge>{@Overridepublic int compare(Edge o1, Edge o2) {return o1.weight - o2.weight;}}
}

Dijkstra算法

  1. Dijkstra算法必须指定一个源点
  2. 生成一个源点到各个点的最小距离表,一开始只有一条记录,即原点到自己的最小距离为0,源点到其他所有点的最小距离都为正无穷大
  3. 从距离表中拿出没拿过记录里的最小记录,通过这个点发出的边,更新源点到各个点的最小距离表,不断重复这一步
  4. 源点到所有的点记录如果都被拿过一遍,过程停止,最小距离表得到了
  • 优化点:在distanceMap中查找起点到其他点的最短距离是用遍历的方式时间复杂度是O(N),如果用小根堆,可以让时间复杂度降到O(logn),但是因为会更新加入到小根堆中的距离数据,一般的小根堆不能自动调整,所以要自己手动改写小根堆。
/*** 1)Dijkstra算法必须指定一个源点* 2)生成一个源点到各个点的最小距离表,一开始只有一条记录,即原点到自己的最小距离为0,源点到其他所有点的最小距离都为正无穷大* 3)从距离表中拿出没拿过记录里的最小记录,通过这个点发出的边,更新源点到各个点的最小距离表,不断重复这一步* 4)源点到所有的点记录如果都被拿过一遍,过程停止,最小距离表得到了*/
public class Dijkstra {public static Map<Node2,Integer> dijkstra1(Node2 node){if(node == null){return null;}// 距离表,一开始只有出发点到出发点的距离为0Map<Node2,Integer> distanceMap = new HashMap<>();distanceMap.put(node,0);// 去重黑名单,每次选举最小记录时排除此表中的数据Set<Node2> noSelect = new HashSet<>();// 获取最小记录点(出发点到某一点的最短距离)Node2 minDistance = getMinAndNoSelect(distanceMap,noSelect);while (minDistance != null){ // 把表中数据都遍历完for (Edge temp : minDistance.edges){// 这个点往能出去的所有边能不能更新表中的距离Node2 to = temp.to;if(!distanceMap.containsKey(to)){// 如果表中不包含这个点,说明还没找到过重from点到这个距离,直接加入表中distanceMap.put(to,distanceMap.get(minDistance) + temp.weight);}else{distanceMap.put(to,Math.min(distanceMap.get(to),distanceMap.get(minDistance) + temp.weight));}}minDistance = getMinAndNoSelect(distanceMap,noSelect);}return distanceMap;}private static Node2 getMinAndNoSelect(Map<Node2,Integer> distanceMap,Set<Node2> noSelcet){// 由于Dijkstra算法中边都是非负的用0就可以了int min = 0;Node2 res = null;// 遍历距离表for (Map.Entry<Node2, Integer> item : distanceMap.entrySet()){Node2 key = item.getKey();if(noSelcet.contains(key)){continue;}// 比较大小if(item.getValue() < min){min = item.getValue();res = item.getKey();}}noSelcet.add(res);return res;}}

【算法训练营】 - ⑩ 并查集与图相关推荐

  1. Kruskal算法与并查集

    Kruskal算法与并查集 一.Kruskal算法 1. 概念 Kruskal算法就是按照图中各个边上的权值大小进行递增排序,以此来构造最小生成树. 2.重点解析 在由Kruskal实现最小生成树的过 ...

  2. 简单易懂的并查集算法以及并查集实战演练

    文章目录 前言 一.引例 二.结合引例写出并查集 1. 并查集维护一个数组 2. 并查集的 并 操作 3. 并查集的 查 操作 4. 基本并查集模板代码实现--第一版(有错误后面分析) 4.1 Jav ...

  3. 算法总结 — 并查集

    参考:算法学习笔记(1) : 并查集 - 知乎 并查集 (disjoint set union) 是 最优美的数据结构之一 合并 (merge): 把两个集合合并 查找 (find): 查找一个元素的 ...

  4. 高阶数据结构(1):并查集 与 图

    "Head in the clouds" 一.并查集 (1)认识并查集? 在一些问题中需要将n个不同的元素划分成 一些不想交的集合. 开始时,每个元素自成一个单元素集合,然后按一定 ...

  5. Java实现_算法_并查集

    并查集 作用:用来查找某个图中是否含有闭环. 比如图一: 上图中就是没有闭环的一个图,而下图(图二)就是一个有闭环的图 思路1-数组寻根法: 顾名思义,数组寻根法(自己称呼的)就是寻找每个节点的根节点 ...

  6. 【算法】并查集的运用

    并查集的概念 朋友圈 团伙问题 连通图 总结 并查集的概念 并查集顾名思义就是合并和查找,问题在于合并什么,查找什么.这里有一种朴素的思想来解释这两个问题.就是把这个想成一棵树.合并什么?就是把不在这 ...

  7. 算法:并查集(四种方式)

    简单并查集 public class UnionFind {private int[] id;private int count;public UnionFind(int N) {count = N; ...

  8. java并查集判断是否是连通图_并查集-判断图的连通

    首先在地图上给你若干个城镇,这些城镇都可以看作点,然后告诉你哪些对城镇之间是有道路直接相连的.最后要解决的是整幅图的连通性问题.比如随意给你两个点,让你判断它们是否连通,或者问你整幅图一共有几个连通分 ...

  9. Kruskal算法和并查集

    Kruskal算法 步骤: 第一步:给所有边按照从小到大顺序排列(直接使用库函数qsort / sort). 第二步:从小到大依次考察每条边(u,v),在执行第二步时会出现以下两种情况: 情况1:u和 ...

最新文章

  1. 2021年大数据Hadoop(二十九):​​​​​​​关于YARN常用参数设置
  2. html中事件调用JavaScript函数时有return与没有return的区别
  3. Python之父Guido推荐的命名规范
  4. scipy笔记:scipy.sparse
  5. 学霸大佬整理,超全 Python 学习路线图(附工具+视频+书籍+面试)
  6. SAP Spartacus里的不同种类的CMS Component type
  7. nssl1142,jzoj3487-剑与魔法【堆,贪心】
  8. 高级SmartGWT教程,第1部分
  9. 控制台资费管理主菜单java_java毕业设计_springboot框架的高速公路收费管理系统...
  10. JAVAWEB入门之Requset原理
  11. Redis面试常问4-- 如何实现异步队列 Blpop key timeout
  12. java 虚拟机类型的卸载_《深入理解Java虚拟机》:类加载和初始化(二)
  13. Unity搭建简单的图片服务器
  14. 20210530:力扣第53场双周赛题解
  15. 【Java编程】建立一个简单的JDBC连接-Drivers, Connection, Statement and PreparedStatement
  16. L298N电机驱动电路
  17. MySQL 计算年龄
  18. 手机如何注册163邮箱?注册邮箱的方法步骤
  19. CDR实例教程-高考789,敢拼就能赢!
  20. 街头篮球手游服务器维护,街头篮球手游2017.6.22维护更新公告 宝箱位置调整更新一览...

热门文章

  1. java字符乱码问题_怎么解决java中的字符乱码问题
  2. 子网怎么算?IP地址(A,B,C,D,E类地址),子网,子网掩码,容纳主机20台,网络号,主机号
  3. 【查看服务器磁盘空间请用情况】
  4. 在线客服系统源码 自适应手机移动端 支持多商家 带搭建教程
  5. redhat linux 9.0 拷贝u盘的文件,Linux redhat 9.0 中挂载U盘的方法!
  6. 红米3s进不了recovery_红米3s卡刷教程_红米3s用recovery刷第三方系统包
  7. 陈奕迅《歌神HQCD》[WAV分轨]
  8. win10总强制更新?教你永久关闭
  9. react项目中,使用.jsx和.js文件书写react代码时,这两者有什么差异性?
  10. dillo支持html5吗,流动聚焦及射流不稳定性