軟體工程師職涯升級與圖論基礎:從圖結構到 LeetCode 實戰

更新 發佈閱讀 15 分鐘

軟體工程師職涯升級計畫啟動!立即預約職涯諮詢、履歷健檢或模擬面試👈,為您的加薪做好準備!


什麼是圖(Graph)?

是一種非線性的資料結構,它由兩個基本元素構成:節點(vertex)和邊(edge)。我們可以將圖想像成一張地圖,其中每個城市是節點,而連接城市之間的道路就是邊。

  • 節點(Vertex):也稱為頂點或節點(node),代表資料元素。通常用 V 或 V(G) 表示所有節點的集合。
  • 邊(Edge):代表節點之間的連結關係。一條邊通常由一對節點 (u,v) 表示。所有邊的集合用 E 或 E(G) 表示。

因此,一個圖 G 就是一個由節點集合 V 和邊集合 E 組成的結構,通常寫作 G(V,E)。


有向圖與無向圖

根據邊是否具有方向性,圖可以分為兩種類型:

raw-image
  • 無向圖(Undirected Graph):邊不具有方向性。如果節點 A 和 B 之間有一條邊,表示從 A 可以到達 B,同時也可以從 B 到達 A。例如,朋友關係就是一種無向圖:如果你是我的朋友,那我也是你的朋友。
raw-image


  • 有向圖(Directed Graph):邊具有方向性。邊 (u,v) 表示可以從 u 到達 v,但不一定能從 v 到達 u。例如,大學課程的先修關係就是一種有向圖:要修習「資料結構」,必須先完成「程式設計」這門課。


圖的常見應用

圖結構在電腦科學和現實世界中應用廣泛,以下是一些經典例子:

  • 最短路徑問題(Shortest Path):這是圖中最常見的問題之一。例如,Google Maps 就是用圖來計算兩點之間的最短行車路線。


圖的程式碼實現

在程式中,我們可以用不同的方式來表示圖,最常見的是鄰接矩陣(Adjacency Matrix)和鄰接列表(Adjacency List)

1. 鄰接矩陣(Adjacency Matrix)

這是一個二維陣列(矩陣),matrix[i][j] 的值用來表示節點 i 和節點 j 之間是否有邊。如果圖是加權的,matrix[i][j] 可以儲存邊的權重。

  • 優點:查找兩個節點之間是否有邊的速度非常快,時間複雜度為 O(1)。
  • 缺點:如果節點數量多但邊很少(稀疏圖),會浪費大量記憶體空間。
// 使用鄰接矩陣實現無向圖
class GraphMatrix {
constructor(numVertices) {
this.numVertices = numVertices;
this.adjMatrix = Array(numVertices)
.fill(null)
.map(() => Array(numVertices).fill(0));
}

// 新增一個節點
addVertex(v) {
// 鄰接矩陣無法動態增加節點,因此通常在初始化時就確定節點數
console.log("Cannot add vertex to a fixed-size adjacency matrix.");
}

// 新增一條邊
addEdge(v1, v2) {
if (v1 >= 0 && v1 < this.numVertices && v2 >= 0 && v2 < this.numVertices) {
this.adjMatrix[v1][v2] = 1;
this.adjMatrix[v2][v1] = 1; // 無向圖,是對稱的
}
}

// 移除一條邊
removeEdge(v1, v2) {
if (v1 >= 0 && v1 < this.numVertices && v2 >= 0 && v2 < this.numVertices) {
this.adjMatrix[v1][v2] = 0;
this.adjMatrix[v2][v1] = 0;
}
}

// 檢查兩個節點是否相連
hasEdge(v1, v2) {
if (v1 >= 0 && v1 < this.numVertices && v2 >= 0 && v2 < this.numVertices) {
return this.adjMatrix[v1][v2] === 1;
}
return false;
}
}

// 範例
const graphMatrix = new GraphMatrix(5); // 建立一個有5個節點的圖
graphMatrix.addEdge(0, 1);
graphMatrix.addEdge(0, 4);
graphMatrix.addEdge(1, 2);
graphMatrix.addEdge(1, 3);
graphMatrix.addEdge(1, 4);
graphMatrix.addEdge(2, 3);
graphMatrix.addEdge(3, 4);

console.log(graphMatrix.adjMatrix);
/* 輸出:
[
[0, 1, 0, 0, 1],
[1, 0, 1, 1, 1],
[0, 1, 0, 1, 0],
[0, 1, 1, 0, 1],
[1, 1, 0, 1, 0]
]
*/

2. 鄰接列表(Adjacency List)

這是一種陣列與鍊結串列(或 JavaScript 中的 Map / 陣列)結合的表示法。用一個 Map 來儲存所有節點,Map 的鍵(key)對應一個節點,值(value)則是一個陣列,用來儲存與該節點相連的所有鄰居節點。

  • 優點:記憶體空間利用率高,特別適合稀疏圖。且可以動態增加、刪除節點。
  • 缺點:查詢兩個節點之間是否有邊需要遍歷該節點的鄰接列表,時間複雜度為 O(∣V∣),而鄰接矩陣為 O(1)。
// 使用鄰接列表實現無向圖
class GraphList {
constructor() {
this.adjList = new Map(); // 使用 Map 存儲鄰接列表
}

// 新增一個節點
addVertex(v) {
if (!this.adjList.has(v)) {
this.adjList.set(v, []);
}
}

// 新增一條邊
addEdge(v1, v2) {
// 確保兩個節點都存在
if (!this.adjList.has(v1)) {
this.addVertex(v1);
}
if (!this.adjList.has(v2)) {
this.addVertex(v2);
}

// 如果邊不存在才新增
if (!this.adjList.get(v1).includes(v2)) {
this.adjList.get(v1).push(v2);
}
if (!this.adjList.get(v2).includes(v1)) {
this.adjList.get(v2).push(v1); // 無向圖
}
}

// 移除一個節點
removeVertex(v) {
if (this.adjList.has(v)) {
// 移除所有指向該節點的邊
for (let neighbor of this.adjList.get(v)) {
this.adjList.set(neighbor, this.adjList.get(neighbor).filter(node => node !== v));
}
// 最後移除該節點
this.adjList.delete(v);
}
}

// 移除一條邊
removeEdge(v1, v2) {
if (this.adjList.has(v1) && this.adjList.has(v2)) {
this.adjList.set(
v1,
this.adjList.get(v1).filter((v) => v !== v2)
);
this.adjList.set(
v2,
this.adjList.get(v2).filter((v) => v !== v1)
);
}
}

// 檢查兩個節點是否相連
hasEdge(v1, v2) {
if (this.adjList.has(v1) && this.adjList.has(v2)) {
return this.adjList.get(v1).includes(v2);
}
return false;
}
}

// 範例
const graphList = new GraphList();
graphList.addVertex('A');
graphList.addVertex('B');
graphList.addVertex('C');
graphList.addEdge('A', 'B');
graphList.addEdge('A', 'C');
graphList.addEdge('B', 'C');

console.log(graphList.adjList);
/* 輸出:
Map(3) {
'A' => [ 'B', 'C' ],
'B' => [ 'A', 'C' ],
'C' => [ 'A', 'B' ]
}
*/


LeetCode 實戰範例:圖的遍歷

題目: LeetCode 200. Number of Islands(島嶼數量)

題目描述:

給定一個由 '1'(陸地)和 '0'(水)組成的 mtimesn 二維網格地圖,計算地圖中島嶼的數量。一個島嶼是由相鄰(水平或垂直)的陸地組成的,周圍被水環繞。

解題思路:

這是一個典型的圖遍歷問題。我們可以將網格中的每個陸地('1')視為圖中的一個節點,相鄰的陸地則有邊相連。我們的目標是計算圖中有多少個連通分量(connected components)。

我們可以使用 深度優先搜尋(DFS)廣度優先搜尋(BFS) 來解決這個問題。

DFS 解法:

  1. 初始化一個變數 count 來計數島嶼數量。
  2. 遍歷網格中的每一個單元格。
  3. 如果當前單元格是陸地('1'),表示我們找到了一個新的島嶼。將 count 加一。
  4. 對這個陸地單元格執行 DFS 遍歷,將它以及所有與它相連的陸地都標記為已訪問(例如,將 '1' 變成 '0'),以避免重複計算。
  5. DFS 函數會遞迴地訪問當前單元格的上下左右四個方向的鄰居。如果鄰居也是陸地且沒有被訪問過,則繼續對鄰居執行 DFS。
  6. 遍歷結束後,count 就是島嶼的總數。

JavaScript 程式碼(DFS):

/**
* @param {character[][]} grid
* @return {number}
*/
var numIslands = function (grid) {
if (!grid || grid.length === 0) {
return 0;
}

const rows = grid.length;
const cols = grid[0].length;
let count = 0;

// DFS 輔助函數
const dfs = (r, c) => {
// 邊界條件檢查
if (r < 0 || r >= rows || c < 0 || c >= cols || grid[r][c] === '0') {
return;
}

// 將當前節點標記為已訪問('0')
grid[r][c] = '0';

// 遞迴地訪問上下左右的鄰居
dfs(r + 1, c); // 下
dfs(r - 1, c); // 上
dfs(r, c + 1); // 右
dfs(r, c - 1); // 左
};

// 遍歷整個網格
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
if (grid[r][c] === '1') {
count++; // 找到一個新的島嶼
dfs(r, c); // 執行 DFS 遍歷,將整個島嶼標記
}
}
}

return count;
};



總結

圖(Graph)作為一種強大的非線性資料結構,透過節點(Vertex)和邊(Edge)來表示複雜的關係網絡。無論是有向圖還是無向圖,它們在現實世界的應用無處不在,從社交網絡到地圖導航,甚至是電腦網路的拓撲結構。

在程式碼實現上,**鄰接列表(Adjacency List)鄰接矩陣(Adjacency Matrix)**是最常見的兩種方式,各有其優缺點。鄰接列表在處理稀疏圖時更具空間效率,而鄰接矩陣則在查詢兩節點之間是否有邊時更快速。掌握這些基本概念和實現方法,是解決許多複雜演算法問題的關鍵,例如尋找最短路徑或計算連通分量等。

留言
avatar-img
跨越國界的程式人生
5會員
41內容數
自學程式,現為網頁開發工程師,同時擔任線上課程講師,專注於幫助自學程式的開發者找到理想工作。熱愛技術與分享,致力於將複雜的概念轉化為實用知識,讓更多人踏入軟體開發的世界。
2025/08/23
本文提供軟體工程師面試常考的樹狀結構(Tree)相關知識,包含樹的基本概念、常見術語、二元樹、二元搜尋樹、樹的遍歷(DFS 與 BFS)、以及經典題目 Validate Binary Search Tree 的 JavaScript 解法與核心思路。
Thumbnail
2025/08/23
本文提供軟體工程師面試常考的樹狀結構(Tree)相關知識,包含樹的基本概念、常見術語、二元樹、二元搜尋樹、樹的遍歷(DFS 與 BFS)、以及經典題目 Validate Binary Search Tree 的 JavaScript 解法與核心思路。
Thumbnail
2025/07/29
什麼是雜湊表?—— 鍵值對的抽象資料型態 想像一下,你有一本非常厚的通訊錄。如果這本通訊錄是按照每個人的姓名首字母排序的,當你想找「王小明」的電話時,你可以迅速翻到「W」的開頭,然後快速找到他。雜湊表就
Thumbnail
2025/07/29
什麼是雜湊表?—— 鍵值對的抽象資料型態 想像一下,你有一本非常厚的通訊錄。如果這本通訊錄是按照每個人的姓名首字母排序的,當你想找「王小明」的電話時,你可以迅速翻到「W」的開頭,然後快速找到他。雜湊表就
Thumbnail
2025/07/22
如果你正在規劃出國工作,那麼今天的這篇文章,絕對能給你一些扎實的參考資訊。我們要來聊聊一個近年來在台灣人圈子裡越來越受歡迎的歐洲工作目的地——愛爾蘭!🇮🇪
Thumbnail
2025/07/22
如果你正在規劃出國工作,那麼今天的這篇文章,絕對能給你一些扎實的參考資訊。我們要來聊聊一個近年來在台灣人圈子裡越來越受歡迎的歐洲工作目的地——愛爾蘭!🇮🇪
Thumbnail
看更多
你可能也想看
Thumbnail
在 vocus 與你一起探索內容、發掘靈感的路上,我們又將啟動新的冒險——vocus App 正式推出! 現在起,你可以在 iOS App Store 下載全新上架的 vocus App。 無論是在通勤路上、日常空檔,或一天結束後的放鬆時刻,都能自在沈浸在內容宇宙中。
Thumbnail
在 vocus 與你一起探索內容、發掘靈感的路上,我們又將啟動新的冒險——vocus App 正式推出! 現在起,你可以在 iOS App Store 下載全新上架的 vocus App。 無論是在通勤路上、日常空檔,或一天結束後的放鬆時刻,都能自在沈浸在內容宇宙中。
Thumbnail
vocus 慶祝推出 App,舉辦 2026 全站慶。推出精選內容與數位商品折扣,訂單免費與紅包抽獎、新註冊會員專屬活動、Boba Boost 贊助抽紅包,以及全站徵文,並邀請你一起來回顧過去的一年, vocus 與創作者共同留下了哪些精彩創作。
Thumbnail
vocus 慶祝推出 App,舉辦 2026 全站慶。推出精選內容與數位商品折扣,訂單免費與紅包抽獎、新註冊會員專屬活動、Boba Boost 贊助抽紅包,以及全站徵文,並邀請你一起來回顧過去的一年, vocus 與創作者共同留下了哪些精彩創作。
Thumbnail
如果你也是從事軟體相關工作的人,一定會遭遇突然需要你去學習一套你不熟悉的程式語言狀況吧,此時你會怎麼做呢? 是趕快去買書來看嗎? 還是趕快找一門程式課來上? 又或者乾脆去找會的同事來教學?
Thumbnail
如果你也是從事軟體相關工作的人,一定會遭遇突然需要你去學習一套你不熟悉的程式語言狀況吧,此時你會怎麼做呢? 是趕快去買書來看嗎? 還是趕快找一門程式課來上? 又或者乾脆去找會的同事來教學?
Thumbnail
LeetCode 是一個程式語言版的線上題庫平臺,提供題目描述、程式碼區塊、解題者分享的解法和疑問討論。藉由這篇文章分享我在 LeetCode 上的使用經驗和觀點,包括刷題的重要性、解題心態和練習目標。
Thumbnail
LeetCode 是一個程式語言版的線上題庫平臺,提供題目描述、程式碼區塊、解題者分享的解法和疑問討論。藉由這篇文章分享我在 LeetCode 上的使用經驗和觀點,包括刷題的重要性、解題心態和練習目標。
Thumbnail
高中數學主題練習—對數方程式
Thumbnail
高中數學主題練習—對數方程式
Thumbnail
Leetcode 精選75題 題目與題解 熱門考點 目錄 (持續更新中) 建議從左側目錄 或者 按Ctrl+F輸入關鍵字進行搜尋
Thumbnail
Leetcode 精選75題 題目與題解 熱門考點 目錄 (持續更新中) 建議從左側目錄 或者 按Ctrl+F輸入關鍵字進行搜尋
Thumbnail
最近有新的訂閱者加入, 想趁這個機會再分享一次學習心法與建議給第一次練習的讀者、同學們。 如果你本身已經很熟練演算法,那隨機挑題目練習ok,可以測試觀念是否正確,並且驗證寫code的效率與正確程度。 如果是剛畢業或還在學,以前沒有打過程式競賽。 想開始有系統地增強演算法&資料結構的能力
Thumbnail
最近有新的訂閱者加入, 想趁這個機會再分享一次學習心法與建議給第一次練習的讀者、同學們。 如果你本身已經很熟練演算法,那隨機挑題目練習ok,可以測試觀念是否正確,並且驗證寫code的效率與正確程度。 如果是剛畢業或還在學,以前沒有打過程式競賽。 想開始有系統地增強演算法&資料結構的能力
Thumbnail
世上沒有天才,卻有成功方程式。 想學習新技能、達成目標或成就,作者透過自身的學習經驗,搭配閱讀書籍,分享成功方程式,幫你體驗生活。
Thumbnail
世上沒有天才,卻有成功方程式。 想學習新技能、達成目標或成就,作者透過自身的學習經驗,搭配閱讀書籍,分享成功方程式,幫你體驗生活。
Thumbnail
本篇文章深入介紹了圖形的基本概念、組成和應用。從圖形的基本組成,到圖的類型與種類,再到圖形演算法的三大類型,本文將接續圖形領域的深入學習,並分享了對圖形的初步認識和學習方向的小心得。希望對正在學習圖形的人有所幫助。
Thumbnail
本篇文章深入介紹了圖形的基本概念、組成和應用。從圖形的基本組成,到圖的類型與種類,再到圖形演算法的三大類型,本文將接續圖形領域的深入學習,並分享了對圖形的初步認識和學習方向的小心得。希望對正在學習圖形的人有所幫助。
Thumbnail
圖形演算法在資料處理上扮演重要角色。本文介紹圖形的歷史、定義、技術用途,以及為什麼我們要關心圖形演算法。文末還提及圖形演算法在機器學習領域的應用。下次將介紹更詳細的圖形演算法內容。
Thumbnail
圖形演算法在資料處理上扮演重要角色。本文介紹圖形的歷史、定義、技術用途,以及為什麼我們要關心圖形演算法。文末還提及圖形演算法在機器學習領域的應用。下次將介紹更詳細的圖形演算法內容。
Thumbnail
解決電腦上遇到的問題、證明正確性、探討效率 並且很著重溝通,說服別人你做的事是正確且有效率的。 內容: 計算模型、資料結構介紹、演算法介紹、時間複雜度介紹。
Thumbnail
解決電腦上遇到的問題、證明正確性、探討效率 並且很著重溝通,說服別人你做的事是正確且有效率的。 內容: 計算模型、資料結構介紹、演算法介紹、時間複雜度介紹。
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News