Leetcode中图的算法是比较常见的类型,比如无向图的单源最短路径,有向图的单源最短路径,多源最短路径等问题,下面就对图的算法进行总结。
1. 题目描述
leetcode题目链接:743. 网络延迟时间
2. 思路分析
这道题目是有向图的单源最短路径,可以使用Dijkstra算法,也可以使用Flod算法。
Dijkstra算法每次取的都是队列中的最小元素,这里可以使用优先队列来实现。
连接关系,可以采用邻接表和邻接矩阵两种。
同时可以使用visited数组来记录是否使用过了(使用过的意思是被当做中间节点 )
3. 参考代码
方法一:Dijkstra算法的优先队列实现
使用邻接表表示连接关系
class Solution {
public int networkDelayTime(int[][] times, int n, int k) {
PriorityQueue<int[]> queue = new PriorityQueue<>((a, b) -> a[1] - b[1]);
// boolean[] visited = new boolean[n + 1];
int[] dist = new int[n + 1];
Arrays.fill(dist, Integer.MAX_VALUE);
dist[k] = 0;
queue.add(new int[]{k, 0});
while (!queue.isEmpty()) {
int[] poll = queue.poll();
int node = poll[0];
int dis = poll[1];
for (int i = 0; i < times.length; i++) {
if (times[i][0] == node) {
int next = times[i][1];
if (dist[next] > dist[node] + times[i][2]) {
dist[next] = dist[node] + times[i][2];
queue.add(new int[]{next, dist[next]});
}
}
}
}
int res = 0;
for (int i = 1; i <= n; i++) {
if (dist[i] == Integer.MAX_VALUE) {
return -1;
}
res = Math.max(res, dist[i]);
}
return res;
}
}
使用邻接矩阵表示连接关系
class Solution {
int maxValue = 999;
public int networkDelayTime(int[][] times, int n, int k) {
int source = k;
int[][] graph = new int[n + 1][n + 1]; // 邻接矩阵
int N = graph.length;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
graph[i][j] = (i == j ? 0 : maxValue);
}
}
for(int i = 0; i < times.length; i++){
int v = times[i][0];
int w = times[i][1];
int weight = times[i][2];
graph[v][w] = weight;
}
int delay = dijkstra(source,graph);
return delay;
}
public int dijkstra(int source,int[][] graph){
int N = graph.length;
int[] distTo = new int[N];
// Queue queue = new LinkedList<>();
PriorityQueue<Integer> queue = new PriorityQueue<>();
Arrays.fill(distTo,maxValue);
distTo[source] = 0;
queue.add(source);
while(!queue.isEmpty()){
int node = queue.poll();
for(int next = 1; next < N; next++){
if(distTo[node] + graph[node][next] < distTo[next]){
distTo[next] = distTo[node] + graph[node][next];
queue.add(next);
}
}
}
int ans = 0;
for(int i = 1; i < N; i++){
if(distTo[i] == maxValue)
return -1;
ans = Math.max(ans,distTo[i]);
}
return ans;
}
}
方法二:SPFA算法
class Solution {
int maxValue = 999;
public int networkDelayTime(int[][] times, int n, int k) {
int source = k;
int[][] graph = new int[n + 1][n + 1]; // 邻接矩阵
int N = graph.length;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
graph[i][j] = (i == j ? 0 : maxValue);
}
}
for(int i = 0; i < times.length; i++){
int v = times[i][0];
int w = times[i][1];
int weight = times[i][2];
graph[v][w] = weight;
}
int delay = dijkstra(source,graph);
return delay;
}
public int dijkstra(int source,int[][] graph){
int N = graph.length;
int[] distTo = new int[N];
Queue<Integer> queue = new LinkedList<>();
Arrays.fill(distTo,maxValue);
distTo[source] = 0;
queue.add(source);
while(!queue.isEmpty()){
int node = queue.poll();
for(int next = 1; next < N; next++){
if(distTo[node] + graph[node][next] < distTo[next]){
distTo[next] = distTo[node] + graph[node][next];
queue.add(next);
}
}
}
int ans = 0;
for(int i = 1; i < N; i++){
if(distTo[i] == maxValue)
return -1;
ans = Math.max(ans,distTo[i]);
}
return ans;
}
}
区别就是是否每次取得最小路径。
另外使用邻接矩阵存储连接关系,效率更高。
leetcode链接:210. 课程表 II
这道题看完就发现是拓扑排序的问题,则使用BFS解法。
class Solution {
public int[] findOrder(int numCourses, int[][] prerequisites) {
// 生成图
List<Integer>[] list = new ArrayList[numCourses];
// 记录节点的入度
int[] points = new int[numCourses];
for (int[] p : prerequisites) {
points[p[0]]++; // [1, 0], 则0->1, 1入度++
if (list[p[1]] == null) {
list[p[1]] = new ArrayList<>();
}
list[p[1]].add(p[0]); // 将0能到达的节点加入集合
}
Queue<Integer> queue = new LinkedList<>();
for (int i = 0; i < numCourses; i++) { // 入度为0的节点加入队列
if (points[i] == 0) {
queue.offer(i);
}
}
int[] res = new int[numCourses]; // 记录结果顺序
int index = 0;
while (!queue.isEmpty()) {
int node = queue.poll();
res[index++] = node;
List<Integer> li = list[node];
if (li == null) continue;
for (int val : li) {
points[val]--;
if (points[val] == 0) {
queue.offer(val);
}
}
}
return index == numCourses ? res : new int[0];
}
}
生成图可以用列表数组,也可以用嵌套列表,效果是一样的
// 生成图
// 用列表数组
List<Integer>[] list = new ArrayList[numCourses];
// 用嵌套列表
List<List<Integer>> list = new ArrayList<>();
class Solution {
public boolean canFinish(int numCourses, int[][] prerequisites) {
List<Integer>[] list = new ArrayList[numCourses];
int[] points = new int[numCourses];
for (int[] p : prerequisites) {
points[p[0]]++;
if (list[p[1]] == null) list[p[1]] = new ArrayList<>();
list[p[1]].add(p[0]);
}
Queue<Integer> queue = new LinkedList<>();
for (int i = 0; i < numCourses; i++) {
if (points[i] == 0) {
queue.offer(i);
}
}
while (!queue.isEmpty()) {
int node = queue.poll();
numCourses--;
if (list[node] == null) continue;
for (int val : list[node]) {
points[val]--;
if (points[val] == 0) {
queue.offer(val);
}
}
}
return numCourses == 0;
}
}
DFS
通过 DFS 判断图中是否有环。有环则返回false。
算法流程:
借助一个标志列表 flags,用于判断每个节点 i (课程)的状态:
对 numCourses 个节点依次执行 DFS,判断每个节点起步 DFS 是否存在环,若存在环直接返回False。DFS 流程;
若整个图 DFS 结束并未发现环,返回 True。
class Solution {
public boolean canFinish(int numCourses, int[][] prerequisites) {
List<List<Integer>> list = new ArrayList<>();
for(int i = 0; i < numCourses; i++) {
list.add(new ArrayList<>());
}
int[] flags = new int[numCourses];
for(int[] p : prerequisites) {
list.get(p[1]).add(p[0]);
}
for(int i = 0; i < numCourses; i++) {
if(!dfs(list, flags, i)) {
return false;
}
}
return true;
}
private boolean dfs(List<List<Integer>> list, int[] flags, int i) {
if(flags[i] == 1) return false;
if(flags[i] == -1) return true;
flags[i] = 1;
for(Integer j : list.get(i)) {
if(!dfs(list, flags, j)) {
return false;
}
}
flags[i] = -1;
return true;
}
}