网易首页 > 网易号 > 正文 申请入驻

《经典图论算法》迪杰斯特拉算法(Dijkstra)

0
分享至

摘要:

1,迪杰斯特拉算法介绍

2,迪杰斯特拉算法的代码实现

3,迪杰斯特拉算法的堆优化

4,为什么迪杰斯特拉算法不能处理带有负权边的图

1,迪杰斯特拉算法介绍

迪杰斯特拉算法(Dijkstra)也叫狄克斯特拉算法,它使用类似广度优先搜索的方法,解决从一个顶点到其他所有顶点的最短路径问题,它解决的是加权图(不能有负权)的最短路径问题。

从起始点开始,采用贪心算法的策略,每次选择一个没被标记且距离起始点最近的顶点,把它标记下,然后更新和它邻接的顶点 ……,直到所有顶点都计算完为止。

如上图所示,假如计算从上海到其他所有城市的最短时间,上面的时间有可能是开车,有可能是高铁也可能是坐飞机,和真实距离不成正比。

我们从起始点开始,使用一个数组 dis ,数组中 dis[j] 的值表示从起始点到顶点 j 的时间,刚开始的时候,起始点到他自己为 0 ,到其他顶点都为无穷大,如下图所示。

如果想要减少从起始点到 j 的时间,唯一的方式就是需要寻找一个中转站 k 。从起始点到 k 的时间为 dis[k] ,从 k 到 j 的时间为 g[k][j] ,然后判断中转的总时间 dis[k] + g[k][j] 是否小于 dis[j] ,如果中转时间小于 dis[j] ,就更新 dis[j] 。

比如最上面图中,从起始点到南京的时间是 3 小时,如果通过杭州中转,时间就会变成 2 小时。核心代码是下面这行。

dis[j] = min(dis[j], dis[k] + g[k][j]);

迪杰斯特拉算法的解题思路如下:

1,从起始点开始计算所有和它相连的点(也就是起始点指向的点),计算完之后把起始点标记下(表示已经计算过了)。

2,找出离起始点最近且没有被标记过的点 v ,计算所有和 v 相连且没有被标记过的点,计算完之后把 v 标记下。

3,重复上面的步骤 2 ,直到所有顶点都标记完为止。

2,迪杰斯特拉算法的代码实现

迪杰斯特拉算法使用的是贪心的策略,每次都是从未标记的顶点中找到一个离起始点最近的点,用它来更新所有和它连接且未被标记过的点,代码比较简单,我们来看下。

Java 代码:

private void test() {
int[][] g = {{0, 1, 3, 0, 0, 0},// 图的邻接矩阵。
{0, 0, 1, 4, 2, 0},
{0, 0, 0, 5, 5, 0},
{0, 0, 0, 0, 0, 3},
{0, 0, 0, 1, 0, 6},
{0, 0, 0, 0, 0, 0}};
dijkstra(g, 0);
}


/**
* @param g 图的邻接矩阵
* @param start 起始点
*/
public void dijkstra(int[][] g, int start) {
int n = g.length;// 顶点的个数。
int[] dis = new int[n];// 每个点到起始点的距离
// 起始点到其他所有顶点默认给一个非常大的值,
// 要注意下面加的时候防止出现溢出。
Arrays.fill(dis, Integer.MAX_VALUE >> 1);
dis[start] = 0;// 起始点到自己的值是 0 。
boolean visited[] = new boolean[n];// 标记哪些顶点被访问过
for (int i = 0; i < n; i++) {
int k = -1;// 下一个没被标记且离起始点最近的顶点。
int min = Integer.MAX_VALUE; // min 是 k 到起始点的值。
for (int j = 0; j < n; j++) {// 寻找 k。
if (!visited[j] && dis[j] < min) {
min = dis[j];
k = j;
}
}
visited[k] = true;// 标记
for (int j = 0; j < n; j++) {// 核心代码。
// 顶点 j 没有被标记,并且 k 有到 j 的路径,并且这个路径更近,就更新。
if (!visited[j] && g[k][j] != 0 && dis[k] + g[k][j] < dis[j])
dis[j] = dis[k] + g[k][j];
}
}
// 打印数组dis的值,测试使用。
for (int di : dis)
System.out.print(di + ",");
}

C++ 代码:

/**
* @param g 图的邻接矩阵
* @param start 起始点
*/
void dijkstra(vector> &g, int start) {
const int n = g.size();// 顶点的个数。
vector dis(n, INT_MAX/2);// 每个点到起始点的距离
dis[start] = 0;// 起始点到自己的值是 0 。
vector visited(n, false); // 标记哪些顶点被访问过
for (int i = 0; i < n; i++) {
int k = -1;// 下一个没被标记且离起始点最近的顶点。
int min = INT_MAX; // min 是 k 到起始点的值。
for (int j = 0; j < n; j++) {// 寻找 k。
if (!visited[j] && dis[j] < min) {
min = dis[j];
k = j;
}
}
visited[k] = true;// 标记
for (int j = 0; j < n; j++) {// 核心代码。
// 顶点 j 没有被标记,并且 k 有到 j 的路径,并且这个路径更近,就更新。
if (!visited[j] && g[k][j] != 0 && dis[k] + g[k][j] < dis[j])
dis[j] = dis[k] + g[k][j];
}
}
// 打印数组dis的值,测试使用。
for (int di: dis)
cout << di << ",";
}

3,迪杰斯特拉算法的堆优化

我们看到上面代码中外面的循环是遍历顶点,里面的循环主要是查找离起始点最近的顶点 v ,然后更新和 v 邻接的顶点。

如果这个图是个稀疏图,边特别少的话,在一个个查找很明显效率不高,所以在这种情况下可以使用最小堆来优化下,每次与顶点 v 邻接的点计算完之后把它加入到堆中,下次循环的时候直接弹出堆顶元素即可,它就是离起始点最近的点。

Java 代码:

/**
* 使用堆优化的算法
*
* @param g 图的邻接矩阵
* @param start 起始点
*/
public void dijkstra(int[][] g, int start) {
int n = g.length;// 顶点的个数。
int[] dis = new int[n];// 每个点到起始点的距离
Arrays.fill(dis, Integer.MAX_VALUE >> 1);
dis[start] = 0;// 起始点到自己的值是 0 。
boolean visited[] = new boolean[n];// 标记哪些顶点被访问过
// 创建堆,根据到起始点的距离排序
PriorityQueue pq = new PriorityQueue<>(Comparator.comparingInt(a -> a[0]));
pq.offer(new int[]{0, start});// 起始点到它自己的距离是 0 。
for (int i = 0; i < n; i++) {
if (pq.isEmpty()) break; // 如果堆为空,退出循环
// 每次出队都是离起始点最近且没被标记过的顶点。
int k = pq.poll()[1];
visited[k] = true;// 标记
for (int j = 0; j < n; j++) {// 核心代码。
// 顶点 j 没有被标记,并且 k 有到 j 的路径,并且这个路径更近,就更新。
if (!visited[j] && g[k][j] != 0 && dis[k] + g[k][j] < dis[j]) {
// 如果顶点 j 经过 k 到起始点的距离更近,就更新顶点 j 到
// 起始点的距离,并把它添加到堆中。
dis[j] = dis[k] + g[k][j];
pq.offer(new int[]{dis[j], j});
}
}
}
// 打印数组dis的值,测试使用。
for (int di : dis)
System.out.print(di + ",");
}

C++ 代码:

void dijkstra(vector> &g, int start) {
int n = g.size(); // 顶点的个数
vector dis(n, INT_MAX / 2); // 每个点到起始点的距离
dis[start] = 0; // 起始点到自己的值是 0
vector visited(n, false); // 标记哪些顶点被访问过
// 创建堆,根据到起始点的距离排序
priority_queue int, int>, vector int, int>>, greater<>> pq;
pq.emplace( 0, start); // 起始点到它自己的距离是 0
for ( int i = 0; i < n; i++) {
// 每次出队都是离起始点最近且没被标记过的顶点
if (pq.empty()) break; // 如果堆为空,退出循环
int k = pq.top().second;
pq.pop();
visited[k] = true; // 标记
for ( int j = 0; j < n; j++) { // 核心代码
// 顶点 j 没有被标记,并且 k 有到 j 的路径,并且这个路径更近,就更新
if (!visited[j] && g[k][j] != 0 && dis[k] + g[k][j] < dis[j]) {
// 如果顶点 j 经过 k 到起始点的距离更近,就更新顶点 j 到起始点的距离,并把它添加到堆中
dis[j] = dis[k] + g[k][j];
pq.emplace(dis[j], j);
}
}
}

// 打印数组dis的值,测试使用
for ( int di: dis)
cout << di << ",";
cout << endl;
}

4,为什么迪杰斯特拉算法不能处理带有负权边的图

为什么通过上述的操作可以保证得到的 dis 值最小?因为这里的图是没有负权边的,值只能越加越大,我们不断选择最小值进行标记然后更新和它邻接的点,即贪心的思路,最终保证起始点到每个顶点的值都是最小的。

如果有负权边在使用 Dijkstra 算法就行不通了,如下图所示,其中有负权边。

最后的结果是起始点到顶点 2 的值是 3 ,但实际上如果选择 0->1->2 这条路径的值是 2 ,会更小,所以有负权边并不适合 Dijkstra 算法。如果图是有环的可不可以使用 Dijkstra 算法呢?实际上只要没有负权边无论有环无环都是可以使用 Dijkstra 算法的。

如果有负权边该怎么解决呢?我们可以使用贝尔曼-福特算法(Bellman–Ford)和最短路径快速算法(Shortest Path Faster Algorithm:简称:SPFA),这两种算法虽然可以解决带有负权边的图,但不能解决有负权回路的图,关于这两种算法,后面我们也都会介绍。

笔者简介

博哥,真名:王一博,毕业十多年, 作者,专注于 数据结构和算法 的讲解,在全球30多个算法网站中累计做题2000多道,在公众号中写算法题解800多题,对算法题有自己独特的解题思路和解题技巧,喜欢的可以给个关注,也可以 下载我整理的1000多页的PDF算法文档 。

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
“全班就2个女生表情正常”,廉价毕业照被吐槽,家长咋不管管

“全班就2个女生表情正常”,廉价毕业照被吐槽,家长咋不管管

泽泽先生
2026-06-21 21:16:07
梅西加冕射手王那夜,牵他手入场的9岁男孩说:这是我一生最好的日子

梅西加冕射手王那夜,牵他手入场的9岁男孩说:这是我一生最好的日子

慢享生活集
2026-06-25 00:24:07
历史第二人?巴西三传奇当场开撕!有人力挺梅西有人死磕马拉多纳

历史第二人?巴西三传奇当场开撕!有人力挺梅西有人死磕马拉多纳

云隐南山
2026-06-24 16:30:58
35页PPT疯传:洛阳女子1女谈3男,每天卡时间,都已谈婚论嫁

35页PPT疯传:洛阳女子1女谈3男,每天卡时间,都已谈婚论嫁

烈史
2026-05-30 13:23:41
加勒万河谷冲突后续,我方秘密武器使印军“雪豹计划”瞬间熄火

加勒万河谷冲突后续,我方秘密武器使印军“雪豹计划”瞬间熄火

南冥那只猫
2025-06-20 20:28:28
巴菲特买股票的经验告诉你:目前大盘,空仓等待和满仓踏空的人,到底谁能迎来春天?作为投资者怎么看

巴菲特买股票的经验告诉你:目前大盘,空仓等待和满仓踏空的人,到底谁能迎来春天?作为投资者怎么看

新浪财经
2026-06-24 16:28:37
陪玩陪睡只是皮毛!继手伸进裤子后,又一女星自曝,50多都不放过

陪玩陪睡只是皮毛!继手伸进裤子后,又一女星自曝,50多都不放过

情感大头说说
2026-06-25 05:56:43
签了!4年1.85亿!湖人休赛期第二签,彻底锁死了...

签了!4年1.85亿!湖人休赛期第二签,彻底锁死了...

詹姆斯吧
2026-06-25 03:11:50
北京师范大学人工智能学院严正声明!

北京师范大学人工智能学院严正声明!

新浪财经
2026-06-23 19:41:16
莫言:动不动就生气的人,没有一个是智者|生活多半过得一团糟

莫言:动不动就生气的人,没有一个是智者|生活多半过得一团糟

杏花烟雨江南的碧园
2026-06-19 11:15:03
赖清德妄称“台湾前途只有2300万人可以决定”,国台办回应

赖清德妄称“台湾前途只有2300万人可以决定”,国台办回应

澎湃新闻
2026-06-24 10:36:26
养老金调整6月23日公布,2.2%涨幅能否实现?

养老金调整6月23日公布,2.2%涨幅能否实现?

风月得自难寻
2026-06-24 17:18:23
长沙街头偶遇李湘一家,母女暴瘦到认不出!离婚后仍不离不弃

长沙街头偶遇李湘一家,母女暴瘦到认不出!离婚后仍不离不弃

草莓解说体育
2026-06-24 12:45:03
马斯克身家跌破万亿美元,较高点缩水4430亿美元

马斯克身家跌破万亿美元,较高点缩水4430亿美元

界面新闻
2026-06-24 16:40:41
取材真实事件,北京中考数学卷把课本知识与真实世界联系起来

取材真实事件,北京中考数学卷把课本知识与真实世界联系起来

新京报
2026-06-24 20:27:29
瞒不住了!国家在宁波布下惊天大局,宁波真正的王牌正在悄悄崛起

瞒不住了!国家在宁波布下惊天大局,宁波真正的王牌正在悄悄崛起

吃货的分享
2026-06-25 01:19:18
FIFA又搞双标?裁判未罚下捂嘴说话的贝林厄姆 断案了:2条件少1个

FIFA又搞双标?裁判未罚下捂嘴说话的贝林厄姆 断案了:2条件少1个

风过乡
2026-06-24 22:15:11
NBA冠军J.R.史密斯退役后惊爆:7辆车堆灰,数百万打了水漂,60%球员正重蹈覆辙

NBA冠军J.R.史密斯退役后惊爆:7辆车堆灰,数百万打了水漂,60%球员正重蹈覆辙

我是一个粉刷匠2
2026-06-24 00:50:10
603001,4天2板!最新公告:筹划购买资产,停牌!

603001,4天2板!最新公告:筹划购买资产,停牌!

证券时报e公司
2026-06-25 02:10:06
下一场他会进球!加纳巫师称已解除诅咒,“释放”英格兰队长凯恩

下一场他会进球!加纳巫师称已解除诅咒,“释放”英格兰队长凯恩

全景体育V
2026-06-24 19:06:39
2026-06-25 07:39:00
数据结构和算法
数据结构和算法
专门介绍和写算法题解的号
273文章数 4关注度
往期回顾 全部

科技要闻

豆包专业版上线:定价68-500元每月

头条要闻

小伙酒店按摩从房间坠亡 酒店:用刀威胁技师脱衣服

头条要闻

小伙酒店按摩从房间坠亡 酒店:用刀威胁技师脱衣服

体育要闻

字母哥,会把凯尔特人拆了吗?

娱乐要闻

向佐向佑兄弟合体直播!母子终于和解

财经要闻

逃税23亿:审计署年报直指七家机构

汽车要闻

施鹏泽:为什么奥迪E7X强调座舱气味安全?

态度原创

房产
游戏
亲子
教育
手机

房产要闻

白鹅潭新增优质宅地!沙涌地块对望太古里,容积率仅 2.14

三国望神州:徐晃先遣实测报告+抽取价值分析!和那谁是不有点像

亲子要闻

坐巴士、认盲区,这所幼儿园的交通安全课“干货满满”

教育要闻

2026年高考地理广东卷综合题评析及其答案

手机要闻

REDMI K90至尊版定档,骁龙8至尊版+风冷散热

无障碍浏览 进入关怀版