圖的存儲方式
- 臨接表
- 臨接矩陣
表達
- 點集/邊集
- 有向圖/無向圖
Graph(大結構就是圖)(包含點集合和邊集合)
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<>();}
}
Node(點集合)?
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<>();}
}
Edge(邊集合)
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 GraphGenerator {// matrix 所有的邊// N*3 的矩陣// [weight, from節點上面的值,to節點上面的值]//example//【7,0,1】第一個代表from,第二個代表to,第三個代表weight//【6,1,2】//【4,2,0】public static Graph createGraph(Integer[][] matrix) {Graph graph = new Graph();for (int i = 0; i < matrix.length; i++) { // from:matrix[0][0], to:matrix[0][1] 權重(weight):matrix[0][2]Integer from = matrix[i][0];Integer to = matrix[i][1];Integer weight = matrix[i][2];if (!graph.nodes.containsKey(from)) {//將未出現的from放入點集合graph.nodes.put(from, new Node(from));}if (!graph.nodes.containsKey(to)) {//將未出現的to放入點集合graph.nodes.put(to, new Node(to));}Node fromNode = graph.nodes.get(from);//獲取from點Node toNode = graph.nodes.get(to);//獲取to點Edge newEdge = new Edge(weight, fromNode, toNode);//創建邊fromNode.nexts.add(toNode);//from鄰居fromNode.out++;//出度++toNode.in++;//入度++fromNode.edges.add(newEdge);//將邊放到我所擁有的邊集合里面graph.edges.add(newEdge);//放入邊值}return graph;}}
圖的遍歷
寬度優先遍歷
代碼?
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Queue;public class Code01_BFS {// 從node出發,進行寬度優先遍歷public static void bfs(Node node) {if (node == null) {return;}Queue<Node> queue = new LinkedList<>();HashSet<Node> set = new HashSet<>();//防止數據重復queue.add(node);set.add(node);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);}}}}}
深度優先遍歷
代碼
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;}}}}}
拓撲排序算法
-
要求是有向圖,入度為0的節點,且沒有環
代碼
package class06;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:某一個node// 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);}}// 拓撲排序的結果,依次加入resultList<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;}
}
Kruskal算法(并查集)
無向圖
- 加最小的邊,只要不形成環,就加上,否則不加入
代碼
package class06;import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Set;
import java.util.Stack;//undirected graph only
public class Code04_Kruskal {public static class MySets{public HashMap<Node, List<Node>> setMap;public MySets(List<Node> nodes) {for(Node cur : nodes) {List<Node> set = new ArrayList<Node>();set.add(cur);setMap.put(cur, set);}}public boolean isSameSet(Node from, Node to) {//判斷from和to是否在一個集合List<Node> fromSet = setMap.get(from);List<Node> toSet = setMap.get(to);return fromSet == toSet;//內存地址}public void union(Node from, Node to) {//List<Node> fromSet = setMap.get(from);List<Node> toSet = setMap.get(to);for(Node toNode : toSet) {fromSet.add(toNode);setMap.put(toNode, fromSet);}}}// Union-Find Setpublic static class UnionFind {// key 某一個節點, value key節點往上的節點private HashMap<Node, Node> fatherMap;// key 某一個集合的代表節點, value key所在集合的節點個數private HashMap<Node, Integer> sizeMap;public UnionFind() {fatherMap = new HashMap<Node, Node>();sizeMap = new HashMap<Node, Integer>();}public void makeSets(Collection<Node> nodes) {fatherMap.clear();sizeMap.clear();for (Node node : nodes) {fatherMap.put(node, node);sizeMap.put(node, 1);}}private Node findFather(Node n) {Stack<Node> path = new Stack<>();while(n != fatherMap.get(n)) {path.add(n);n = fatherMap.get(n);}while(!path.isEmpty()) {fatherMap.put(path.pop(), n);}return n;}public boolean isSameSet(Node a, Node b) {return findFather(a) == findFather(b);}public void union(Node a, Node b) {if (a == null || b == null) {return;}Node aDai = findFather(a);Node bDai = findFather(b);if (aDai != bDai) {int aSetSize = sizeMap.get(aDai);int bSetSize = sizeMap.get(bDai);if (aSetSize <= bSetSize) {fatherMap.put(aDai, bDai);sizeMap.put(bDai, aSetSize + bSetSize);sizeMap.remove(aDai);} else {fatherMap.put(bDai, aDai);sizeMap.put(aDai, aSetSize + bSetSize);sizeMap.remove(bDai);}}}}public static class EdgeComparator implements Comparator<Edge> {@Overridepublic int compare(Edge o1, Edge o2) {return o1.weight - o2.weight;}}public static Set<Edge> kruskalMST(Graph graph) {UnionFind unionFind = new UnionFind();unionFind.makeSets(graph.nodes.values());PriorityQueue<Edge> priorityQueue = new PriorityQueue<>(new EdgeComparator());for (Edge edge : graph.edges) { // M 條邊priorityQueue.add(edge); // O(logM)}Set<Edge> result = new HashSet<>();while (!priorityQueue.isEmpty()) { // M 條邊Edge edge = priorityQueue.poll(); // O(logM)if (!unionFind.isSameSet(edge.from, edge.to)) { // O(1)result.add(edge);unionFind.union(edge.from, edge.to);}}return result;}
}
prim算法
無向圖
代碼
package class06;import java.util.Comparator;
import java.util.HashSet;
import java.util.PriorityQueue;
import java.util.Set;// undirected graph only
public class Code05_Prim {public static class EdgeComparator implements Comparator<Edge> {@Overridepublic int compare(Edge o1, Edge o2) {return o1.weight - o2.weight;}}public static Set<Edge> primMST(Graph graph) {// 解鎖的邊進入小根堆PriorityQueue<Edge> priorityQueue = new PriorityQueue<>(new EdgeComparator());HashSet<Node> set = new HashSet<>();Set<Edge> result = new HashSet<>(); // 依次挑選的的邊在result里for (Node node : graph.nodes.values()) { // 隨便挑了一個點// node 是開始點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 : toNode.edges) {priorityQueue.add(nextEdge);}}}}//break;}return result;}// 請保證graph是連通圖// graph[i][j]表示點i到點j的距離,如果是系統最大值代表無路// 返回值是最小連通圖的路徑之和public static int prim(int[][] graph) {int size = graph.length;int[] distances = new int[size];boolean[] visit = new boolean[size];visit[0] = true;for (int i = 0; i < size; i++) {distances[i] = graph[0][i];}int sum = 0;for (int i = 1; i < size; i++) {int minPath = Integer.MAX_VALUE;int minIndex = -1;for (int j = 0; j < size; j++) {if (!visit[j] && distances[j] < minPath) {minPath = distances[j];minIndex = j;}}if (minIndex == -1) {return sum;}visit[minIndex] = true;sum += minPath;for (int j = 0; j < size; j++) {if (!visit[j] && distances[j] > graph[minIndex][j]) {distances[j] = graph[minIndex][j];}}}return sum;}public static void main(String[] args) {System.out.println("hello world!");}}
?Dijkstra算法(單元最遠路徑算法)
-
要求沒有權值為負的邊
代碼
package class06;import java.util.HashMap;
import java.util.HashSet;
import java.util.Map.Entry;// no negative weight
public class Code06_Dijkstra {public static HashMap<Node, Integer> dijkstra1(Node head) {// 從head出發到所有點的最小距離// key : 從head出發到達key// value : 從head出發到達key的最小距離// 如果在表中,沒有T的記錄,含義是從head出發到T這個點的距離為正無窮HashMap<Node, Integer> distanceMap = new HashMap<>();distanceMap.put(head, 0);// 已經求過距離的節點,存在selectedNodes中,以后再也不碰HashSet<Node> selectedNodes = new HashSet<>();Node minNode = getMinDistanceAndUnselectedNode(distanceMap, selectedNodes);while (minNode != null) {int distance = distanceMap.get(minNode);for (Edge edge : minNode.edges) {Node toNode = edge.to;if (!distanceMap.containsKey(toNode)) {distanceMap.put(toNode, distance + edge.weight);} else {distanceMap.put(edge.to, Math.min(distanceMap.get(toNode), distance + edge.weight));}}selectedNodes.add(minNode);minNode = getMinDistanceAndUnselectedNode(distanceMap, selectedNodes);}return distanceMap;}public static Node getMinDistanceAndUnselectedNode(HashMap<Node, Integer> distanceMap, HashSet<Node> touchedNodes) {Node minNode = null;int minDistance = Integer.MAX_VALUE;for (Entry<Node, Integer> entry : distanceMap.entrySet()) {Node node = entry.getKey();int distance = entry.getValue();if (!touchedNodes.contains(node) && distance < minDistance) {minNode = node;minDistance = distance;}}return minNode;}public static class NodeRecord {public Node node;public int distance;public NodeRecord(Node node, int distance) {this.node = node;this.distance = distance;}}public static class NodeHeap {private Node[] nodes; // 實際的堆結構// key 某一個node, value 上面數組中的位置private HashMap<Node, Integer> heapIndexMap;// key 某一個節點, value 從源節點出發到該節點的目前最小距離private HashMap<Node, Integer> distanceMap;private int size; // 堆上有多少個點public NodeHeap(int size) {nodes = new Node[size];heapIndexMap = new HashMap<>();distanceMap = new HashMap<>();size = 0;}public boolean isEmpty() {return size == 0;}// 有一個點叫node,現在發現了一個從源節點出發到達node的距離為distance// 判斷要不要更新,如果需要的話,就更新public void addOrUpdateOrIgnore(Node node, int distance) {if (inHeap(node)) {distanceMap.put(node, Math.min(distanceMap.get(node), distance));insertHeapify(node, heapIndexMap.get(node));}if (!isEntered(node)) {nodes[size] = node;heapIndexMap.put(node, size);distanceMap.put(node, distance);insertHeapify(node, size++);}}public NodeRecord pop() {NodeRecord nodeRecord = new NodeRecord(nodes[0], distanceMap.get(nodes[0]));swap(0, size - 1);heapIndexMap.put(nodes[size - 1], -1);distanceMap.remove(nodes[size - 1]);// free C++同學還要把原本堆頂節點析構,對java同學不必nodes[size - 1] = null;heapify(0, --size);return nodeRecord;}private void insertHeapify(Node node, int index) {while (distanceMap.get(nodes[index]) < distanceMap.get(nodes[(index - 1) / 2])) {swap(index, (index - 1) / 2);index = (index - 1) / 2;}}private void heapify(int index, int size) {int left = index * 2 + 1;while (left < size) {int smallest = left + 1 < size && distanceMap.get(nodes[left + 1]) < distanceMap.get(nodes[left])? left + 1: left;smallest = distanceMap.get(nodes[smallest]) < distanceMap.get(nodes[index]) ? smallest : index;if (smallest == index) {break;}swap(smallest, index);index = smallest;left = index * 2 + 1;}}private boolean isEntered(Node node) {return heapIndexMap.containsKey(node);}private boolean inHeap(Node node) {return isEntered(node) && heapIndexMap.get(node) != -1;}private void swap(int index1, int index2) {heapIndexMap.put(nodes[index1], index2);heapIndexMap.put(nodes[index2], index1);Node tmp = nodes[index1];nodes[index1] = nodes[index2];nodes[index2] = tmp;}}// 改進后的dijkstra算法// 從head出發,所有head能到達的節點,生成到達每個節點的最小路徑記錄并返回public static HashMap<Node, Integer> dijkstra2(Node head, int size) {NodeHeap nodeHeap = new NodeHeap(size);nodeHeap.addOrUpdateOrIgnore(head, 0);HashMap<Node, Integer> result = new HashMap<>();while (!nodeHeap.isEmpty()) {NodeRecord record = nodeHeap.pop();Node cur = record.node;int distance = record.distance;for (Edge edge : cur.edges) {nodeHeap.addOrUpdateOrIgnore(edge.to, edge.weight + distance);}result.put(cur, distance);}return result;}}