1. 图的表达、存储和生成方式
图分为有向图和无向图,无向图可以用有向图来表示。
邻接表
上图用邻接表表示如下:
1:2
2:3,4
3:4
4:null
如果上图为无向图,则邻接表表示如下:
1:2
2:1,3,4
3:2,4
4:2,3
邻接矩阵
上图用邻接矩阵表示如下:
1:(2,7),(3,5)
2:(3,2)
3:null
图中点的存储方式
/**
* in 入度:代表几条边进入该点
* out 出度:代表几条边从该点出发
**/
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<>();
}
}图中边的存储方式
/**
* weight 边的权重
* from 边出发的边
* to 边到达的点
**/
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;
}
}图的存储方式
/**
* nodes 点集,Integer 是点中封装的数据项
* edges 边集
*/
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();
for (int i = 0; i < matrix.length; i++) {
Integer weight = matrix[i][0];
Integer from = matrix[i][1];
Integer 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 newEdge = new Edge(weight, fromNode, toNode);
// 更新点的 nexts 集,并更新出入度
fromNode.nexts.add(toNode);
fromNode.out++;
toNode.in++;
// 更新点的 edges 集,并更新图的 edges 集
fromNode.edges.add(newEdge);
graph.edges.add(newEdge);
}
return graph;
}
}
2. 图的宽度优先遍历
- 利用队列实现
- 从源结点开始依次按照宽度进队列,然后弹出
- 每弹出一个点,把该节点所有没有进过队列的邻接点放入队列
- 直到队列变空
/**
* HashSet 用来注册进过队列的点,防止点重复进入队列
*/
public class Code_01_BFS {
public static void bfs(Node node) {
if (node == null) {
return;
}
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) {
if (!map.contains(next)) {
map.add(next);
queue.add(next);
}
}
}
}
}
3. 图的深度优先遍历
- 利用栈实现
- 从源节点开始把节点按照深度放入栈,然后弹出
- 每弹出一个点,把该节点下一个没有进过栈的邻接点放入栈
- 直到栈变空
/**
* HashSet 用来注册进入栈的节点
* 源节点入栈并打印,源节点弹出
* 遍历源节点的所有邻接点
* 找到其中没入过栈的节点 x,将源节点和 x 压回栈中并打印 x
*/
public class Code_02_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;
}
}
}
}
}
4. 拓扑排序
适用范围:
- 有向图
- 有入度为 0 的节点
- 没有环
- 应用:编译过程等
/**
* 通过不断的删除入度为 0 的节点来完成拓扑排序
* 每次删除都会出现新的入度为 0 的节点
* inmap 存储节点入度和节点
* zeroInQueue 表示入度为 0 的节点队列
* @param graph
* @return
*/
public static List<Node> sortedTopology(Graph graph) {
HashMap<Node, Integer> inMap = new HashMap<>();
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;
}
5. 最小生成树的两种算法
K算法和P算法都只能应用于无向图。
- 最小生成树:保证所有点连通的情况下,所有边的权重之和最小
Kruskal
从最小权重的边开始考察,如果形成回路则丢弃该边,直到所有节点被连通。
/**
* 使用并查集,刚开始每个节点各自成一个集合
* 使用小根堆来存放边
* 如果边的 from 和 to 属于同一集合则表示形成了回路,不考虑该边
* 否则对边的 from 和 to 执行 union 操作
*/
//undirected graph only
public class Code_04_Kruskal {
// Union-Find Set
public static class UnionFind {
private HashMap<Node, Node> fatherMap;
private HashMap<Node, Integer> rankMap;
public UnionFind() {
fatherMap = new HashMap<Node, Node>();
rankMap = new HashMap<Node, Integer>();
}
private Node findFather(Node n) {
Node father = fatherMap.get(n);
if (father != n) {
father = findFather(father);
}
fatherMap.put(n, father);
return father;
}
public void makeSets(Collection<Node> nodes) {
fatherMap.clear();
rankMap.clear();
for (Node node : nodes) {
fatherMap.put(node, node);
rankMap.put(node, 1);
}
}
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 aFather = findFather(a);
Node bFather = findFather(b);
if (aFather != bFather) {
int aFrank = rankMap.get(aFather);
int bFrank = rankMap.get(bFather);
if (aFrank <= bFrank) {
fatherMap.put(aFather, bFather);
rankMap.put(bFather, aFrank + bFrank);
} else {
fatherMap.put(bFather, aFather);
rankMap.put(aFather, aFrank + bFrank);
}
}
}
}
public static class EdgeComparator implements Comparator<Edge> {
public 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) {
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;
}
}Prim
/**
* 任选一个点出发,将该点加入点集(HashSet)
* 将该点的所有边加入小根堆中和边集中(HashSet),弹出边如出现新的点则保留
* 将新出现的点的所有边加入小根堆和边集
*/
// undirected graph only
public class Code_05_Prim {
public static class EdgeComparator implements Comparator<Edge> {
public 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<>();
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 : toNode.edges) {
priorityQueue.add(nextEdge);
}
}
}
}
}
return result;
}
}