图论算法精解(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(V²)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.mzph.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;但分区和文件…