Dijkstra算法详细(单源最短路径算法)

首先我们先回顾一下图论的基础吧,

一幅图是由节点和边构成的,逻辑结构如下,通常我们会用邻接表或邻接矩阵来实现:

在这里插入图片描述

在这里插入图片描述

①邻接表很直观,我把每个节点 x 的邻居都存到一个列表里,然后把 x 和这个列表关联起来,这样就可以通过一个节点 x 找到它的所有相邻节点。

②邻接矩阵则是一个二维布尔数组,我们权且成为 matrix,如果节点 x 和 y 是相连的,那么就把 matrix[x][y] 设为 true(上图中绿色的方格代表 true)。如果想找节点 x 的邻居,去扫一圈 matrix[x][…] 就行了。

优缺点:

对于邻接表,好处是占用的空间少。但是,邻接表无法快速判断两个节点是否相邻。

比如说我想判断节点 1 是否和节点 3 相邻,我要去邻接表里 1 对应的邻居列表里查找 3 是否存在。但对于邻接矩阵就简单了,只要看看 matrix[1][3] 就知道了,效率高。

有向加权图的实现

如果是邻接表,我们不仅仅存储某个节点 x 的所有邻居节点,还存储 x 到每个邻居的权重。

如果是邻接矩阵,matrix[x][y] 不再是布尔值,而是一个 int 值,0 表示没有连接,其他值表示权重。

无向图怎么实现

如果连接无向图中的节点 x 和 y,把 matrix[x][y] 和 matrix[y][x] 都变成 true 不就行了;邻接表也是类似的操作。

图的遍历

图和多叉树最大的区别是,图是可能包含环的,你从图的某一个节点开始遍历,有可能走了一圈又回到这个节点。所以,如果图包含环,遍历框架就要一个 visited 数组进行辅助;

boolean[] visited;

/* 图遍历框架 */

void traverse(Graph graph, int s) {

if (visited[s]) return;

// 经过节点 s

visited[s] = true;

for (int neighbor : graph.neighbors(s))

traverse(graph, neighbor);

// 离开节点 s

visited[s] = false;

}

这个 visited 数组的操作很像回溯算法做「做选择」和「撤销选择」,区别在于位置,回溯算法的「做选择」和「撤销选择」在 for 循环里面,而对 visited 数组的操作在 for 循环外面。

1.二叉树层级遍历和 BFS 算法

在这里插入图片描述

// 输入一棵二叉树的根节点,层序遍历这棵二叉树

void levelTraverse(TreeNode root) {

if (root == null) return 0;

Queue q = new LinkedList<>();

q.offer(root);

int depth = 1;

// 从上到下遍历二叉树的每一层

while (!q.isEmpty()) {

int sz = q.size();

// 从左到右遍历每一层的每个节点

for (int i = 0; i < sz; i++) {

TreeNode cur = q.poll();

printf(“节点 %s 在第 %s 层”, cur, depth);

// 将下一层节点放入队列

if (cur.left != null) {

q.offer(cur.left);

}

if (cur.right != null) {

q.offer(cur.right);

}

}

depth++;

}

}

在这里插入图片描述

while 循环控制一层一层往下走,for 循环利用 sz 变量控制从左到右遍历每一层二叉树节点。

注意我们代码框架中的 depth 变量,其实就记录了当前遍历到的层数。换句话说,每当我们遍历到一个节点 cur,都知道这个节点属于第几层。

2.基于二叉树的遍历框架,我们又可以扩展出多叉树的层序遍历框架:

// 输入一棵多叉树的根节点,层序遍历这棵多叉树

void levelTraverse(TreeNode root) {

if (root == null) return 0;

Queue q = new LinkedList<>();

q.offer(root);

int depth = 1;

// 从上到下遍历多叉树的每一层

while (!q.isEmpty()) {

int sz = q.size();

// 从左到右遍历每一层的每个节点

for (int i = 0; i < sz; i++) {

TreeNode cur = q.poll();

printf(“节点 %s 在第 %s 层”, cur, depth);

// 将下一层节点放入队列

for (TreeNode child : cur.children) {

q.offer(child);

}

}

depth++;

}

}

基于多叉树的遍历框架,我们又可以扩展出 BFS(广度优先搜索)的算法框架:

// 输入起点,进行 BFS 搜索

int BFS(Node start) {

Queue q; // 核心数据结构

Set visited; // 避免走回头路

q.offer(start); // 将起点加入队列

visited.add(start);

int step = 0; // 记录搜索的步数

while (q not empty) {

int sz = q.size();

/* 将当前队列中的所有节点向四周扩散一步 */

for (int i = 0; i < sz; i++) {

Node cur = q.poll();

printf(“从 %s 到 %s 的最短距离是 %s”, start, cur, step);

/* 将 cur 的相邻节点加入队列 */

for (Node x : cur.adj()) {

if (x not in visited) {

q.offer(x);

visited.add(x);

}

}

}

step++;

}

}

基于多叉树的遍历框架,我们又可以扩展出 BFS(广度优先搜索)的算法框架,所谓 BFS 算法,就是把算法问题抽象成一幅「无权图」,然后继续玩二叉树层级遍历那一套罢了。:

// 输入起点,进行 BFS 搜索

int BFS(Node start) {

Queue q; // 核心数据结构

Set visited; // 避免走回头路

q.offer(start); // 将起点加入队列

visited.add(start);

int step = 0; // 记录搜索的步数

while (q not empty) {

int sz = q.size();

/* 将当前队列中的所有节点向四周扩散一步 */

for (int i = 0; i < sz; i++) {

Node

必看视频!获取2024年最新Java开发全套学习资料 备注Java

cur = q.poll();

printf(“从 %s 到 %s 的最短距离是 %s”, start, cur, step);

/* 将 cur 的相邻节点加入队列 */

for (Node x : cur.adj()) {

if (x not in visited) {

q.offer(x);

visited.add(x);

}

}

}

step++;

}

}

这是对于无权图的应用,那么对于有权图我们就不能这么用了,因为有权图的最短路径问题不是依照步数来判断的了。所以我们要进一步简化框架,把while 循环里面的的for 去掉。

// 输入一棵二叉树的根节点,遍历这棵二叉树所有节点

void levelTraverse(TreeNode root) {

if (root == null) return 0;

Queue q = new LinkedList<>();

q.offer(root);

// 遍历二叉树的每一个节点

while (!q.isEmpty()) {

TreeNode cur = q.poll();

printf(“我不知道节点 %s 在第几层”, cur);

// 将子节点放入队列

if (cur.left != null) {

q.offer(cur.left);

}

if (cur.right != null) {

q.offer(cur.right);

}

}

}

如果你想同时维护 depth 变量,让每个节点 cur 知道自己在第几层,可以想其他办法,比如新建一个 State 类,记录每个节点所在的层数:

class State {

// 记录 node 节点的深度

int depth;

TreeNode node;

State(TreeNode node, int depth) {

this.depth = depth;

this.node = node;

}

}

// 输入一棵二叉树的根节点,遍历这棵二叉树所有节点

void levelTraverse(TreeNode root) {

if (root == null) return 0;

Queue q = new LinkedList<>();

q.offer(new State(root, 1));

// 遍历二叉树的每一个节点

while (!q.isEmpty()) {

State cur = q.poll();

TreeNode cur_node = cur.node;

我的面试宝典:一线互联网大厂Java核心面试题库

以下是我个人的一些做法,希望可以给各位提供一些帮助:

整理了很长一段时间,拿来复习面试刷题非常合适,其中包括了Java基础、异常、集合、并发编程、JVM、Spring全家桶、MyBatis、Redis、数据库、中间件MQ、Dubbo、Linux、Tomcat、ZooKeeper、Netty等等,且还会持续的更新…可star一下!

image

283页的Java进阶核心pdf文档

Java部分:Java基础,集合,并发,多线程,JVM,设计模式

数据结构算法:Java算法,数据结构

开源框架部分:Spring,MyBatis,MVC,netty,tomcat

分布式部分:架构设计,Redis缓存,Zookeeper,kafka,RabbitMQ,负载均衡等

微服务部分:SpringBoot,SpringCloud,Dubbo,Docker

image

还有源码相关的阅读学习

image

M、Spring全家桶、MyBatis、Redis、数据库、中间件MQ、Dubbo、Linux、Tomcat、ZooKeeper、Netty等等,且还会持续的更新…可star一下!

[外链图片转存中…(img-R452rzDf-1716406840211)]

283页的Java进阶核心pdf文档

Java部分:Java基础,集合,并发,多线程,JVM,设计模式

数据结构算法:Java算法,数据结构

开源框架部分:Spring,MyBatis,MVC,netty,tomcat

分布式部分:架构设计,Redis缓存,Zookeeper,kafka,RabbitMQ,负载均衡等

微服务部分:SpringBoot,SpringCloud,Dubbo,Docker

[外链图片转存中…(img-06HhbPdh-1716406840211)]

还有源码相关的阅读学习

[外链图片转存中…(img-pARj4dkU-1716406840211)]

  • 25
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Dijkstra算法是一种用于求解单源路径的经典算法。它以一个源顶点为起始点,通过不断扩展最路径树来寻找到其他所有顶点的最路径。下面是Dijkstra算法的思路和步骤。 首先,我们需要定义一个顶点集合,用于存放已经求得最路径的顶点。算法开始时,所有顶点都被标记为未被访问,并且它们的最路径长度都初始化为无穷大。 然后,我们从起始顶点开始,将其最路径长度置为0,并将其加入到已求得最路径的集合中。此外,我们还需要更新起始顶点的邻居顶点的最路径长度。 接下来,我们进入循环,不断选择最路径长度最小的顶点,将其加入到已求得最路径的集合中。然后,更新该顶点的邻居顶点的最路径长度。具体的更新方式是,如果通过当前选中的顶点访问邻居顶点的路径长度比已知的最路径长度小,那么更新邻居顶点的最路径长度。 最后,当所有顶点都被加入到已求得最路径的集合中,或者存在无穷大的路径时,算法结束。此时,我们得到了从起始顶点到其他所有顶点的最路径长度。 Dijkstra算法的时间复杂度为O(V^2),其中V为图中顶点的数量。此外,它还可以通过使用优先队列来优化,将时间复杂度降低到O((V+E)logV),其中E为图中边的数量。 总之,Dijkstra算法是一种求解单源路径的有效算法,它可以应用于各种实际问题中,如路由选择、网络通信、物流规划等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值