計算機基礎速通--數據結構·圖的基礎應用二(基礎圖算法)

如有問題大概率是我的理解比較片面,歡迎評論區或者私信指正。

最近了解到了一個新的改變和提高自己的方法·時刻記錄不論多小的事情都記下,我目前用了4天,之前感覺一天天忙死但沒啥收獲,但是記錄了之后知道自己的時間花在了哪里,后期該如何調整,并且可以很好緩解焦慮情緒。

一、最小生成樹(最小代價樹)

1、基礎概念

最小生成樹基本概念

MST的定義:權值和最小的生成樹(連通所有頂點、無環、邊數=頂點數-1、砍掉一條邊則不連通)。

性質:權值和唯一,但樹形可能不唯一(存在相同權值的邊時)。

適用場景:連通圖(非連通圖只有生成森林)。

Prim算法

核心思想貪心策略,從單個頂點逐步擴展,每次添加與當前樹連接的最小權值邊對應的頂點。

實現關鍵

  • 維護lowCost[]數組存儲頂點到生成樹的最小代價。
  • isJoin[]標記頂點是否已加入樹(避免成環)。

時間復雜度(適合稠密圖)。

Kruskal算法

核心思想:貪心策略、按邊權升序選擇邊、避免成環。

實現關鍵

邊排序

并查集(Union-Find)判斷邊的兩點是否連通。

時間復雜度(適合稀疏圖)。

兩種算法對比

適用場景:稠密圖用Prim,稀疏圖用Kruskal。

時間復雜度差異:Prim依賴頂點數,Kruskal依賴邊數。

2、Prim 算法(普?姆)

數據結構

lowCost[]:記錄各頂點加入生成樹的最小代價(初始:起點為0,直連頂點為邊權,非直連為∞)

isJoin[]:標記頂點是否已加入生成樹(初始:僅起點為true

核心流程

for i in range(n-1):  # 共n-1輪# 1. 掃描lowCost找最小值頂點u(未加入的頂點)u = find_min(lowCost, isJoin)  isJoin[u] = True  # 加入生成樹# 2. 更新鄰接點v的lowCostfor v in graph[u]:if not isJoin[v] and graph[u][v] < lowCost[v]:lowCost[v] = graph[u][v]

時間復雜度

樸素實現:O(|V|^2)

堆優化:O(|E| log |V|)(用優先隊列存(lowCost, v)

易錯點

初始化錯誤

  • 未將起點lowCost設為0(誤設為∞)
  • 未標記起點isJoin=True(導致重復加入)

更新邏輯錯誤

  • 更新鄰接點時未檢查?isJoin[v](已加入頂點不應更新)
  • 錯誤更新非鄰接點(應只更新當前頂點的鄰接點)

非連通圖處理

  • 未檢查是否加入所有頂點(若未完成但lowCost全為∞,說明圖不連通)

權值比較錯誤

  • 更新時用邊權與舊lowCost比較(正確:graph[u][v] < lowCost[v]
  • 未處理重邊(應取最小權值邊)
基礎模板如下:

import java.util.*;public class PrimMST {// 樸素版Prim算法實現(O(V^2))public static int prim(int[][] graph) {int n = graph.length;// 易錯點1:初始化 - 未正確設置起點會導致邏輯錯誤int[] lowCost = new int[n];      // 各頂點加入生成樹的最小代價boolean[] isJoin = new boolean[n]; // 標記頂點是否已加入生成樹Arrays.fill(lowCost, Integer.MAX_VALUE);lowCost[0] = 0; // 起點最小代價設為0(易錯點1.1:起點設為0)int totalCost = 0;int selectedCount = 0; // 已選頂點計數(用于檢測非連通圖)// 核心流程:共需選擇n個頂點(包括起點)for (int i = 0; i < n; i++) {// 1. 尋找當前lowCost中的最小值頂點uint u = -1;for (int v = 0; v < n; v++) {// 只考慮未加入的頂點(易錯點2:已加入頂點不能重復選擇)if (!isJoin[v] && (u == -1 || lowCost[v] < lowCost[u])) {u = v;}}isJoin[u] = true; // 將u加入生成樹totalCost += lowCost[u];selectedCount++;// 2. 更新u的鄰接點的lowCost值for (int v = 0; v < n; v++) {// 易錯點4:更新條件判斷(必須同時滿足三個條件)if (!isJoin[v] && graph[u][v] != 0 && graph[u][v] < lowCost[v]) {// 注意:graph[u][v]!=0 表示存在邊(0表示無直接邊)// 易錯點5:權值比較(需嚴格小于當前值才更新)lowCost[v] = graph[u][v];}}}// 易錯點3補充:檢查是否加入全部頂點if (selectedCount != n) {System.out.println("圖不連通!");return -1;}return totalCost;}public static void main(String[] args) {int[][] graph = {{0, 1, 5, 6},{1, 0, 5, 0},  // 易錯點7:無向圖需要對稱{5, 5, 0, 4},{6, 0, 4, 0}};int cost = prim(graph);if (cost != -1) {System.out.println("最小生成樹權值和: " + cost); // 正確結果:10}}
}
練習1:訓練Prim的二維坐標處理能力

1584. 連接所有點的最小費用 - 力扣(LeetCode)https://leetcode.cn/problems/min-cost-to-connect-all-points/description/?utm_source=LCUS&utm_medium=ip_redirect&utm_campaign=transfer2china

class Solution {public int minCostConnectPoints(int[][] points) {int n=points.length;//標記數組boolean[] isJion=new boolean[n];//最小代價數組int[] lowCost=new int[n];//初始化Arrays.fill(isJion,false);Arrays.fill(lowCost,Integer.MAX_VALUE);lowCost[0]=0;int selectedCount=0;//非連通圖檢測//記錄最終答案int ans=0;//prim算法for(int i=0;i<n;i++){//依次將lowCost中代價最小的節點加入生成樹int u=-1;//找出lowCost中代價最小的節點for(int v=0;v<n;v++){//節點未加入生成樹 && (第一次·生成樹的根節點 || 更小花費)if(!isJion[v] && (u==-1 || lowCost[v]<lowCost[u])){u=v;}}//加入生成樹并處理isJion[u]=true;ans+=lowCost[u];selectedCount++;//更新鄰接點的lowCostfor(int v=0;v<n;v++){if(!isJion[v] && cost(u,v,points)<lowCost[v]){lowCost[v]=cost(u,v,points);}}}return ans;}public int cost(int u,int v,int[][] points){return Math.abs(points[u][0]-points[v][0])+Math.abs(points[u][1]-points[v][1]);}
}

3、Kruskal 算法(克魯斯卡爾)

數據結構

邊集數組:按權值排序?edges = [(weight, u, v), ...]

并查集:判斷連通性(初始每個頂點獨立集合)

核心流程

edges.sort()  # 按權值升序排序
for weight, u, v in edges:if find(u) != find(v):  # 不連通union(u, v)         # 合并集合add_edge(u, v)      # 加入生成樹if edges_added == n-1: break  # 邊數達標終止

時間復雜度

排序:O(|E| log |E|)

并查集:O(\alpha(|V|))每輪(近似常數)

排序遺漏

  • 未對邊排序直接遍歷(破壞貪心策略正確性)

并查集錯誤

  • 未初始化并查集(每個頂點應為獨立集合)
  • 合并后未更新父節點(需路徑壓縮優化)

環判斷錯誤

  • 未檢查連通性直接加邊(應用并查集而非DFS/BFS判環)

終止條件缺失

  • 未在邊數達n-1時提前終止(多余計算)

重邊處理

  • 未去重直接排序(應保留最小權值重邊)
import java.util.*;public class KruskalMST {// 邊類static class Edge implements Comparable<Edge> {int src, dest, weight;public Edge(int src, int dest, int weight) {this.src = src;this.dest = dest;this.weight = weight;}@Overridepublic int compareTo(Edge other) {// 易錯點1:排序遺漏 - 必須按權值排序return this.weight - other.weight;}}// 并查集類static class UnionFind {int[] parent, rank;public UnionFind(int n) {parent = new int[n];rank = new int[n];// 易錯點2:并查集未初始化 - 每個頂點獨立集合for (int i = 0; i < n; i++) {parent[i] = i;  // 初始每個頂點獨立成集合}}public int find(int x) {// 路徑壓縮優化if (parent[x] != x) {parent[x] = find(parent[x]);}return parent[x];}public void union(int x, int y) {int rootX = find(x);int rootY = find(y);if (rootX == rootY) return;// 按秩合并if (rank[rootX] < rank[rootY]) {parent[rootX] = rootY;} else if (rank[rootX] > rank[rootY]) {parent[rootY] = rootX;} else {parent[rootY] = rootX;rank[rootX]++;}}}public static List<Edge> kruskal(List<Edge> edges, int n) {// 易錯點1:排序遺漏 - 必須先對邊排序Collections.sort(edges);UnionFind uf = new UnionFind(n);List<Edge> mst = new ArrayList<>();int edgesAdded = 0;for (Edge edge : edges) {// 易錯點3:環判斷錯誤 - 必須檢查連通性if (uf.find(edge.src) != uf.find(edge.dest)) {uf.union(edge.src, edge.dest);mst.add(edge);edgesAdded++;// 易錯點4:終止條件缺失 - 邊數達標提前終止if (edgesAdded == n - 1) break;}}// 檢查是否形成完整生成樹if (edgesAdded != n - 1) {System.out.println("圖不連通,無法形成最小生成樹");return null;}return mst;}public static void main(String[] args) {int n = 4;List<Edge> edges = new ArrayList<>();edges.add(new Edge(0, 1, 1));edges.add(new Edge(0, 2, 5));edges.add(new Edge(0, 3, 6));edges.add(new Edge(1, 2, 5));edges.add(new Edge(2, 3, 4));// 處理重邊(可選)// 易錯點5:重邊處理 - 保留最小權值邊// 本例無重邊,實際應用中需要預處理List<Edge> mst = kruskal(edges, n);if (mst != null) {System.out.println("最小生成樹邊集:");int totalWeight = 0;for (Edge edge : mst) {System.out.println(edge.src + " - " + edge.dest + " : " + edge.weight);totalWeight += edge.weight;}System.out.println("總權重: " + totalWeight); // 正確結果:4+2+4+4+4=18}}
}
練習2:掌握Kruskal并查集實現

1584. 連接所有點的最小費用 - 力扣(LeetCode)https://leetcode.cn/problems/min-cost-to-connect-all-points/?utm_source=LCUS&utm_medium=ip_redirect&utm_campaign=transfer2china

class Solution {public int minCostConnectPoints(int[][] points) {//極端if(points.length==0 || points==null)return 0;//邊List<Eadge> eadges=new ArrayList<>();//并查集UnionFind uf=new UnionFind(points.length);//終止條件int selectedCount=0;int ans=0;//初始化邊for(int v=0;v<points.length;v++){for(int u=v+1;u<points.length;u++){eadges.add(new Eadge(v,u,cost(v,u,points)));}}//排序Collections.sort(eadges);for(Eadge e:eadges){//判斷連通性if(uf.find(e.s)!=uf.find(e.e)){uf.union(e.s,e.e);selectedCount++;ans+=e.weight;}//終止if(selectedCount==points.length-1)break;}return ans;}public int cost(int u,int v,int[][] points){return Math.abs(points[u][0]-points[v][0])+Math.abs(points[u][1]-points[v][1]);}
}//邊類
class Eadge implements Comparable<Eadge>{int s;int e;int weight;public Eadge(int s,int e,int weight){this.s=s;this.e=e;this.weight=weight;}//重寫比較·升序@Overridepublic int compareTo(Eadge other) {return this.weight - other.weight;}
}//并查集類class UnionFind {//雙親數組·記錄當前下標節點的雙親節點下標int[] parent;//rank數組·union優化int[] rank;//初始化public UnionFind(int n){parent=new int[n];rank=new int[n];Arrays.fill(rank,0);//初始·每個節點都是獨立集合for(int i=0;i<n;i++){parent[i]=i;}}//查找·路徑優化public int find(int x){//終止條件·根節點的父節點是自己本身if(parent[x]!=x){parent[x]=find(parent[x]);}return parent[x];}//合并·高度壓縮(小樹并大樹上)public void union(int x,int y){//找根int v=find(x);int u=find(y);if(v==u)return ;//同一個集合不需要合并//檢查秩if(rank[v]<rank[u]){parent[v]=u;}else if(rank[v]>rank[u]){parent[u]=v;}else{parent[v]=u;rank[v]++;}}}

4、綜合練習·考察MST關鍵邊的理解

1489. 找到最小生成樹里的關鍵邊和偽關鍵邊 - 力扣(LeetCode)https://leetcode.cn/problems/find-critical-and-pseudo-critical-edges-in-minimum-spanning-tree/description/

二、最短路徑

基礎

1.?算法適用場景
算法無權圖帶權圖負權邊負權回路單源/多源
BFS????單源(無權圖)
Dijkstra????單源(帶權圖)
Floyd????多源(任意兩點)
2.?核心原理
  • BFS:層序遍歷,距離=層數(權值視為1)。

  • Dijkstra:貪心策略,每次選距離起點最近的未訪問點,更新鄰接點。

  • Floyd:動態規劃,A[k][i][j] = min(A[k-1][i][j], A[k-1][i][k] + A[k-1][k][j])

3.?時間復雜度
算法時間復雜度空間復雜度
BFSO(|V| + |E|)O(|V|)
Dijkstra(基礎)O(|V|2)O(|V|)
FloydO(|V|3)O(|VBFS

BFS

問題1:“為什么DFS不適合?權值不等時為何BFS失效?”(為何BFS適合解決??無權圖單源最短路徑??(每條邊權值為1,路徑長度=邊數))

??答案??:DFS可能探索非最短路徑;權值不等時路徑長度≠邊數,需Dijkstra算法。

問題2:代碼中的??三個核心數組??

  • d[]:記錄源點到各頂點的最短距離(初始化為)。
  • visited[]:標記頂點是否被訪問(初始化為false)。
  • path[]:記錄路徑前驅(初始化為-1)。
if (!visited[w]) {d[w] = d[u] + 1;  // 更新距離path[w] = u;       // 記錄前驅visited[w] = TRUE;EnQueue(Q, w);
}

問題3:“BFS與Dijkstra在權值為1時的關系?”

??答案??:BFS是Dijkstra在無權圖中的特例(優先級隊列退化為普通隊列)。

問題4:若圖非連通,部分頂點d[]保持∞,如何處理?

??方法??:遍歷所有頂點,對未訪問頂點再次調用BFS。

問題5:

“如何輸出完整路徑?”→ 用path[]回溯。

“若需記錄多條最短路徑?”→ 將path[]改為存儲鏈表。

基礎代碼實現:

import java.util.*;public class BFSShortestPath {private List<List<Integer>> graph; // 鄰接表存儲圖結構private int[] dist;                // 存儲源點到各頂點的最短距離private int[] parent;              // 存儲最短路徑的前驅節點private boolean[] visited;         // 訪問標記數組public void bfsMinDistance(int start) {// 使用graph的大小(包含索引0)int n = graph.size();dist = new int[n];parent = new int[n];visited = new boolean[n];// 初始化Arrays.fill(dist, Integer.MAX_VALUE);Arrays.fill(parent, -1);Queue<Integer> queue = new LinkedList<>();dist[start] = 0;visited[start] = true;queue.offer(start);while (!queue.isEmpty()) {int u = queue.poll();// 安全遍歷(防止空指針)if (graph.get(u) != null) {for (int w : graph.get(u)) {if (!visited[w]) {dist[w] = dist[u] + 1;parent[w] = u;visited[w] = true;queue.offer(w);}}}}}// 重建最短路徑public List<Integer> getPath(int target) {LinkedList<Integer> path = new LinkedList<>();for (int v = target; v != -1; v = parent[v]) {path.addFirst(v);}return path;}// 圖初始化public static void main(String[] args) {BFSShortestPath bfs = new BFSShortestPath();int vertexCount = 8; // 頂點數量int arraySize = vertexCount + 1; // 數組大小=頂點數+1// 創建包含索引0~8的圖(共9個位置)bfs.graph = new ArrayList<>(arraySize);for (int i = 0; i < arraySize; i++) {bfs.graph.add(new ArrayList<>());}// 添加圖的邊(索引1~8對應頂點1~8)bfs.graph.get(1).add(2); bfs.graph.get(2).add(1);bfs.graph.get(1).add(5); bfs.graph.get(5).add(1);bfs.graph.get(2).add(6); bfs.graph.get(6).add(2);bfs.graph.get(6).add(3); bfs.graph.get(3).add(6);bfs.graph.get(6).add(7); bfs.graph.get(7).add(6);bfs.graph.get(3).add(4); bfs.graph.get(4).add(3);bfs.graph.get(7).add(8); bfs.graph.get(8).add(7);bfs.bfsMinDistance(2); // 從頂點2(索引2)開始System.out.println("從2到8的最短距離: " + bfs.dist[8]);System.out.println("路徑: " + bfs.getPath(8)); // [2, 6, 7, 8]}
}

練習:1091. 二進制矩陣中的最短路徑 - 力扣(LeetCode)https://leetcode.cn/problems/shortest-path-in-binary-matrix/

class Solution {//移動數組int[][] dir=new int[][]{{-1,1},{0,1},{1,1},{-1,0},{1,0},{-1,-1},{0,-1},{1,-1}};public int shortestPathBinaryMatrix(int[][] grid) {//起始判斷if(grid[0][0]==1)return -1;int n=grid.length;//路徑距離int[][] dist=new int[n][n];//路徑節點int[][][] path=new int[n][n][2];//標記數組boolean[][] visited=new boolean[n][n];//初始化for(int i=0;i<n;i++){Arrays.fill(dist[i],Integer.MAX_VALUE);}Deque<int[]> deque=new ArrayDeque<>();//起始節點入隊deque.add(new int[]{0,0});visited[0][0]=true;dist[0][0]=1;path[0][0]=new int[]{-1,-1};while(!deque.isEmpty()){//出隊int[] node=deque.poll();//處理int x=node[0],y=node[1];if(x==n-1 && y==n-1){PrintPath(grid,path);return dist[x][y];}//鄰居入隊for(int i=0;i<8;i++){int newx=x+dir[i][0];int newy=y+dir[i][1];//邊界if(newx<0 || newx>=n || newy<0 || newy>=n)continue;//符合條件的鄰居if(!visited[newx][newy] && grid[newx][newy]==0){deque.add(new int[]{newx,newy});dist[newx][newy]=dist[x][y]+1;visited[newx][newy]=true;path[newx][newy]=new int[]{x,y};}}}return -1;}//輸出路徑public void PrintPath(int[][] grid,int[][][] path){int n=grid.length;int x=path[n-1][n-1][0];int y=path[n-1][n-1][1];System.out.print("("+ (n-1) +", "+(n-1) +")"+"<-");while(x!=-1 && y!=-1){System.out.print("("+ x +", "+y +")"+"<-");int[] node=path[x][y];x=node[0];y=node[1];}}}

為了更加直觀,添加了路徑打印

Dijkstra

重點理解更新過程與負權圖的局限性

1. 初始化階段

易錯:漏掉不可達頂點的初始化

正確:dist[V] = ∞,?path[V] = -1

難點:源點到自身(dist[V0]=0)和直接鄰接點的處理

2. 頂點選擇(每輪循環)
  • 易錯:選擇已確定頂點(final[i]=true的頂點不應再選)

  • 難點:當存在多個相同dist值時,選擇任意一個均可(需說明)

3. 更新鄰接點
  • 核心公式
    若 dist[i] + arcs[i][j] < dist[j],則更新 dist[j] 和 path[j]

  • 易錯

    • 未檢查final[j]==false

    • 忽略不可達頂點(∞參與計算)

  • 難點:理解"松弛操作"本質(通過新頂點縮短路徑)

4. 負權圖問題

負權值的破壞性??:

當存在負權邊(如 V?→V? 權值為 -5),已標記為?final=true的節點可能被??更短路徑推翻??

算法路徑:V?→V?(dist=7)

實際更優路徑:V?→V?→V?(10 + (-5) = 5)

但 V? 被提前標記為?final=true,導致無法更新:

import java.util.Arrays;public class DijkstraAlgorithm {// 使用鄰接矩陣存儲圖(易錯點1:需確保矩陣對稱性)private static final int INF = Integer.MAX_VALUE; // 表示無窮大public static void dijkstra(int[][] graph, int source) {int n = graph.length;int[] dist = new int[n];       // 最短路徑長度數組int[] path = new int[n];       // 前驅節點數組(易錯點2:需記錄路徑)boolean[] finalSet = new boolean[n]; // 已確定最短路徑的頂點集// 初始化三個核心數組(關鍵點1)Arrays.fill(dist, INF);Arrays.fill(path, -1);dist[source] = 0;  // 源點到自身距離為0// 主循環:執行n-1輪(關鍵點2)for (int count = 0; count < n - 1; count++) {// 步驟1:找當前dist最小的未確定節點int u = -1;int minDist = INF;for (int v = 0; v < n; v++) {if (!finalSet[v] && dist[v] <= minDist) { // 易錯點3:注意等號minDist = dist[v];u = v;}}if (u == -1) break; // 所有頂點已處理完finalSet[u] = true; // 標記為已確定// 步驟2:更新鄰接點(關鍵點3)for (int v = 0; v < n; v++) {// 易錯點4:需同時檢查4個條件if (!finalSet[v] &&graph[u][v] != INF &&dist[u] != INF &&dist[u] + graph[u][v] < dist[v]) {dist[v] = dist[u] + graph[u][v];path[v] = u; // 記錄前驅節點}}}// 打印結果(示例)printSolution(dist, path, source);}private static void printSolution(int[] dist, int[] path, int source) {System.out.println("源點: " + source);System.out.println("頂點\t距離\t路徑");for (int i = 0; i < dist.length; i++) {System.out.printf("%d\t%d\t", i, dist[i]);printPath(path, i);System.out.println();}}// 遞歸打印路徑(關鍵點4)private static void printPath(int[] path, int current) {if (current == -1) return;printPath(path, path[current]);System.out.print(current + " ");}public static void main(String[] args) {int[][] graph = {{0, 10, INF, INF, 5},{INF, 0, 1, INF, 2},{INF, INF, 0, 4, INF},{7, INF, 6, 0, INF},{INF, 3, 9, 2, 0}};dijkstra(graph, 0);}
}

練習:

743. 網絡延遲時間 - 力扣(LeetCode)https://leetcode.cn/problems/network-delay-time/description/?utm_source=LCUS&utm_medium=ip_redirect&utm_campaign=transfer2china

class Solution {public int networkDelayTime(int[][] times, int n, int k) {//異常判斷if(times.length==0 || times==null || k>n)return -1;//構建圖int[][] g=new int[n][n];final int INF =Integer.MAX_VALUE/2;for(int i=0;i<n;i++){Arrays.fill(g[i],INF);//g[x][y]==INF 無邊}for(int[] t:times){int x=t[0]-1,y=t[1]-1;g[x][y]=t[2];}//初始化boolean[] used=new boolean[n];//used[x]==true 存在x到源點最短距離int[] dist=new int[n];//dist[x] 源點到x最短距離Arrays.fill(dist,INF);dist[k-1]=0;int[] path=new int[n];Arrays.fill(path,-1);//計算其他n-1個節點的distfor(int i=0;i<n-1;i++){//找最短距離的鄰居(第一輪是初始節點)int u=-1;for(int v=0;v<n;v++){if(!used[v] && (u==-1 || dist[v]<dist[u]))u=v;}used[u]=true;//更新distfor(int j=0;j<n;j++){if(!used[j] && g[u][j]!=INF & dist[u]!=INF && dist[u]+g[u][j]<dist[j]){dist[j]=dist[u]+g[u][j];path[j]=u;}}}//直觀演示// Print(path,dist,k-1);int ans=Arrays.stream(dist).max().getAsInt();return ans==INF? -1:ans;}public void Print(int[] path,int[] dist,int source){System.out.println("源點: " + source);System.out.println("頂點\t距離\t路徑");for (int i = 0; i < dist.length; i++) {System.out.printf("%d\t%d\t", i, dist[i]);printPath(path, i);System.out.println();}}public void printPath(int[] path,int curr){if(curr==-1)return ;printPath(path,path[curr]);System.out.print(curr+"->");} 
}

Floyd

1、算法思想(建立直觀理解)

通過逐步增加中轉點,動態更新所有頂點之間的最短路徑

初始化:假設一張地圖有多個城市(頂點),先記錄所有城市間直達的路徑長度(沒有直達記為無窮大)。

動態中轉:逐個考慮每個城市作為中轉站(比如先允許通過城市A中轉,再允許通過A和B中轉,以此類推)。

檢查優化:對于每一對城市(比如X到Y),檢查如果從X先到中轉站K,再從K到Y,總距離是否比當前已知的X到Y路徑更短。

如果更短:更新X到Y的最短路徑為?X->K->Y?的路徑,并記錄這個中轉站K;否則:保持原有路徑。

完成:當所有城市都作為中轉站被考慮過后,最終得到任意兩城市間的最短路徑。


想象你要為所有城市之間規劃最短快遞路線。Floyd的做法是:

  1. 第一輪:只允許快遞從起點直達終點。

  2. 第二輪:允許快遞在城市A中轉一次(比如?上海->A->廣州?可能比?上海->廣州?更快)。

  3. 第三輪:允許在城市A和B中轉,依此類推...
    直到所有城市都可作為中轉站,最終得到全局最優路線。

關鍵

  • 能處理任意兩點間的最短路徑(單源/多源都適用)。

  • 支持帶權圖(包括負權邊,但不能有負權環路)。

  • 本質是動態規劃,通過三重循環實現(外層控制中轉點)。

可以去看看這個博主的講解,很直觀:

圖-最短路徑-Floyd(弗洛伊德)算法_嗶哩嗶哩_bilibilihttps://www.bilibili.com/video/BV19k4y1Q7Gj?spm_id_from=333.788.videopod.sections&vd_source=4b89f462036a892baf8931104a1f36b1

注意:視頻的path數組初始化和本文所采用的初始化方式不同(本文參考王道教材),但大體算法思路一致,可以參考。
算法演示:

Floyd-Warshall All-Pairs Shortest Pathhttps://www.cs.usfca.edu/~galles/visualization/Floyd.html

2、代碼實現·明確易錯點和難點

距離矩陣?dist

定義與初始化

int[][] dist = new int[n][n];
  • 含義dist[i][j]?表示從頂點?i?到頂點?j?的當前已知最短路徑長度

  • 初始化

    • 如果?i == j,則?dist[i][j] = 0(頂點到自身距離為0)

    • 如果存在邊?i→j,則?dist[i][j] = weight(i, j)(邊的權重)

    • 如果不存在邊?i→j,則?dist[i][j] = Integer.MAX_VALUE(表示無窮大)

更新原理(動態規劃核心)

if (dist[i][k] + dist[k][j] < dist[i][j]) {dist[i][j] = dist[i][k] + dist[k][j];
}
  • 物理意義:檢查通過中間點?k?的路徑是否比當前路徑更短

  • 狀態轉移方程

    dist^{(k)}[i][j] = \min \left( dist^{(k-1)}[i][j],\ dist^{(k-1)}[i][k] + dist^{(k-1)}[k][j] \right)

  • 動態規劃思想:當考慮允許通過頂點?k?中轉時,更新所有?i→j?的路徑

路徑矩陣?path

定義與初始化

int[][] path = new int[n][n];
  • 含義path[i][j]?記錄從?i?到?j?的最短路徑上的第一個中轉點

  • 初始化

    • path[i][j] = -1?表示沒有中轉點(直接相連)

    • path[i][j] = k?表示路徑?i→j?需要經過中轉點?k

更新原理

if (dist[i][k] + dist[k][j] < dist[i][j]) {path[i][j] = k;? // 記錄中轉點
}
  • 關鍵點:當發現通過?k?中轉的路徑更短時,記錄這個關鍵中轉點

  • 路徑重建原理:路徑信息是遞歸存儲的:

    • 要獲取?i→j?的完整路徑,需要:

      1. 找到?path[i][j] = k

      2. 遞歸查找?i→k?的路徑

      3. 遞歸查找?k→j?的路徑

路徑重建算法

void printPath(int i, int j, int[][] path) {if (path[i][j] == -1) {System.out.print(j + " "); // 直接連接return;}int k = path[i][j];printPath(i, k, path); // 前半段路徑printPath(k, j, path); // 后半段路徑
}
import java.util.Arrays;public class FloydAlgorithm {public static void floyd(int[][] graph) {int n = graph.length;// 初始化距離矩陣(存儲最短路徑長度)int[][] dist = new int[n][n];// 初始化路徑矩陣(存儲中轉點)int[][] path = new int[n][n];// 易錯點1:正確初始化矩陣for (int i = 0; i < n; i++) {for (int j = 0; j < n; j++) {dist[i][j] = graph[i][j];  // 復制原始圖// 難點1:路徑矩陣初始化// -1表示沒有中轉點(直接相連)path[i][j] = -1;}}// 核心三重循環// 易錯點2:循環順序必須是k->i->jfor (int k = 0; k < n; k++) {          // 考慮每個頂點作為中轉點for (int i = 0; i < n; i++) {      // 遍歷所有起點// 難點2:跳過無效路徑提升性能if (dist[i][k] == Integer.MAX_VALUE) continue;for (int j = 0; j < n; j++) {  // 遍歷所有終點// 跳過不可達路徑if (dist[k][j] == Integer.MAX_VALUE) continue;// 發現更短路徑// 易錯點3:避免整數溢出long newDist = (long) dist[i][k] + dist[k][j];if (newDist < dist[i][j]) {dist[i][j] = (int) newDist;path[i][j] = k;  // 記錄中轉點}}}}// 打印最終結果printSolution(dist, path);}// 遞歸打印路徑(難點3:路徑回溯)private static void printPath(int[][] path, int i, int j) {if (path[i][j] == -1) {System.out.print(" → " + j);return;}printPath(path, i, path[i][j]);  // 前半段路徑printPath(path, path[i][j], j);  // 后半段路徑}private static void printSolution(int[][] dist, int[][] path) {int n = dist.length;System.out.println("距離矩陣:");for (int[] row : dist) {System.out.println(Arrays.toString(row));}System.out.println("\n路徑矩陣:");for (int[] row : path) {System.out.println(Arrays.toString(row));}System.out.println("\n詳細路徑:");for (int i = 0; i < n; i++) {for (int j = 0; j < n; j++) {if (i != j && dist[i][j] != Integer.MAX_VALUE) {System.out.print(i + "→" + j + ": " + i);printPath(path, i, j);System.out.println(" (長度: " + dist[i][j] + ")");}}}}public static void main(String[] args) {// 示例圖(使用MAX_VALUE表示無窮大)int INF = Integer.MAX_VALUE;int[][] graph = {{0,   6,  13},{10,  0,   4},{5, INF,   0}};floyd(graph);}
}
3、練習1334. 閾值距離內鄰居最少的城市 - 力扣(LeetCode)https://leetcode.cn/problems/find-the-city-with-the-smallest-number-of-neighbors-at-a-threshold-distance/?utm_source=LCUS&utm_medium=ip_redirect&utm_campaign=transfer2china
class Solution {public int findTheCity(int n, int[][] edges, int distanceThreshold) {//極端條件if(edges==null || edges.length==0)return 0;int INF=Integer.MAX_VALUE/2;//構建圖int[][] g=new int[n][n];for(int i=0;i<n;i++){Arrays.fill(g[i],INF);}for(int[] e:edges){int x=e[0],y=e[1];g[x][y]=e[2];g[y][x]=e[2];//無向圖}//距離數組int[][] dist=new int[n][n];//路徑數組·存中轉點int[][] path=new int[n][n];//初始化for(int i=0;i<n;i++){Arrays.fill(path[i],-1);}for(int i=0;i<n;i++){for(int j=0;j<n;j++){if(i==j)dist[i][j]=0;else dist[i][j]=g[i][j];}}//floy流程for(int i=0;i<n;i++){//中轉點for(int s=0;s<n;s++){//源點if(dist[s][i]==INF)continue;//源點無法到達中轉點for(int e=0;e<n;e++){//終點if(dist[i][e]==INF)continue;//中轉點無法到達終點//更新dist與path·發現通過中轉點有更小的路徑就更新int curr=dist[s][i]+dist[i][e];if(curr<dist[s][e]){dist[s][e]=curr;path[s][e]=i;}}}}//結果int max_index=-1;int min_city=INF/2;for(int i=0;i<n;i++){int count=0;for(int j=0;j<n;j++){if(dist[i][j]<=distanceThreshold)count++;}if(count<=min_city){min_city=count;max_index=i;//i是遞增的}}// printSolution(dist,path);//可視化return max_index;}// 遞歸打印路徑private static void printPath(int[][] path, int i, int j) {if (path[i][j] == -1) {System.out.print(" → " + j);return;}printPath(path, i, path[i][j]);  // 前半段路徑printPath(path, path[i][j], j);  // 后半段路徑}private static void printSolution(int[][] dist, int[][] path) {int n = dist.length;System.out.println("距離矩陣:");for (int[] row : dist) {System.out.println(Arrays.toString(row));}System.out.println("\n路徑矩陣:");for (int[] row : path) {System.out.println(Arrays.toString(row));}System.out.println("\n詳細路徑:");for (int i = 0; i < n; i++) {for (int j = 0; j < n; j++) {if (i != j && dist[i][j] != Integer.MAX_VALUE) {System.out.print(i + "→" + j + ": " + i);printPath(path, i, j);System.out.println(" (長度: " + dist[i][j] + ")");}}}}
}
4、細節處理

無窮大(∞)的處理

代碼中需用足夠大的數表示∞,注意避免運算溢出。

Floyd與Dijkstra的區別?

Floyd:解決所有頂點對最短路徑,支持負權邊,時間復雜度O(|V|3)。

Dijkstra:解決單源最短路徑,不支持負權邊,時間復雜度O(|V|2)。

為何Floyd能處理負權邊,但不支持負權回路?

負權邊可通過中轉優化路徑。

負權回路導致路徑長度無限降低,無確定最短路徑。

如何用Floyd檢測負權回路?

檢查最終距離矩陣A對角線:若存在A[i][i] < 0,則頂點i在負權回路中。

空間復雜度優化?

使用單個矩陣滾動更新(無需保留所有階段矩陣),空間復雜度O(|V|2)。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/919053.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/919053.shtml
英文地址,請注明出處:http://en.pswp.cn/news/919053.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

設計模式-策略模式 Java

模式概述 策略模式是一種行為型設計模式&#xff0c;它通過定義一系列可互換的算法&#xff0c;并將每個算法封裝成獨立類&#xff0c;使客戶端能夠根據需要動態切換算法 簡單代碼示例 // 1. 抽象策略接口 interface PaymentStrategy {void pay(int amount); }// 2. 具體策略實…

【機器學習深度學習】客觀評估訓練程度

目錄 前言 一、什么是客觀評估&#xff1f; 二、客觀評估的兩大核心方法 1. 判別式評測&#xff08;Discriminative Evaluation&#xff09; 2. 生成式評測&#xff08;Generative Evaluation&#xff09; 三、為什么客觀評估成本更高&#xff1f; 1.訓練目標收緊 2.訓…

Linux軟件編程:線程間通信

目錄 一、線程間通信基礎 1. 概念 2. 通信基礎&#xff1a;共享空間 二、互斥鎖&#xff08;Mutex&#xff09; 1. 概念 2. 使用流程 3. 函數接口 三、死鎖 1. 概念 2. 死鎖產生的 4 個必要條件 3. 避免死鎖的方法 四、信號量&#xff08;Semaphore&#xff09; 1…

【學習筆記】JVM GC回收機制

1.三種基本的垃圾回收算法 1>標記-清除法 ①先將從樹根開始&#xff0c;可以到達的對象標記為可達&#xff08;JVM中的對象們存儲為一顆樹&#xff09; ②將沒有標記的對象清除掉 缺點&#xff1a;會產生大量內存碎片 2>復制算法&#xff08;新生代&#xff09; ①先將a區…

軟件的終極:為70億人編寫70億個不同的軟件

這是個腦洞大開的想法。昨天晚上&#xff0c;我在用Claude code幫我寫一個小工具&#xff0c;用來管理我本地那些亂七八糟的文檔。寫著寫著&#xff0c;突然意識到一個問題&#xff1a;這個工具完全是按照我的工作習慣定制的——我喜歡用Markdown&#xff0c;習慣把TODO放在文件…

LakeHouse--湖倉一體架構

大家可能發現了,近些年湖倉一體數據架構被提及的頻率越來越高。各家大廠也有湖倉一體架構的實踐,也有很多公開分享。 那什么是湖倉一體?為什么出現了湖倉一體架構,換言之,它解決了以前數據倉庫、數據湖+數倉兩層架構所不能解決的什么問題? 本文會從數倉、數據湖依次介紹…

基于FPGA的實時圖像處理系統(1)——SDRAM回環測試

SDRAM回環設計 文章目錄SDRAM回環設計一、SDRAM簡介1、引腳2、內部結構框圖3、操作指令二、系統設計三、實現流程1、SDRAM接口2、FIFO設置3、內部SDRAM的控制模塊4、其他四、實現效果五、總結六、代碼1、top2、sdram_top3、sdram_ctrl一、SDRAM簡介 SDRAM英文全稱“Synchronou…

一鍵檢測接口是否存活:用 Python/Shell 寫個輕量級監控腳本

網羅開發&#xff08;小紅書、快手、視頻號同名&#xff09;大家好&#xff0c;我是 展菲&#xff0c;目前在上市企業從事人工智能項目研發管理工作&#xff0c;平時熱衷于分享各種編程領域的軟硬技能知識以及前沿技術&#xff0c;包括iOS、前端、Harmony OS、Java、Python等方…

優秀工具包-Hutool工具詳解

優秀工具包-Hutool工具詳解 課程概述 Hutool簡介 定位&#xff1a; 小而全的Java工具庫&#xff0c;簡化開發流程。對文件、流、加密解密、轉碼、正則、線程、XML等JDK方法進行封裝。 核心優勢&#xff1a;零依賴、高性能、中文網頁完善。 應用場景&#xff1a;Web開發、數…

《深度解構:構建瀏覽器端Redis控制臺的WebSocket協議核心技術》

Redis作為高性能的內存數據庫,其原生客戶端多依賴命令行或桌面應用,而瀏覽器端控制臺的缺失,成為制約Web化管理的關鍵瓶頸,WebSocket協議的出現,打破了HTTP協議單向通信的局限,為瀏覽器與Redis服務之間建立持久、雙向的實時連接提供了可能。本文將從協議本質、交互邏輯、…

Pushgateway安裝和部署,以及對應Prometheus調整

目錄Pushgateway簡介安裝驗證Prometheus的配置&#xff1a;其它命令Pushgateway簡介 Pushgateway 是 Prometheus 生態系統中的一個組件。主要特點是推送而非拉取&#xff1a;Prometheus 默認采用拉取&#xff08;pull&#xff09;模式收集指標&#xff0c;但 Pushgateway 允許…

JAVA面試匯總(四)JVM(一)

久違的重新寫了一篇面試匯總的&#xff0c;關于JVM的一篇&#xff0c;一共三篇&#xff0c;今天寫了第一篇&#xff0c;繼續重新學習&#xff0c;重新卷起來&#xff0c;come on baby 1.什么情況下會觸發類的初始化&#xff1f; &#xff08;1&#xff09;首先是類未被初始化時…

Agent中的memory

rag系列文章目錄 文章目錄rag系列文章目錄前言一、Memory機制作用二、memory分類三、langgraph實踐總結前言 眾所周知&#xff0c;大模型是無狀態的。但是基于大模型的agent一般是有狀態的&#xff0c;也就是它有記憶功能。在AI Agent框架中&#xff0c;Memory機制是核心組件之…

AI與IT從業者的未來:替代焦慮還是協作革命?

??引言&#xff1a;技術滲透與核心命題??2025年&#xff0c;人工智能技術已從實驗室走向產業核心。國務院《關于深入實施“人工智能”行動的意見》推動AI在醫療、制造、金融等領域的規模化落地&#xff0c;全球AI應用用戶規模突破2.3億&#xff0c;生成式AI工具滲透率達16.…

手機版碰一碰發視頻系統批量剪輯功能開發,支持OEM貼牌

引言在當今短視頻盛行的時代&#xff0c;視頻內容的快速生產與分享變得愈發重要。手機版碰一碰發視頻系統&#xff0c;借助 NFC 等近場通信技術&#xff0c;實現了便捷的數據交互與視頻分享&#xff0c;而在此基礎上集成的批量剪輯功能&#xff0c;更是為內容創作者和商家帶來了…

Spring AMQP如何通過配置文件避免硬編碼實現解耦

在使用Spring AMQP基于注解聲明監聽者時&#xff0c;可通過抽取常量來避免硬編碼&#xff1a;RabbitListener(bindings QueueBinding(exchange Exchange(MQConstant.USER_EXCHANGE),value Queue(MQConstant.USER_QUEUE),key MQConstant.USER_REDIS_BINDING))public void de…

解決zabbix圖片中文亂碼

要把 Zabbix 前端字體替換為 simkai.ttf&#xff08;楷體&#xff0c;解決亂碼常用&#xff09;&#xff0c;按以下步驟操作&#xff1a;1. 確認 simkai.ttf 路徑 先找到系統里 simkai.ttf 字體文件&#xff0c;若沒有&#xff0c;可從 Windows 系統&#xff08;C:\Windows\Fon…

實例分割-動手學計算機視覺13

介紹 實例分割(instance segmentation)的目的是從圖像中分割出每個目標實例的掩模(mask)。與語義分割相比&#xff0c;實例分割不但要區分不同的類別&#xff0c;還要區分出同一種類別下的不同目標實例。如圖13-1所示 語義分割的結果中&#xff0c;不同的羊對應的標簽是一樣的…

水環境遙感分析!R語言編程+多源遙感數據預處理;水體指數計算、水深回歸分析、水溫SVM預測、水質神經網絡建模及科研級可視化制圖

系統性地整合R語言編程、遙感數據處理及機器學習建模&#xff0c;涵蓋水線提取&#xff08;水體指數與閾值法&#xff09;、水深反演&#xff08;多元回歸&#xff09;、水溫預測&#xff08;支持向量機&#xff09;、水質評估&#xff08;神經網絡&#xff09;等核心內容&…

微信公眾號/小程序百萬級OpenID自動化獲取工具

摘要 本報告詳細闡述了微信用戶列表數據獲取與處理工具的設計思路,包括分頁處理機制、頻率控制策略、斷點續傳功能和分布式存儲方案。針對微信API調用限制和用戶數據規模特點,該工具旨在高效、安全地獲取和存儲微信用戶列表數據,同時嚴格遵守微信API調用頻率限制,確保系統…