目录
图结构
存储图的结构有很多,不同的结构写出来的算法模型也不同。所以最好有自己熟悉的图结构,对题目所给的边、点信息通过写一个接口转换为自己熟悉的图结构,就可以用自己熟悉的图结构解题。
package com.wtp.图.图结构; import java.util.HashMap; import java.util.HashSet; public class Graph { public HashMap<Integer,Node> nodes;//点集 public HashSet<Edge> edges;//边集 public Graph() { nodes = new HashMap<>(); edges = new HashSet<>(); } }
package com.wtp.图.图结构; public class Edge { public int weight;//边权重 public Node from;//从哪个点出发 public Node to;//到哪个点 public Edge(int weight,Node from,Node to) { this.weight = weight; this.from = from; this.to = to; } }
package com.wtp.图.图结构; import java.util.ArrayList; public class Node { public int value;//点值 public int in;//入度 public int out;//出度 public ArrayList<Node> nexts;//由该点延伸出的点 public ArrayList<Edge> edges;//由该点延伸出的边 public Node(int value) { this.value = value; in = 0; out = 0; nexts = new ArrayList<>(); edges = new ArrayList<>(); } }
广度优先搜索
准备一个队列,和一个容器记录所有点是否出现过。根据队列先进先出的特点,弹出一个点看这个点的下属点,遇到点如果没有加入过队列就加入。
package com.wtp.图; import java.util.HashSet; import java.util.LinkedList; import java.util.Queue; import com.wtp.图.图结构.Node; public class 广度优先搜索 { public static void bfs(Node node) { if(node == null) { return; } Queue<Node> queue = new LinkedList<>(); HashSet<Node> set = new HashSet<>();//记录点是否出现过 set.add(node); queue.add(node); while(!queue.isEmpty()) { Node cur = queue.remove(); System.out.println(node.value); for(Node next : cur.nexts) { if(!set.contains(next)) { set.add(next); queue.add(next); } } } } }
深度优先搜索
深度优先:set记录node是否出现过,手动压栈,弹出一个node就遍历与该node相连的node,若发现一个没遍历过的就把node和没遍历过的压栈每次只找一个。
package com.wtp.图; import java.util.HashSet; import java.util.Stack; import com.wtp.图.图结构.Node; public class 深度优先搜索 { public static void dfs(Node node) { if(node == null) { return; } Stack<Node> stack = new Stack<>(); HashSet<Node> set = new HashSet<>(); stack.push(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)) { //记录 set.add(next); System.out.println(next.value); //当前节点还没看完 压回去 stack.push(cur); //下一次弹出的就是此次新发现的节点 stack.push(next); break; } } } } }
拓扑排序
拓扑排序:把所有点加入到map中记录节点和剩余入度,队列中存储入度为0的点,移除队列中入度为0的点,消除指向其他节点的影响,若出现入度为0的点继续入队列 队列可以保证发现入度为0的点的顺序,记顺序用队列
package com.wtp.图; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; import com.wtp.图.图结构.Graph; import com.wtp.图.图结构.Node; public class 拓扑排序 { public static List<Node> process(Graph graph){ //入度为0的点才进队列 Queue<Node> zeroQueue = new LinkedList<>(); //记录每个节点的入度 Map<Node,Integer> inMap = new HashMap<>(); List<Node> res = new ArrayList<>(); for(Node node : graph.nodes.values()) { if(node.in == 0) { zeroQueue.add(node); } inMap.put(node, node.in); } while(!zeroQueue.isEmpty()) { Node cur = zeroQueue.remove(); res.add(cur); for(Node next : cur.nexts) { inMap.put(next, inMap.get(next)-1); if(inMap.get(next) == 0) { zeroQueue.add(next); } } } return res; } }
最小生成树
k算法
排序边,从小的边开始选,选的所有边不能连成环
kruskal:
选边,一直选小的边但保证不连成回路。自定义一个结构map,初始每个点对应只含有自己的集合,再选边时看此边的from和to是否是同一个集合,如果是同一个集合说明这条边将构成回路。选不构成回路的边,再将该边的两个集合合并成一个集合。选边时通过所有节点获取到这个结构,将所有边加入到优先级队列。不断弹出判断最后返回一个边集。选边就可能出现一堆边的图与另一堆边构成的图合并,所以需要集合的合并。
package com.wtp.图.最小生成树; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.PriorityQueue; import java.util.Set; import com.wtp.图.图结构.Edge; import com.wtp.图.图结构.Graph; import com.wtp.图.图结构.Node; public class k算法 { public static class mySet{//用于判断加入某个点后是否形成环 public HashMap<Node,List<Node>> map; public mySet(Collection<Node> collection) { map = new HashMap<>(); //初始化 初始每个点都各自为一个集合 for(Node node : collection) { List<Node> list = new ArrayList<>(); list.add(node); map.put(node, list); } } //判断两个节点是否在同一个集合中 public boolean isSameSet(Node from,Node to) {//时间复杂度O(1) return map.get(from) == map.get(to); } //将两个点所在的集合合并 public void union(Node from,Node to) {//集合的合并用的是遍历的方法时间复杂度O(N)可用并查集结构优化 List<Node> fromNodes = map.get(from); List<Node> toNodes = map.get(to); for(Node toNode : toNodes) { //将to集合中的节点加入到from集合中 fromNodes.add(toNode); //将to集合中的节点指向from集合 map.put(toNode, fromNodes); } } } //选边 加上边后不形成环就选 public static Set<Edge> kruskal(Graph graph){ mySet unionFind = new mySet(graph.nodes.values()); PriorityQueue<Edge> priorityQueue = new PriorityQueue<>((o1,o2) -> o1.weight - o2.weight); for(Edge edge : graph.edges) { priorityQueue.add(edge); } HashSet<Edge> res = new HashSet<>(); while(!priorityQueue.isEmpty()) { Edge edge = priorityQueue.remove(); if(!unionFind.isSameSet(edge.from, edge.to)) { res.add(edge); unionFind.union(edge.from, edge.to); } } return res; } }
p算法
选节点,节点选完后解锁边,在根据节点所有已解锁的边中选一个最小且没有解锁新节点的,加入新节点的边,直到选择完所有点
prim: 选节点,从所有节点中选取一个,如果是联通图一个其中任意一个节点都能获取到最小生成树,但如果是一片森林就需要遍历所有节点。从所有节点中获取一个节点,如果没有被访问过就添加这个新节点所连的边(新解锁的边)到优先级队列。优先级队列不为空时一直弹出一条边,如果当前边所指向的顶点没有被访问过,那将这个顶点所连边加入到优先级队列。(可能会重复加入边,但判断的时候都能判断是否访问过)
package com.wtp.图.最小生成树; import java.util.HashSet; import java.util.PriorityQueue; import java.util.Set; import com.wtp.图.图结构.Edge; import com.wtp.图.图结构.Graph; import com.wtp.图.图结构.Node; public class p算法 { public static Set<Edge> prim(Graph graph){ //用来标记解锁的点是否出现过 HashSet<Node> set = new HashSet<>(); PriorityQueue<Edge> priorityQueue = new PriorityQueue<>((o1,o2) -> o1.weight - o2.weight); Set<Edge> res = new HashSet<>(); for(Node node : graph.nodes.values()) {//若为连通图一个点就够 森林情况要都遍历 if(!set.contains(node)) { set.add(node); //随便一个点 for(Edge edge : node.edges) { //由一个点解锁的边 priorityQueue.add(edge); } while(!priorityQueue.isEmpty()) { Edge edge = priorityQueue.remove(); if(!set.contains(edge.to)) {//这个边的点没有遇到过 res.add(edge); set.add(edge.to); //加入新点新解锁的边 for(Edge nextEdge : edge.to.edges) { priorityQueue.add(nextEdge); } } } } } return res; } }
过程中可能有重复加入的边,但重复的边的节点已经结算,所以不影响结论
最短路径
dijkstra:从一个点出发,到其他顶点,找一个从头节点到当前节点最短的节点,遍历该节点的所有边更新到其他顶点的最短距离
dijkstra(可以有权值为负数的边,不能有累加和为负数的环): 建立一个map(Node-Integer):key-从head出发到次node value-head到key的最小距离(如果表中没有点代表正无穷大) set记录点有没有访问过,在所有没走过的节点中选一个目前路径最小的出发点minNode。 到新出发点的距离为map.get(minNode) 如果出发点不为null就从这个点拿边,根据边的toNode节点更新信息:若toNode不在map中就添加,距离为走到这个点的距离加上边的长度。 若在map中就更新距离为原先到达toNode的距离和通过当前minNode的边加到minNode的距离取一个小的作为新路径 遍历完minNode的所有边后记录该节点已访问继续获取下一个minNode
package com.wtp.图.最短路径; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import com.wtp.图.图结构.Edge; import com.wtp.图.图结构.Node; public class d { public static Map<Node,Integer> dijkstra1(Node head){ //从head点到node点的距离为value Map<Node,Integer> distanceMap = new HashMap<>(); distanceMap.put(head, 0); //标记节点是否已经锁定 HashSet<Node> set = new HashSet<>(); Node minNode = getMinDistanceNode(distanceMap, set); while(minNode != null) { int distance = distanceMap.get(minNode); for(Edge edge : minNode.edges) { Node toNode = edge.to; //如果不存在 就新增 存在就将更小的更新 if(!distanceMap.containsKey(toNode)) { distanceMap.put(toNode, edge.weight + distance); }else { distanceMap.put(toNode, Math.min(distanceMap.get(toNode), edge.weight + distance)); } } set.add(minNode); minNode = getMinDistanceNode(distanceMap, set); } return distanceMap; } //从未锁定的节点中找一个最小距离的点 public static Node getMinDistanceNode(Map<Node,Integer> distanceMap,Set<Node> set) { int min = Integer.MAX_VALUE; Node minNode = null; for(Map.Entry<Node,Integer> entry : distanceMap.entrySet()) { Node node = entry.getKey(); int distance = entry.getValue(); if(!set.contains(node) && distance < min) { min = distance; minNode = node; } } return minNode; } }