最短路径算法——Dijkstra算法

文章介绍了Dijkstra算法的创始人,以及算法的基本思想、适用范围和证明。它用于寻找单源最短路径,初始设置和贪心策略是关键。基础版算法基于邻接矩阵,时间复杂度为O(n²),而堆优化版通过优先队列降低到O(mlogn)。
摘要由CSDN通过智能技术生成

1.Dijkstra人物简介

艾兹格·W·迪科斯彻(Edsger Wybe Dijkstra,1930年5月11日~2002年8月6日),计算机科学家,1972年图灵奖获得者,于1959年提出了Dijkstra算法;

2.Dijkstra算法

适用范围:
1.单源最短路径
2.边权非负图
Dijkstra算法基于贪心的思想,dist[x]已经是起点到x的最短路径,我们不断用全局最小值进行标记和扩展,最终可得到起点到每个节点的最短路径长度。

Dijkstra算法证明:

证明略,可参考其他博客
ps:其实我不会( ̄﹏ ̄ )

算法流程:

1.初始化dist[1] = 0,其余节点为正无穷大
2.找出一个未被标记的、dist[x]最小的节点x,然后标记节点x
3.扫描节点x的所有出边(x,y,z),若dist[y] >dist[x] + z,则使用dist[x] + z 更新dist[y]
4.重复上述2~3两个步骤,直到所有节点被标记。

流程图:
1.初始化dist[1] = 0,其余节点为正无穷大
请添加图片描述

2.dist数组中未被标记的、值最小的,是1号节点,将其标记
请添加图片描述
3.扫描1号节点的所有出边,由于dist[3]>dist[1] + 1,说明通过1号节点走一段长度为1的路可以到达3号节点,并且比当前dist[3]中的存储的路要短,所以将dist3更新,则dist[3] = 1;
由于dist[2] > dist[1] + 7, 则 dist[2] = 7;
请添加图片描述
4.(2)dist数组中未被标记的、值最小的,是3号节点,将其标记;
请添加图片描述
5.(3)扫描3号节点的所有出边
由于dist[2]>dist[3] + 2,则 dist[2] = 3;
由于dist[4]>dist[3] + 5,则 dist[4] = 6;
由于dist[6]>dist[3] + 4,则 dist[6] = 5;将其更新;
在这里插入图片描述
6.(2)dist数组中未被标记的、值最小的,是2号节点,将其标记;
在这里插入图片描述
7.(3)扫描2号节点的所有出边
由于dist[4] < dist[2] + 8,则本次不更新;
在这里插入图片描述
8.(2)dist数组中未被标记的、值最小的,是6号节点,将其标记;
在这里插入图片描述
9.(3)扫描2号节点的所有出边
由于dist[4] < dist[6] + 10,则本次不更新;
在这里插入图片描述

10.(2)dist数组中未被标记的、值最小的,是4号节点,将其标记;
在这里插入图片描述
11.(3)扫描4号节点的所有出边
由于dist[5] > dist[4] + 2,则 dist[5] = 8;
在这里插入图片描述
12.(2)dist数组中未被标记的、值最小的,是5号节点,将其标记;
在这里插入图片描述
13.(3)扫描5号节点的所有出边
由于dist[6] < dist[5] + 6,则本次不更新;
在这里插入图片描述
14.所有节点被标记,算法完成

3.Dijkstra算法模板——基础版

dist[i] : 表示从1到i的最短路径为dist[i]
v[i] = ture:表示 i 节点已被标记,false为未标记
A[i][j]:邻接矩阵,表示i节点到j节点的路径长度为A[i][j]
时间复杂度:O(n²)

void dijkstra() {
memset(dist,0x3f, sizeof(dist)); // dist数组,初始化为无穷大
memset(v, 0sizeof(v) );//节点标记数组初始化
d[1] = 0;//将源点的距离初始化为0
for (int i = 1; i < n; i++) { //重复进行n-1次
	int x = 0;
	//找到未标记节点中dist值最小的节点号,并记录到x中
	for (int j = 1; j<= n;j++)
		if (!v[j] && (x == 0 || d[j] < d[x])) 
			x = j;
	v[x] = 1;//将其标记
	//用全局最小值点×更新其他节点
	for (int y = 1; y <= n; y++)
		d[y] = min(d[y], d[x] + A[x][y]);
	}
}

4.Dijkstra算法模板——堆优化版

上述代码时间复杂度为O(n²),主要瓶颈就在于寻找全局最小值的过程,在上述代码中,寻找全局最小值时,循环了n次,此处可以用二叉堆(priority_queue)对dist数组进行维护,用O(log n)的时间找到最小值,执行一条边的扩展和更新,最终在O(mlog n)的时间内实现Dijkstra算法.(m条边,n个节点)

priority_queue< pair<int ,int> > q;
void add(int x,int y,int z)//邻接表存储图
{
    ver[++tot]=y;edge[tot]=z;
    Next[tot]=head[x];head[x]=tot;
}
void dijkstra(){
    memset(dist,0x3f,sizeof dist);
    memset(v,0,sizeof v);
    dist[1]=0;
    q.push(make_pair(0,1));
    while(q.size()){
        //取出堆顶
        int x=q.top().second;
        q.pop();
        if(v[x]) continue;
        v[x]=1;
        //扫描所有出边
        for(int i=head[x];i;i=Next[i])
        {
            int y=ver[i],z=edge[i];
            if(dist[y]>dist[x]+z){
                dist[y]=dist[x]+z;
                q.push(make_pair(-dist[y],y));//加入堆
            }
        }
    }
}

注意:为什么加入堆时用-dist[y]呢? 由于priority_queue默认是从大到小的顺序排列的,故将所有dist取负数时,堆顶的dist是最小的。也可以自定义比较函数使得该堆从小到大排列:
priority_queue<int, vector< int >, greater< int > > q

5.参考

《算法竞赛进阶指南》——李煜东

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值