目录
1 生成图
图的表示
图表示有很多中,一般在面试中常见的如下:
代码实现
图的节点
//图的节点的定义 public class Node { //value的类型不一定是整型可以是泛型 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<>(); } }
图的边
//图的边的的定义 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; } }
图的定义
//图的定义 public class Graph { //图就是所有点的集合和边的集合 public HashMap<Integer,Node> nodes; public HashSet<Edge> edges; public Graph() { nodes = new HashMap<>(); edges = new HashSet<>(); } }
生成图
public class GraphGenerator { //生成图 public static Graph createGraph(Integer[][] matrix) { Graph graph = new Graph(); //如博客中给出的示例,矩阵的行数不定但是列数为3即每一行的长度为3 for (int i = 0; i < matrix.length; i++) { Integer from = matrix[i][0]; Integer to = matrix[i][1]; Integer weight = matrix[i][2]; //如果from这个点的没有在图中定义,则建上该点 if (!graph.nodes.containsKey(from)) { graph.nodes.put(from, new Node(from)); } //如果to这个点的没有在图中定义,则建上该点 if (!graph.nodes.containsKey(to)) { graph.nodes.put(to, new Node(to)); } //拿到from点和to点 Node fromNode = graph.nodes.get(from); Node toNode = graph.nodes.get(to); //根据两个点生成一个边 Edge newEdge = new Edge(weight, fromNode, toNode); //from的邻居增加to这个点 fromNode.nexts.add(toNode); //from点出度自增 fromNode.out++; //to点入度自增 toNode.in++; //from点的边集中增加这个边 fromNode.edges.add(newEdge); //整个图的边集中增加这个边 graph.edges.add(newEdge); } return graph; } }
2 图的遍历
宽度(广度)优先遍历
依照距离起始节点的距离确定输出的先后顺序,同等距离则可能会出现不同情况,注意图的遍历顺序是不确定的
//整体与树中的BFS很类似的,不同点在于需要注意重复元素 public static void bfs(Node node) { if (node == null) { return; } //用队列实现BFS Queue<Node> queue = new LinkedList<>(); //用来记录哪些节点已经进入了 HashSet<Node> map = new HashSet<>(); queue.add(node); map.add(node); while (!queue.isEmpty()) { Node cur = queue.poll(); System.out.println(cur.value); for (Node next : cur.nexts) { //只有没进过set集合的节点才添加,避免重复添加的问题 if (!map.contains(next)) { map.add(next); queue.add(next); } } } }
深度优先
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) { //如果当前节点的后代不在set中,那就把当前节点和其后代都放入栈中 if (!set.contains(next)) { stack.push(cur); stack.push(next); set.add(next); System.out.println(next.value); break; } } } }
3 进阶算法
1 拓扑排序算法
适用范围:有向图,且有入度为0的节点,且没有环
使用场景:编辑一个工程A其依赖有BCDE配置文件,配置文件内部又相互依赖,决定应该先编译哪一个文件。
算法思想:
代码实现:
public static List<Node> sortedTopology(Graph graph) { //统计当前所有节点的入度 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()) { //弹出一个入度为0的点,并在结果中保存 Node cur = zeroInQueue.poll(); result.add(cur); //把当前节点的所有后代入度自减1,实质就是消除该节点的影响 for (Node next : cur.nexts) { inMap.put(next, inMap.get(next) - 1); //如果有入度为0的点进队列 if (inMap.get(next) == 0) { zeroInQueue.add(next); } } } return result; }
最小生成树算法
适用范围:无向图
使用场景:在保证图的连通性前提下,使得图的权值最小
1 Kruskal 算法
算法思想:选择权值最小的边,如下图中先选择1,然后选择2,如果选择的边构成了一个回路则舍弃,当选择的边能够遍历到所有的点时算法结束。
public static Set<Edge> kruskalMST(Graph graph) { //把所有的点放入并查集中 UnionFind unionFind = new UnionFind(); unionFind.makeSets(graph.nodes.values()); //按照边的权重组成一个小根堆,堆中放的都是边 PriorityQueue<Edge> priorityQueue = new PriorityQueue<>(); for (Edge edge : graph.edges) { priorityQueue.add(edge); } //保存最后的结果 Set<Edge> result = new HashSet<>(); while (!priorityQueue.isEmpty()) { Edge edge = priorityQueue.poll(); //每次从最小堆弹出一个边,如果边的出入节点已经属于一个集合,则舍弃该边 if (!unionFind.isSameSet(edge.from, edge.to)) { //如果没有则添加该边,并把该边的出入节点合并在一起 result.add(edge); unionFind.union(edge.from, edge.to); } } return result; }
2 prim 算法
算法思想:从一个点出发(V1)找其发出边中最小的边,到达下一个新的节点,然后从这两个点的所有发出边中找到最小可以到达新的点的边,直至达到所有的点。
public static Set<Edge> primMST(Graph graph) { //最小堆中放边 PriorityQueue<Edge> priorityQueue = new PriorityQueue<>(); //保存已经遍历过的点 HashSet<Node> set = new HashSet<>(); //保存结果的边 Set<Edge> result = 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.poll(); Node toNode = edge.to; //只有没遍历过的新点才加入 if (!set.contains(toNode)) { set.add(toNode); result.add(edge); //加入新的节点的所有发出边 for (Edge nextEdge : node.edges) { priorityQueue.add(nextEdge); } } } } } return result; }
0