最短路径算法——Dijkstra

目录

思路与过程

查找做法1.暴力

 查找做法2.二叉堆

查找做法3.优先队列

查找做法4.set

查找做法5.斐波那契堆

查找做法6.线段树


我们今天讲一讲另一种求单源最短路径(非负权图)的算法, Dijkstra 。

思路与过程

Dijkstra 也是要使用松弛操作的,如果不太懂松弛操作,可以看看 Bellman-Ford 中松弛操作的解释。

在讲解 Dijkstra 算法之前,我们先看看一些定义。

n 代表顶点的个数, m 代表边数。

w(u,v) 代表从 u 到 v 的边的边权;

s 表示起点,如果有终点则用 t 表示。

定义 dist ( u ) 代表我们当前求出的 s 到 u 的最短路径的长度。

我们还需要一个集合 A ,满足对于所有集合 A 中的顶点 x ,我们都已经计算出了从 s 到 x 的最短路径,dist ( x ) 存储的就是最短路径的长度。

我们再来看看 Dijkstra 算法的过程。

我们初始化 dist(s)=0 ,其他的点为无穷大。接下来,我们重复进行以下操作:

        1.将一个距离起点最近的,同时还不在 A 里的顶点加入集合 A 。

        2.同时,利用这个点所有连出去的边,通过松弛操作去尝试更新其他点的 dist 。

当没有新的顶点可以加入 A 时,算法结束。

那么,我们算法的时间复杂度是多少呢?

操作2的总时间复杂度是 O(m) 。

我们在操作1中,要查距起点最近的顶点,有好几种做法,做法的不同直接导致了效率的不同。

查找做法1.暴力

        我们直接暴力寻找距离起点最近的结点,也是最短路长度最小的结点。这样的话,操作1的时间复杂度为 O(n^{2}) ,总时间复杂度为 O(n^{2}+m) 。

        代码:

#include<bits/stdc++.h>
using namespace std;
struct node{
	int v,w;
	node(int _v,int _w)
	{
		v=_v;
		w=_w;
	};
};
int n,m;
vector<node> vec[1000010];
int dist[1000010];
bool vis[1000010];
void dijkstra(int s,int t)
{
	memset(vis,false,sizeof(vis));
	memset(dist,127,sizeof(dist));
	dist[1]=0;
	for(;;)
	{
		int x=-1;
		for(int i=1;i<=n;i++)
		{
			if(!vis[i]&&dist[i]<1<<30)
			{
				if(x==-1||dist[i]<dist[x])
				{
					x=i;
				}
			}
		}
		if(x==-1||x==t)
		{
			break;
		}
		vis[x]=true;
		for(auto i:vec[x])
		{
			dist[i.v]=min(dist[i.v],dist[x]+i.w);
		}
	}
	if(dist[t]<1<<30)
	{
		cout<<dist[t]<<" ";
	}
	else
	{
		cout<<-1<<" ";
	}
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int u,v,w;
		cin>>u>>v>>w;
		vec[u].push_back(node(v,w));
	}
	for(int i=1;i<=n;i++)
	{
		dijkstra(1,i);
	}
	return 0;
}

 查找做法2.二叉堆

        我们每成功松弛一条边 ( u , v ) ,就将 u 插入二叉堆(如果 u 已经在二叉堆里了,我们就修改相应元素的权值就行了)。操作1我们可以直接取堆顶点就行了。一共有 m 次修改或插入, n 次删除堆顶的操作,每次修改、插入或删除的时间复杂度都是 O(logn) ,所以我们总的时间复杂度就是 O((n+m)logn) 。

查找做法3.优先队列

        和二叉堆差不多,但使用优先队列时,如果同一个点的最短路被更新多次,因为先前更新时插入的元素不能被删除,也不能被修改,只能留在优先队列中,故优先队列内的元素个数有 m 个,时间复杂度为 O(mlogm ) 。

        代码:
 

#include<bits/stdc++.h>
using namespace std;
struct edge
{
	int v,w;
};
struct node
{
	int dist,x;
	bool operator>(const node &a)const
	{
		return dist>a.dist;
	}
};
priority_queue<node,vector<node>,greater<node> >q;
int n,m;
vector<edge>vec[100010];
int dist[100010];
bool vis[100010];
void dijkstra(int s)
{
	memset(dist,127,sizeof(dist));
	dist[s]=0;
	q.push({dist[s],s});
	while(!q.empty())
	{
		int x=q.top().x;
		q.pop();
		if(vis[x])
		{
			continue;
		}
		vis[x]=true;
		for(auto i:vec[x]) {
			int v=i.v;
			int w=i.w;
			if(dist[v]>dist[x]+w)
			{
				dist[v]=dist[x]+w;
				q.push({dist[v],v});
			}
		}
	}
	for(int i=1;i<=n;i++)
	{
		cout<<dist[i]<<" ";
	}
}
int main()
{
	cin>>n>>m;
	while(m--)
	{
		int u,v,w;
		cin>>u>>v>>w;
		vec[u].push_back({v,w});
	}
	dijkstra(1);
	return 0;
}

查找做法4.set

        就是把优先队列换成了 set 啦,其他的一模一样。

        代码:
 

#include<bits/stdc++.h>
using namespace std;
struct node{
	int v,w;
	node(int _v,int _w)
	{
		v=_v;
		w=_w;
	};
};
int n,m;
vector<node> vec[1000010];
int dist[1000010];
set<pair<int,int> >ss;
void dijkstra(int s,int t)
{
	memset(dist,127,sizeof(dist));
	dist[1]=0;
	ss.clear();
	for(int i=1;i<=n;i++)
	{
		ss.insert({dist[i],i});
	}
	for(;!ss.empty();)
	{
		int x=ss.begin()->second;
		ss.erase(ss.begin());
		if(dist[x]>1<<30||x==t)
		{
			break;
		}
		for(auto i:vec[x])
		{
			if(dist[x]+i.w<dist[i.v])
			{
				ss.erase({dist[i.v],i.v});
				dist[i.v]=dist[x]+i.w;
				ss.insert({dist[i.v],i.v});
			}
		}
	}
	if(dist[t]<1<<30)
	{
		cout<<dist[t]<<" ";
	}
	else
	{
		cout<<-1<<" ";
	}
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int u,v,w;
		cin>>u>>v>>w;
		vec[u].push_back(node(v,w));
	}
	for(int i=1;i<=n;i++)
	{
		dijkstra(1,i);
	}
	return 0;
}

查找做法5.斐波那契堆

        也和前面的也差不多,只不过是斐波那契堆插入的时间复杂度是 O(1 ) ,所以总时间复杂度是 O(nlogn+m) ,但是它写起来比较困难。

查找做法6.线段树

        还是和二叉堆差不多,插入操作改成在线段树上执行单点修改,而操作1就是线段树上的全局查询最小值。时间复杂度 O(mlogn ) 。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值