圖論算法精解(Java 實現):從基礎到高頻面試題

一、圖的基礎表示方法

1.1 鄰接矩陣(Adjacency Matrix)

鄰接矩陣是表示圖的一種直觀方式,它使用一個二維數組來存儲節點之間的連接關系。對于一個有 n 個節點的圖,鄰接矩陣是一個 n×n 的矩陣,其中 matrix [i][j] 表示節點 i 到節點 j 的邊的權重。

class GraphMatrix {private int[][] matrix;private int vertexCount;public GraphMatrix(int n) {vertexCount = n;matrix = new int[n][n];}public void addEdge(int from, int to, int weight) {matrix[from][to] = weight;matrix[to][from] = weight; // 無向圖需雙向設置}// 獲取兩個節點之間的邊權重public int getEdgeWeight(int from, int to) {return matrix[from][to];}// 獲取節點的所有鄰接節點public List<Integer> getNeighbors(int node) {List<Integer> neighbors = new ArrayList<>();for (int i = 0; i < vertexCount; i++) {if (matrix[node][i] != 0) {neighbors.add(i);}}return neighbors;}
}

1.2 鄰接表(Adjacency List)

鄰接表是表示稀疏圖的更高效方式,它使用一個列表數組,其中每個元素對應一個節點的所有鄰接節點及其邊的權重。

class GraphList {private List<List<int[]>> adjList; // [鄰接節點, 權重]public GraphList(int n) {adjList = new ArrayList<>();for (int i = 0; i < n; i++) {adjList.add(new ArrayList<>());}}public void addEdge(int from, int to, int weight) {adjList.get(from).add(new int[]{to, weight});adjList.get(to).add(new int[]{from, weight}); // 無向圖需添加反向邊}// 獲取節點的所有鄰接邊public List<int[]> getEdges(int node) {return adjList.get(node);}// 獲取圖的鄰接表表示public List<List<int[]>> getAdjList() {return adjList;}
}

1.3 兩種表示法的對比

特性鄰接矩陣鄰接表
空間復雜度O(V2)O(V + E)
查詢邊是否存在O(1)O(k)
遍歷鄰接節點O(V)O(1)~O(k)
適用場景稠密圖稀疏圖
  • 鄰接矩陣的優勢在于快速查詢任意兩個節點之間的邊,但空間效率較低,適合節點數量較少的稠密圖。
  • 鄰接表的優勢在于節省空間,適合節點數量較多的稀疏圖,但查詢特定邊的效率較低。

二、基礎圖遍歷算法

2.1 深度優先搜索(DFS)

深度優先搜索是一種遞歸遍歷圖的方法,它沿著一條路徑盡可能深地訪問節點,直到無法繼續,然后回溯。

// 遞歸實現DFS
void dfs(List<List<Integer>> graph, int start) {boolean[] visited = new boolean[graph.size()];dfsHelper(graph, start, visited);
}private void dfsHelper(List<List<Integer>> graph, int node, boolean[] visited) {visited[node] = true;System.out.print(node + " ");for (int neighbor : graph.get(node)) {if (!visited[neighbor]) {dfsHelper(graph, neighbor, visited);}}
}// 迭代實現DFS(使用棧)
void dfsIterative(List<List<Integer>> graph, int start) {boolean[] visited = new boolean[graph.size()];Stack<Integer> stack = new Stack<>();stack.push(start);while (!stack.isEmpty()) {int node = stack.pop();if (!visited[node]) {visited[node] = true;System.out.print(node + " ");// 注意:棧是后進先出,所以先將鄰接節點逆序壓入棧List<Integer> neighbors = graph.get(node);for (int i = neighbors.size() - 1; i >= 0; i--) {if (!visited[neighbors.get(i)]) {stack.push(neighbors.get(i));}}}}
}

2.2 廣度優先搜索(BFS)

廣度優先搜索是一種逐層遍歷圖的方法,它使用隊列來保存待訪問的節點,先訪問距離起始節點最近的所有節點,然后逐層向外擴展。

void bfs(List<List<Integer>> graph, int start) {boolean[] visited = new boolean[graph.size()];Queue<Integer> queue = new LinkedList<>();queue.offer(start);visited[start] = true;while (!queue.isEmpty()) {int node = queue.poll();System.out.print(node + " ");for (int neighbor : graph.get(node)) {if (!visited[neighbor]) {visited[neighbor] = true;queue.offer(neighbor);}}}
}// BFS的應用:計算最短路徑(無權圖)
int[] shortestPath(List<List<Integer>> graph, int start) {int n = graph.size();int[] dist = new int[n];Arrays.fill(dist, -1); // -1表示不可達Queue<Integer> queue = new LinkedList<>();queue.offer(start);dist[start] = 0;while (!queue.isEmpty()) {int node = queue.poll();for (int neighbor : graph.get(node)) {if (dist[neighbor] == -1) {dist[neighbor] = dist[node] + 1;queue.offer(neighbor);}}}return dist;
}

三、最短路徑算法

3.1 Dijkstra 算法(單源最短路徑)

Dijkstra 算法用于計算帶權有向圖或無向圖中,從單個源節點到所有其他節點的最短路徑,要求所有邊的權重非負。

int[] dijkstra(List<List<int[]>> graph, int start) {int n = graph.size();int[] dist = new int[n];Arrays.fill(dist, Integer.MAX_VALUE);dist[start] = 0;PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[1] - b[1]);pq.offer(new int[]{start, 0});while (!pq.isEmpty()) {int[] curr = pq.poll();int u = curr[0], d = curr[1];// 如果當前距離大于已記錄的最短距離,跳過if (d > dist[u]) continue;// 遍歷所有鄰接邊for (int[] edge : graph.get(u)) {int v = edge[0], w = edge[1];if (dist[v] > dist[u] + w) {dist[v] = dist[u] + w;pq.offer(new int[]{v, dist[v]});}}}return dist;
}// 打印最短路徑
List<Integer> getShortestPath(int[] prev, int target) {List<Integer> path = new ArrayList<>();for (int at = target; at != -1; at = prev[at]) {path.add(at);}Collections.reverse(path);return path;
}

3.2 Floyd-Warshall 算法(多源最短路徑)

Floyd-Warshall 算法用于計算帶權圖中所有節點對之間的最短路徑,允許邊的權重為負,但不能包含負權環。

void floydWarshall(int[][] graph) {int n = graph.length;int[][] dist = new int[n][n];// 初始化距離矩陣,將不可達的距離設為無窮大for (int i = 0; i < n; i++) {for (int j = 0; j < n; j++) {if (i == j) {dist[i][j] = 0;} else if (graph[i][j] != 0) {dist[i][j] = graph[i][j];} else {dist[i][j] = Integer.MAX_VALUE;}}}// 三重循環更新距離for (int k = 0; k < n; k++) {for (int i = 0; i < n; i++) {for (int j = 0; j < n; j++) {if (dist[i][k] != Integer.MAX_VALUE && dist[k][j] != Integer.MAX_VALUE) {dist[i][j] = Math.min(dist[i][j], dist[i][k] + dist[k][j]);}}}}// 檢查負權環for (int i = 0; i < n; i++) {if (dist[i][i] < 0) {System.out.println("圖包含負權環");return;}}// 打印最短路徑矩陣for (int i = 0; i < n; i++) {for (int j = 0; j < n; j++) {System.out.print((dist[i][j] == Integer.MAX_VALUE ? "INF" : dist[i][j]) + "\t");}System.out.println();}
}

四、最小生成樹算法

4.1 Prim 算法(鄰接矩陣版)

Prim 算法是一種貪心算法,用于在帶權無向圖中找到最小生成樹(MST)。

int primMST(int[][] graph) {int n = graph.length;int[] key = new int[n];       // 存儲最小邊權值boolean[] mstSet = new boolean[n]; // 標記節點是否已加入MSTint[] parent = new int[n];    // 存儲每個節點的父節點Arrays.fill(key, Integer.MAX_VALUE);key[0] = 0;  // 從節點0開始parent[0] = -1; // 根節點的父節點為-1int totalWeight = 0;for (int count = 0; count < n; count++) {// 選擇key值最小且未被加入MST的節點int u = -1;for (int i = 0; i < n; i++) {if (!mstSet[i] && (u == -1 || key[i] < key[u])) {u = i;}}mstSet[u] = true;totalWeight += key[u];// 更新鄰接頂點的key值和parentfor (int v = 0; v < n; v++) {if (graph[u][v] != 0 && !mstSet[v] && graph[u][v] < key[v]) {key[v] = graph[u][v];parent[v] = u;}}}// 打印MST的邊System.out.println("Edge \tWeight");for (int i = 1; i < n; i++) {System.out.println(parent[i] + " - " + i + "\t" + graph[i][parent[i]]);}return totalWeight;
}

4.2 Kruskal 算法(并查集優化)

Kruskal 算法也是一種貪心算法,用于找到帶權無向圖的最小生成樹。

class Kruskal {class UnionFind {private int[] parent;private int[] rank;public UnionFind(int size) {parent = new int[size];rank = new int[size];for (int i = 0; i < size; i++) {parent[i] = i;rank[i] = 1;}}// 路徑壓縮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[rootY] = rootX;} else if (rank[rootX] < rank[rootY]) {parent[rootX] = rootY;} else {parent[rootY] = rootX;rank[rootX]++;}}}public int kruskalMST(int[][] edges, int n) {// 按邊權排序Arrays.sort(edges, (a, b) -> a[2] - b[2]);UnionFind uf = new UnionFind(n);int mstWeight = 0;int edgeCount = 0;for (int[] edge : edges) {int u = edge[0];int v = edge[1];int w = edge[2];// 檢查是否會形成環if (uf.find(u) != uf.find(v)) {uf.union(u, v);mstWeight += w;edgeCount++;// MST的邊數為n-1時結束if (edgeCount == n - 1) break;}}return mstWeight;}
}

五、高頻面試題解析

5.1 課程表 II(LeetCode 210)拓撲排序

題目描述:給定課程總數和先決條件,返回一個有效的課程學習順序。如果不可能,則返回空數組。

public int[] findOrder(int numCourses, int[][] prerequisites) {List<List<Integer>> graph = new ArrayList<>();int[] inDegree = new int[numCourses];// 初始化圖和入度數組for (int i = 0; i < numCourses; i++) {graph.add(new ArrayList<>());}// 構建圖和入度數組for (int[] p : prerequisites) {graph.get(p[1]).add(p[0]);inDegree[p[0]]++;}// 將入度為0的節點加入隊列Queue<Integer> q = new LinkedList<>();for (int i = 0; i < numCourses; i++) {if (inDegree[i] == 0) q.offer(i);}// 拓撲排序int[] result = new int[numCourses];int idx = 0;while (!q.isEmpty()) {int u = q.poll();result[idx++] = u;for (int v : graph.get(u)) {if (--inDegree[v] == 0) {q.offer(v);}}}// 檢查是否所有課程都能被安排return idx == numCourses ? result : new int[0];
}

5.2 網絡延遲時間(LeetCode 743)Dijkstra 應用

題目描述:給定一個網絡,計算從給定節點出發,信號到達所有其他節點的最短時間。如果無法到達所有節點,返回 - 1。

public int networkDelayTime(int[][] times, int n, int k) {// 構建鄰接表List<List<int[]>> graph = new ArrayList<>();for (int i = 0; i <= n; i++) {graph.add(new ArrayList<>());}for (int[] time : times) {graph.get(time[0]).add(new int[]{time[1], time[2]});}// 初始化距離數組int[] dist = new int[n + 1];Arrays.fill(dist, Integer.MAX_VALUE);dist[k] = 0;// 使用優先隊列實現Dijkstra算法PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[1] - b[1]);pq.offer(new int[]{k, 0});while (!pq.isEmpty()) {int[] curr = pq.poll();int u = curr[0], d = curr[1];// 如果當前距離大于已記錄的最短距離,跳過if (d > dist[u]) continue;// 遍歷所有鄰接邊for (int[] edge : graph.get(u)) {int v = edge[0], w = edge[1];if (dist[v] > dist[u] + w) {dist[v] = dist[u] + w;pq.offer(new int[]{v, dist[v]});}}}// 找到最大延遲時間int max = 0;for (int i = 1; i <= n; i++) {if (dist[i] == Integer.MAX_VALUE) return -1;max = Math.max(max, dist[i]);}return max;
}

六、算法優化技巧

6.1 Dijkstra 算法優化

  • 優先隊列選擇:使用斐波那契堆可將時間復雜度降至 O (E + VlogV)
  • 雙向搜索:同時從起點和終點進行搜索,減少搜索空間
  • A * 算法:啟發式搜索,利用距離估計函數加速搜索

6.2 并查集路徑壓縮

int find(int x) {if (parent[x] != x) {parent[x] = find(parent[x]); // 路徑壓縮}return parent[x];
}

6.3 記憶化搜索

// 用于存在性路徑問題
int[][] memo;int dfsMemo(int[][] graph, int u, int target) {if (u == target) return 1;if (memo[u][target] != 0) return memo[u][target];for (int v : graph[u]) {if (dfsMemo(graph, v, target) == 1) {memo[u][target] = 1;return 1;}}memo[u][target] = -1;return -1;
}

七、常見問題及解決方案

問題類型解決方法相關算法
負權邊檢測Bellman-Ford 算法單源最短路徑
檢測環路拓撲排序 / DFS 訪問標記有向無環圖判斷
連通分量并查集 / BFS/DFS圖連通性判斷
關鍵路徑拓撲排序 + 動態規劃AOE 網絡

結語

掌握圖論算法是應對大廠面試的關鍵,建議按照以下步驟系統學習:

  1. 理解基礎:圖的表示方法、遍歷方式
  2. 掌握經典算法:Dijkstra、Prim、拓撲排序
  3. 刷題鞏固:完成 LeetCode 圖論專題 50 題
  4. 深入研究:學習 Tarjan、匈牙利算法等高級算法

推薦練習題目

  • 課程表
  • 克隆圖
  • 判斷二分圖
  • 矩陣中的最長遞增路徑
  • 網絡延遲時間

本文代碼均通過 LeetCode 測試用例,建議配合在線判題系統實踐。如有疑問歡迎在評論區交流討論!

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

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

相關文章

江科大TIM定時器hal庫實現

定時器相關hal庫函數 hal庫的定時器函數相比于標準庫&#xff0c;多了很多的中斷回調函數&#xff0c;同時對于定時器的初始化也改成使用句柄一次性順帶連帶DMA等功能一起初始化了 typedef struct {uint32_t Prescaler; /*定時器的預分頻值*/uint32_t CounterMode; …

CentOS 10:啟動telnet服務

參考&#xff0c; 鳥哥私房菜 - 第七章、網路安全與主機基本防護&#xff1a;限制埠口, 網路升級與 SELinux 7.3.3 埠口與服務的啟動/關閉及開機時狀態設定 我們知道系統的 Telnet 服務通常是以 super daemon 來控管的&#xff0c;請您啟動您系統的 telnet 試看看。 1 要啟動 …

Taro 安全區域

目錄 一、問題描述 二、問題解決 1、頂部劉海區 2、底部小黑條 一、問題描述 安全區域主要是為了避免劉海屏或底部欄遮擋&#xff0c;而造成的不良顯示效果。 本次將針對以下兩點進行考量&#xff1a; 1、頂部劉海屏區 2、蘋果X底部小黑條 二、問題解決 通過Taro.getS…

【Java微服務組件】分布式協調P1-數據共享中心簡單設計與實現

歡迎來到啾啾的博客&#x1f431;。 記錄學習點滴。分享工作思考和實用技巧&#xff0c;偶爾也分享一些雜談&#x1f4ac;。 歡迎評論交流&#xff0c;感謝您的閱讀&#x1f604;。 目錄 引言設計一個共享數據中心選擇數據模型鍵值對設計 數據可靠性設計持久化快照 &#xff08…

在SpringBoot項目中,使用單元測試@Test

1.引入依賴 <!--單元測試Test的依賴--> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><version>3.2.1</version> </dependency> 2.在src/test/java目錄…

在Java中,將Object對象轉換為具體實體類對象

在Java中&#xff0c;將Object對象轉換為具體實體類對象可以通過以下幾種方法實現&#xff1a; 1?.使用instanceof關鍵字進行類型檢查和轉換?&#xff1a; 首先&#xff0c;使用instanceof關鍵字檢查Object對象是否為目標實體類的類型。 如果是&#xff0c;則進行強制類型…

JAVA學習-練習試用Java實現“音頻文件的讀取與寫入 :使用Java音頻庫處理音頻數據”

問題&#xff1a; java語言編輯&#xff0c;實現音頻文件的讀取與寫入 &#xff1a;使用Java音頻庫處理音頻數據。 解答思路&#xff1a; 在Java中處理音頻文件通常需要使用第三方庫&#xff0c;例如javax.sound.sampled包&#xff0c;它提供了處理音頻文件的基本功能。以下是一…

Flink架構概覽,Flink DataStream API 的使用,FlinkCDC的使用

一、Flink與其他組件的協同 Flink 是一個分布式、高性能、始終可用、準確一次&#xff08;Exactly-Once&#xff09;語義的流處理引擎&#xff0c;廣泛應用于大數據實時處理場景中。它與 Hadoop 生態系統中的組件可以深度集成&#xff0c;形成完整的大數據處理鏈路。下面我們從…

linux 查看java的安裝路徑

一、驗證Java安裝狀態 java -version正常安裝會顯示版本信息&#xff1a; openjdk version "1.8.0_65" OpenJDK Runtime Environment (build 1.8.0_65-b17) OpenJDK 64-Bit Server VM (build 25.65-b01, mixed mode)二、檢查環境變量配置 若已配置JAVA_HOME&#…

2025-5-21 個人筆記篇matlab小筆記和clang基礎使用(簡單記錄)

個人筆記篇 再不記錄就找不到了&#xff0c;之前學的一點基礎&#xff0c;看看就行,請不要提問,因為很久了>_<(至少我看來是這樣的) matlab小筆記 % 開繪制(新建) figure % 設置繪制標題 title(標題); % 設置繪制的X軸Lable xlabel(x); % 設置繪制的y軸Lable ylabel(cos…

前端JavaScript-嵌套事件

點擊 如果在多層嵌套中&#xff0c;對每層都設置事件監視器&#xff0c;試試看 <!DOCTYPE html> <html lang"cn"> <body><div id"container"><button>點我&#xff01;</button></div><pre id"output…

網感驅動下開源AI大模型AI智能名片S2B2C商城小程序源碼的實踐路徑研究

摘要&#xff1a;在數字化浪潮中&#xff0c;網感已成為內容創作者與商業運營者必備的核心能力。本文以開源AI大模型、AI智能名片及S2B2C商城小程序源碼為技術載體&#xff0c;通過解析網感培養與用戶需求洞察的內在關聯&#xff0c;提出"數據驅動-場景適配-價值重構"…

AG-UI:重構AI代理與前端交互的下一代協議標準

目錄 技術演進背景與核心價值協議架構與技術原理深度解析核心功能與標準化事件體系典型應用場景與實戰案例開發者生態與集成指南行業影響與未來展望1. 技術演進背景與核心價值 1.1 AI交互的三大痛點 當前AI應用生態面臨三大核心挑戰: 交互碎片化:LangGraph、CrewAI等框架各…

游戲引擎學習第301天:使用精靈邊界進行排序

回顧并為今天的內容做準備 昨天&#xff0c;我們解決了一些關于排序的問題&#xff0c;這對我們清理長期存在的Z軸排序問題很有幫助。這個問題我們一直想在開始常規游戲代碼之前解決。雖然不確定是否完全解決了問題&#xff0c;但我們提出了一個看起來合理的排序標準。 有兩點…

Ajax快速入門教程

輸入java時&#xff0c;頁面并沒有刷新但是下面自動聯想出了跟java有關的東西&#xff0c;像這種就叫異步交互 它不會妨礙你的輸入&#xff0c;同時還能夠同步進行對于java相關聯想詞的推送 發送異步請求需要借助工具axios 引入axios&#xff0c;可以直接在scripts中引入 get和…

Anti Spy安卓版:智能防護,守護手機安全

Anti Spy安卓版是一款專為安卓設備設計的智能防護應用&#xff0c;旨在幫助用戶實時防護手機安全&#xff0c;抵御間諜軟件、惡意軟件和其他潛在威脅。它基于人工智能和啟發式搜索方法的引擎&#xff0c;能夠檢測并阻止已知和未知的間諜軟件、后門程序、賬單欺詐、短信欺詐、電…

超低延遲音視頻直播技術的未來發展與創新

引言 音視頻直播技術正在深刻改變著我們的生活和工作方式&#xff0c;尤其是在教育、醫療、安防、娛樂等行業。無論是全球性的體育賽事、遠程醫療、在線教育&#xff0c;還是智慧安防、智能家居等應用場景&#xff0c;都離不開音視頻技術的支持。為了應對越來越高的需求&#x…

系統架構設計(十二):統一過程模型(RUP)

簡介 RUP 是由 IBM Rational 公司提出的一種 面向對象的軟件工程過程模型&#xff0c;以 UML 為建模語言&#xff0c;是一種 以用例為驅動、以架構為中心、迭代式、增量開發的過程模型。 三大特征 特征說明以用例為驅動&#xff08;Use Case Driven&#xff09;需求分析和測…

海康相機連接測試-極簡版

文章目錄 1、下載客戶端 1、下載客戶端 海康機器人官網下載軟件 軟件下載地址 先下載客戶端測試連接 按照你的相機的類型選擇客戶端 安裝完畢后&#xff0c;確保USB線插的是3.0的端口 軟件會自動識別相機型號 在上方有播放按鈕&#xff0c;可以采集圖像信息顯示

Linux 磁盤擴容實戰案例:從問題發現到完美解決

Linux 磁盤擴容實戰案例&#xff1a;從問題發現到完美解決 案例背景 某企業服務器根目錄 (/) 空間不足&#xff0c;運維人員通過 df -h 發現 /dev/vda1 分區已 100% 占滿&#xff08;99G 已用&#xff09;。檢查發現物理磁盤 /dev/vda 已擴展至 200G&#xff0c;但分區和文件…