Dijkstra算法
导言
Dijkastra以其优秀的复杂度O(mlogm)被我们所熟知。成为学习最短路的必备算法。
基本信息
迪杰斯特拉算法(Dijkstra)是由荷兰计算机科学家狄克斯特拉于1959年提出的,因此又叫狄克斯特拉算法。是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题。迪杰斯特拉算法主要特点是从起始点开始,采用贪心算法的策略,每次遍历到始点距离最近且未访问过的顶点的邻接节点,直到扩展到终点为止。
别名:Dij,迪杰斯特拉算法。
算法介绍
Dijkstra运用贪心策略,每次选择离此点最近的下一个点进行松弛操作——保证了操作的有效性。
主要准备工作:
- int/long long型的dis数组,统计起点到各个点的最短距离。
- bool型的vis数组,为已经确定最短距离的点打上标记,避免重复运算。
- 邻接矩阵或者邻接表(邻接矩阵建议用vector双重嵌套)。
- 按照题意读入,并将边的关系加入邻接矩阵或邻接表。
算法主要步骤:
- 将dis数组全部重置为INT_MAX(就最大值)——memset和for初始化都可。
- dis[起点]=0;
下面开始,在for循环内:for(int i=1;i<=n-1;i++)
- 设置一个变量,for循环枚举与i相连的点的距离最小值(被选中过要跳过),并记录下节点编号。
- 将最小值最小的点打上标记,杜绝重复运算。vis[相应的节点] = 1;
- 将选中的点相连的点进行松弛操作。
根据这样的操作,我们可以保证每次选中的点 t 不可能再被松弛:我们令 dis[i] 为 i 到起点 s 的已知最短距离,若节点 t 已经被选中过,说明 dis[t] 一定小于 ( now 为当前选中进行松弛操作的节点),不然 now 应该比 t 先被选中。所以一个节点被选中后,说明其 dis 值已经确定,不会在进行更改。
代码实现:
for(int i=1;i<=n-1;i++){
long long t, Min = 0x7fffffff/2;
for(int j=1;j<=n;j++){//寻找距离起点最近的节点
if(vis[j]) continue ; //被选中过则跳过
if(dis[j] < Min){
Min = dis[j];//更新最小值
t = j;//更新t节点
}
}
dis[t] = 1;//标记
for(int j=0;j<v[t].size();j++){//枚举与t相邻的节点
long long k=v[t][j].v;
if(dis[k] > dis[t]+v[t][j].w){//松弛操作
dis[k] = dis[t]+v[t][j].w;
}
}
}
算法优化
这时候,大家肯定会发现——这,这时间复杂度……。
呵呵,不用惊讶,未优化前的Dijkstra就是比Floyd稍好一点的算法而已——O(n2)的复杂度。
所以我们要:提高找到最小值的效率——就用堆!!!
现在我们共有两种选择:1. 直接声明小根堆:priority_queue<int, vector<int>, greater<int>>;——但我们也需要节点编号啊!没有关系,我们可以套用pair<int, int> ,以下为示例:
typedef pair<int,int> PII;
priority_queue<PII, vector<PII>, greater<PII>> heap;
//或者
struct node{
int weight, site;
}
priority_queue<node, vector<node>, greater<node>>;
2.使用重载运算符
struct priority
{
int ans;
int id;
bool operator <(const priority &x)const
{
return x.ans<ans;
}
};
priority_queue<priority> q;
优化后,我们只需要push({x,x}) 或 push(makepair(x,x)) —— 时间复杂度直线下降!
代码示例
小根堆实现
#include <vector>
#include <queue>
#include <limits>
using namespace std;
typedef pair<int, int> PII;
const int N = 1e5 + 10;
int h[N], e[N], ne[N], w[N], idx;
int dist[N];
bool st[N];
void add(int a, int b, int c) {
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
void dijkstra(int n, int start) {
priority_queue<PII, vector<PII>, greater<PII>> heap;
fill(dist, dist + n, numeric_limits<int>::max());
dist[start] = 0;
heap.push({0, start});
while (heap.size()) {
auto t = heap.top();
heap.pop();
int ver = t.second, distance = t.first;
if (st[ver]) continue;
st[ver] = true;
for (int i = h[ver]; i != -1; i = ne[i]) {
int j = e[i];
if (dist[j] > distance + w[i]) {
dist[j] = distance + w[i];
heap.push({dist[j], j});
}
}
}
}
// 使用示例
int main() {
// 初始化图
int n = 5; // 节点总数
memset(h, -1, sizeof h);
add(0, 1, 1);
add(0, 2, 2);
add(1, 2, 1);
add(1, 3, 2);
add(2, 4, 1);
add(3, 4, 2);
// 运行Dijkstra算法
dijkstra(n, 0);
// 输出结果
for (int i = 0; i < n; ++i) {
printf("起点到%d的最短路径长度为:%d\n", i, dist[i]);
}
return 0;
}
重载运算符实现
#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
using namespace std;
inline int read()
{
char ch=getchar();
int x=0,f=1;
while((ch>'9'||ch<'0')&&ch!='-')
ch=getchar();
if(ch=='-')
{
f=-1;
ch=getchar();
}
while('0'<=ch&&ch<='9')
{
x=x*10+ch-'0';
ch=getchar();
}
return x*f;
}
int mod=100003;
int n,m,x,y,tot=0;
const int N=1000005,M=4000005;
int head[N],to[M],nxt[M],d[N],ans[N];
bool p[N];
priority_queue< pair< int,int > > q;
void add(int x,int y)
{
to[++tot]=y;
nxt[tot]=head[x];
head[x]=tot;
}
int main()
{
n=read();m=read();
for(int i=1;i<=m;i++)
{
x=read();y=read();
add(x,y);
add(y,x);
}
for(int i=1;i<=n;i++)
{
d[i]=1e9;p[i]=0;
}
d[1]=0;ans[1]=1;
q.push(make_pair(0,1));
while(q.size())
{
x=q.top().second;
q.pop();
if(p[x]) continue;
p[x]=1;
for(int i=head[x];i;i=nxt[i])
{
y=to[i];
if(d[y]>d[x]+1)
{
d[y]=d[x]+1;
ans[y]=ans[x];
q.push(make_pair(-d[y],y));
}
else if(d[y]==d[x]+1)
{
ans[y]+=ans[x];
ans[y]%=mod;
}
}
}
for(int i=1;i<=n;i++)
printf("%d\n",ans[i]);
return 0;
}
谢谢大家的阅读——最短路题题AC!!!
浙公网安备 33010602011771号