目录
难阿,菜阿。。本文章持续更新。。。。。。。
1. 图的表示
(u,v) 表示一条边,u、v 都表示图的节点
-
邻接矩阵
二维数组存储即可:int grab[][]
无向图:grab[i][j] == grab[i][j] 需要注意的是,在初始化无向图的权值时,一定要初始化grab[i][j],grab[i][j] 有向图:grab[i][j] != grab[i][j] 权值:grab[i][j]
优点:访问快,算法简单,修改也非常简单。
缺点:只适合密集类型的图,疏松的就很浪费空间。 -
邻接链表
表示方法非常多,链表,集合都可以。 例如:Map<List<int[]>>, Map<List<Node>>, Node[] 优点也很明显,对于稀疏的图来说,相对于邻接矩阵,是非常节省空间的。 <br>链表的缺点就是这个存储的缺点
-
链式前向星
2. 拓扑排序
一个图能进行拓扑排序的充要条件是 它是一个有向无环(DAG)。
所以不是每张图都可以使用拓扑排序的。拓扑排序有两种 bfs/dfs。
bfs适合处理入度一样的同级关系的问题,而dfs适合处理上下级关系的问题。
2.1 bfs
需要清楚入度/出度的概念,首先要知道边的表示(u, v),u、v都是节点。
入度:以u为起点的边的数量。
出度:以v为终点的边的数量。
入度为0的先进入队列,然后一直维护入度。
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class Main {
private void bfs(int[][] grid, int n) {
// 初始化
int[] inDegrees = new int[n];
List<List<Integer>> edges = new ArrayList<>();
for (int i = 0; i < n; i++) {
edges.add(new ArrayList<>());
}
for (int[] edge : grid) {
int u = edge[0], v = edge[1];
inDegrees[v]++;
edges.get(u).add(v);
}
// 进队列
LinkedList<Integer> query = new LinkedList<>();
for (int i = 0; i < inDegrees.length; i++) {
if (inDegrees[i] == 0) {
query.offer(inDegrees[i]);
}
}
// 拓扑排序核心
while (!query.isEmpty()) {
int u = query.poll();
List<Integer> edge = edges.get(u);
// 维护每个节点的入度
for (int v : edge) {
inDegrees[v]--;
if (inDegrees[v] == 0) {
query.offer(v);
}
}
}
}
}
2.2 dfs
3. 最短路径路径问题
定义:有向或无向图,n个点、m条边、边有权值。起点是s,终点是t,在所有能连接到s和t的路径中寻找边的权值之和
最小的路径。
3.1 dijkstra
贪心思想:抄近路走,肯定能找到最端路径
思路:每次都选择最短的边进行计算。
问题场景:适合图规模大,且边的权值非负。
3.1.2 for循环写法
下面代码是最小路径和的模板。for循环这种适合邻接矩阵。
public class Main{
// 假设 {n | 0 <= n <= 2^31 - 1}
private void dijkstra(int[][] grid, int n) {
int info = Integer.MAX_VALUE;
// 注意我假设 n >= 0
int[] dis = new int[n];
boolean[] use = new boolean[n];
for (int i = 0; i < n; i++) {
int x = -1;
// 每次获取最小的路径
for (int y = 0; y < n; y++) {
if (!use[y] && (x == -1 || dis[y] < dis[x])) {
x = y;
}
}
use[x] = true;
for (int y = 0; y < n; y++) {
if (dis[y] > grid[x][y] + dis[x]) {
dis[y] = grid[x][y] + dis[x];
}
}
}
}
}
3.2.2 优先队列
下面代码是最小路径和的模板。优先队列这种比较适合邻接链表类型的数据结构。
import java.util.*;
public class Main {
enum Type {
undirected, // 无向
directed // 有向
}
// 注意无向图和有向图的初始化
private void setVal(Type type, Map<Integer, List<int[]>> map, int[] edge) {
int u = edge[0], v = edge[1], w = edge[2];
if (!map.containsKey(u)) map.put(u, new ArrayList<>());
switch (type) {
// 无向
case directed:
map.get(u).add(new int[]{v, w});
map.get(v).add(new int[]{u, w});
break;
// 有向
case undirected:
map.get(u).add(new int[]{v, w});
break;
}
}
private void dijkstra(int[][] data, int n) {
// int[]{u, w} u是节点,w是权值
Map<Integer, List<int[]>> map = new HashMap<>();
for (int[] edge : data) {
// 选择无向图
setVal(Type.undirected, map, edge);
}
// 注意我假设 n >= 0
int[] dis = new int[n];
boolean[] use = new boolean[n];
LinkedList<int[]> query = new LinkedList<>();
while (!query.isEmpty()) {
int[] u = query.poll();
if (use[u[0]]) continue;
use[u[0]] = true;
List<int[]> edges = map.get(u[0]);
if (edges != null) {
for (int[] edge : edges) {
int to = edge[0];
if (use[to]) {
continue;
} else if (dis[to] > u[1] + edge[1]) { // 求最小路径和
dis[to] = u[1] + edge[1];
query.offer(new int[]{to, dis[to]});
}
}
// 最小路径和,这里按照权值进行升序
// 因为poll是从出口拿元素,所以我们必须保证出口是一个权值最小的边。
// 如果觉得自己很牛。。可以优化一下这里。
query.sort(new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
return o1[1] - o2[1];
}
});
}
}
}
}
LeetCode统计路径数量问题
1976: 求最短路径的路径数量。
要做出这道题目,必须了解Dijkstra的过程,同一个节点可能会被多次遍历。比如:(u, v)边,u的权值+v
的权值如果小于v已经存在记录的权值和,那从起点0到v的最小路径就要被更换,此时v点的路径数量应该换成
u的路径数量。如果等于那就加起来。
public class Main{
public int countPaths(int n, int[][] roads) {
int mod = 1000000007, inf = Integer.MAX_VALUE;
Map<Integer, List<int[]>> map = new HashMap<>();
for (int i = 0; i < n; i++) {
map.put(i, new ArrayList<>());
}
for (int[] edge : roads) {
int u = edge[0],
v = edge[1],
w = edge[2];
map.get(u).add(new int[]{v, w});
map.get(v).add(new int[]{u, w});
}
int[] dis = new int[n];
boolean[] use = new boolean[n];
int[] pre = new int[n];
Arrays.fill(pre, 1);
LinkedList<int[]> query = new LinkedList<>();
Arrays.fill(dis, inf);
dis[0] = 0;
query.add(new int[]{0, 0});
while (!query.isEmpty()) {
int[] u = query.poll();
if (use[u[0]]) continue;
use[u[0]] = true;
List<int[]> edges = map.get(u[0]);
if (edges != null) {
for (int[] edge : edges) {
int to = edge[0];
if (use[to]) {
continue;
} else if (dis[to] > u[1] + edge[1]) {
// u点到v点权值和更小,直接替换路径数量
pre[to] = pre[u[0]];
dis[to] = u[1] + edge[1];
query.offer(new int[]{to, dis[to]});
} else if (dis[to] == u[1] + edge[1]) {
// 权值和相同,就是多条路径到v点的距离都相同。
pre[to] += pre[u[0]];
pre[to] %= mod;
}
}
query.sort(new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
return o1[1] - o2[1];
}
});
}
}
//System.out.println(Arrays.toString(pre));
return pre[n - 1];
}
}
LeetCode计算路径权值
class Solution {
public double maxProbability(int n, int[][] edges, double[] succProb, int start, int end) {
Map<Integer ,List<Node>> table = new HashMap<>();
int row = edges.length;
for (int i = 0; i < n; i++) {
table.put(i, new ArrayList<>());
}
for (int i = 0; i < row; i++) {
int u = edges[i][0],
v = edges[i][1];
double w = succProb[i];
table.get(u).add(new Node(v, w));
table.get(v).add(new Node(u, w));
}
double[] dis = new double[n];
boolean[] use = new boolean[n];
PriorityQueue<Node> query = new PriorityQueue<>();
query.offer(new Node(start, 1));
dis[start] = 1;
while (!query.isEmpty()) {
Node u = query.poll();
if (use[u.v]) continue;
use[u.v] = true;
List<Node> v = table.get(u.v);
if (v != null) {
for (Node node : v) {
int to = node.v;
if (use[to]) {
continue;
} else if (dis[to] < u.w * node.w) {
dis[to] = u.w * node.w;
query.offer(new Node(to, dis[to]));
}
}
}
}
return dis[end];
}
class Node implements Comparable<Node>{
int v;
double w;
Node(int v, double w) {
this.v = v;
this.w = w;
}
@Override
public int compareTo(Node o) {
return this.w - o.w > 0 ? -1 : 1;
}
}
}
class Solution {
public int findTheCity(int n, int[][] edges, int distanceThreshold) {
int[][] grid = new int[n][n];
for (int[] edge : edges) {
int u = edge[0],
v = edge[1],
w = edge[2];
grid[u][v] = w;
grid[v][u] = w;
}
// System.out.println("grid");
// for (int[] edge : grid) {
// System.out.println(Arrays.toString(edge));
// }
int inf = Integer.MAX_VALUE ;
// 记录节点的权值
int[] dis = new int[n];
boolean[] use = new boolean[n];
int curr = 0;
long number = inf;
for (int i = 0; i < n; i++) {
Arrays.fill(dis, inf);
Arrays.fill(use, false);
dis[i] = 0;
for (int j = 0; j < n; j++) {
int x = -1;
for (int k = 0; k < n; k++) {
if (!use[k] && (x == -1 || dis[k] < dis[x])) {
x = k;
}
}
use[x] = true;
for (int k = 0; k < n; k++) {
if (grid[x][k] != 0 && dis[x] != inf) {
int w = grid[x][k] + dis[x];
dis[k] = Math.min(w, dis[k]);
}
}
}
// System.out.println(Arrays.toString(dis));
long count = Arrays.stream(dis).filter(a -> a <= distanceThreshold).count();
if (count <= number) {
curr = i;
number = count;
}
}
return curr;
}
}
3.2 floyd-warshall
思想:floyd用到了动态规划的思想:即求两点i,j的最短路径,分两种情况
第一种是经过图中某个点k的路径和不经过点k的路径,取两者最短。
缺点:这个算法时间复杂度n^3,只能用于小规模的图 < 200。
优点:程序简单,可以判断负圈、可以一次求出所有节点的最短路径。
下面模板是最小路径和。
public class Main{
// 我这个写法是会改变grid的数据,注意啊!!!!
private void floyd(int[][] grid, int n) {
for (int k = 0; k < n; k++) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] > grid[i][k] + grid[k][j]) {
grid[i][j] = grid[i][k] + grid[k][j];
}
}
}
}
// gird[x][y] 就是计算好的最小路径和,拿出来就行了。
}
}