dijkstra算法的基本总结
我自己其实也没做特别多的题目(也就十道左右,不敢说,了解的很深)
不过,这个算法确实很牛叉
在下都服了,特别是刚学完就去做题,发现只能做新手村的最短路,后面研究了好一会,才会做一下不是新手村 的题,感觉智商不够用,升天吧!!!!
本人还没看spfa,floyd算法,一直在搞dijkstra算法,难过
开始吧
-
dijkstra本人(挺好看滴 0.0)
-
什么是dijkstra算法?
就是求最短路的一种贪心策略
通过这个算法来得到我们要的最短路径 -
dijkstra算法的原理
我简单的说一下,其实这个方法很简单。
代码也很简单,思维真心简单,网上讲的是天花乱坠,代码也是,各有特色。
(还是y总代码厉害,有简单,有明白)
其实也挺简单的,就是从起始点出发,将其他点变成无穷,然后讲起始点能接触的点,延伸去其他点,每次取最短的距离,以此类推
简单来说就是说,谁短延伸谁,直到找到需要的点,且此时一定是最短的点,如果你们不行,可以去网上找证明,反正我是不会证的,别问我
例如 图示 1 和6,4,3接触,就更新6,4,3的距离,然后3本身距离加6 的距离明显短1到3,所有更新1到6的距离为6,原本为10。以此类推
好了,废话不多说。
这里有两种版本的dijkstra算法,一个针对多边,另一个针对普遍的最短路。
dijkstra算法只针对单源最短路问题
核心代码(模板)
朴素版
int g[N][N]; // 存储每条边
int dist[N]; // 存储1号点到每个点的最短距离
bool st[N]; // 存储每个点的最短路是否已经确定
// 求1号点到n号点的最短路,如果不存在则返回-1
int dijkstra()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
for (int i = 0; i < n - 1; i ++ )
{
int t = -1; // 在还未确定最短路的点中,寻找距离最小的点
for (int j = 1; j <= n; j ++ )
if (!st[j] && (t == -1 || dist[t] > dist[j]))
t = j;
// 用t更新其他点的距离
for (int j = 1; j <= n; j ++ )
dist[j] = min(dist[j], dist[t] + g[t][j]);
st[t] = true;
}
if (dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}
堆优化的版本 (我觉得这个版本用的比较多,毕竟很多题都是稀梳图)
typedef pair<int, int> PII;
int n; // 点的数量
int h[N], w[N], e[N], ne[N], idx; // 邻接表存储所有边
int dist[N]; // 存储所有点到1号点的距离
bool st[N]; // 存储每个点的最短距离是否已确定
// 求1号点到n号点的最短距离,如果不存在,则返回-1
int dijkstra()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
priority_queue<PII, vector<PII>, greater<PII>> heap;
heap.push({0, 1}); // first存储距离,second存储节点编号
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});
}
}
}
if (dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}
针对不同情况我们用不同的模板
其实模板真不重要,重要的是思想,你明白其中的过程和思维,就连背篼不需要背
先来模板题,看看吧
最短路模板题
这里我们就是用dijkstra算法求起点到所有点的最短距离
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
typedef pair<int,int> PII;
const int N=1e5+10,M=2e5+10;
int n,m,s;
int h[N],e[M],ne[M],w[M],idx; //邻接表
int d[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(){
memset(d,0x3f,sizeof d);
d[s]=0;
priority_queue<PII,vector<PII>,greater<PII> > heap;
heap.push({d[s],s});
while(!heap.empty()){
auto t=heap.top();
heap.pop();
int u=t.second,v=t.first;
if(st[u]) continue;
st[u]=true;
for(int i=h[u];i!=-1;i=ne[i]){
int j=e[i];
if(d[j]>d[u]+w[i]){
d[j]=d[u]+w[i];
heap.push({d[j],j});
}
}
}
}
int main(){
cin>>n>>m>>s;
memset(h,-1,sizeof h);
for(;m;m--){
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
}
dijkstra();
for(int i=1;i<=n;i++) printf("%d ",d[i]==0x3f3f3f3f?0x7fffffff:d[i]);
return 0;
}
除了求最短路,还可以求最长路
最小花费
这里就是求乘积最大,来求答案
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=2010;
double g[N][N]; //邻接矩阵
int n,m,l,r;
double d[N];
bool st[N];
double dijkstra(){
//memset(d,0,sizeof d);
d[l]=1;
for(int i=0;i<n-1;i++){
int t= -1;
for(int j=1;j<=n;j++)
if(!st[j]&&(t==-1||d[t]<d[j]))
t=j;
for(int j=1;j<=n;j++)
d[j]=max(d[j],d[t]*g[t][j]);
st[t]=true;
if(t==r) break;
}
return d[r];
}
int main(){
cin>>n>>m;
//memset(g,0,sizeof g);
for(;m;m--){
int a,b,c;
cin>>a>>b>>c;
g[a][b]=g[b][a]=(100.0-c)*1.0/100;
}
cin>>l>>r;
printf("%.8lf\n",100.0/dijkstra());
return 0;
}
对于dijkstra算法的核心,就是求最短路,但是dijkstra算法需要变通
这里有三种变通
1 超级源点
2 超级汇点
3 反向建边
4 多权边
我们先来一道关于反向求解问题
反向求解
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=60;
int g[N][N];
int d[N];
bool st[N];
int m;
int get(char a){
if(a<='Z') return a-'A'+1;
return a-'a'+27;
}
int dijkstra(){
memset(d,0x3f,sizeof d);
d[26]=0;
for(int i=0;i<52;i++){
int t=-1;
for(int j=1;j<=52;j++)
if(!st[j]&&(t==-1||d[t]>d[j]))
t=j;
st[t]=true;
for(int j=1;j<=52;j++)
d[j]=min(d[j],d[t]+g[t][j]);
}
int k=1;
for(int i=2;i<26;i++)
if(d[k]>d[i]) k=i;
return k;
}
int main(){
cin>>m;
memset(g,0x3f,sizeof g);
for(;m;m--){
char a,b;
int c;
cin>>a>>b>>c;
int x=get(a),y=get(b);
g[x][y]=g[y][x]=min(g[x][y],c);
}
int k=dijkstra();
cout<<char('A'+k-1)<<" "<<d[k];
return 0;
}
来一道超级源点题
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;
typedef pair<int,int> PII;
const int N=1e6+10;
int n,m,k,q;
int h[N],e[N],ne[N],w[N],idx;
int d[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(){
memset(d,0x3f,sizeof d);
priority_queue<PII,vector<PII>,greater<PII> > heap;
heap.push({0,0});
d[0]=0;
while(!heap.empty()){
auto t=heap.top();
heap.pop();
int u=t.second,v=t.first;
for(int i=h[u];i!=-1;i=ne[i]){
int j=e[i];
if(d[j]>d[u]+w[i]){
d[j]=d[u]+w[i];
heap.push({d[j],j});
}
}
}
}
int main(){
scanf("%d%d",&n,&m);
memset(h,-1,sizeof h);
while(m--){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
add(b,a,c);
}
scanf("%d",&k);
while(k--){
int a;
scanf("%d",&a);
add(0,a,0);
}
dijkstra();
scanf("%d",&q);
while(q--){
int a;
scanf("%d",&a);
printf("%d\n",d[a]);
}
return 0;
}
这里再补充一道多权边的dijksstra算法
因为是稀疏图,我们用邻接表
#include<iostream>
#include<cstring>
#include<algorithm>
#include<stack>
using namespace std;
const int N=2010;
int h[N],e[N],ne[N],w[N],o[N],idx;
int dist[N],cost[N],p[N];
bool st[N];
int n,m,s,d;
void add(int a,int b,int c,int d){
e[idx]=b;ne[idx]=h[a];w[idx]=c;o[idx]=d;h[a]=idx++;
}
void dijkstra(){
memset(dist,0x3f,sizeof dist);
memset(cost,0x3f,sizeof cost);
dist[s]=0,cost[s]=0;
for(int i=0;i<n;i++){
int t=-1;
for(int j=0;j<n;j++)
if(!st[j]&&(t==-1||dist[t]>dist[j]))
t=j;
st[t]=true;
for(int j=h[t];j!=-1;j=ne[j]){
int k=e[j];
if(dist[k]>dist[t]+w[j]){
dist[k]=dist[t]+w[j];
cost[k]=cost[t]+o[j];
p[k]=t;
}
else if(dist[k]==dist[t]+w[j]){
if(cost[k]>cost[t]+o[j]){
cost[k]=cost[t]+o[j];
p[k]=t;
}
}
}
}
}
int main(){
cin>>n>>m>>s>>d;
memset(h,-1,sizeof h);
for(;m;m--){
int a,b,c,d;
cin>>a>>b>>c>>d;
add(a,b,c,d);
add(b,a,c,d);
}
dijkstra();
stack<int> l;
for(int i=d;i!=s;i=p[i]) l.push(i);
l.push(s);
while(l.size()) cout<<l.top()<<" ",l.pop();
cout<<dist[d]<<" "<<cost[d];
return 0;
}
我做的不是很多,也找不到很多简单的题,我很无奈,做不来。伤心太平洋
本人不怎么喜欢用邻接矩阵,主要是邻接矩阵太占空间(当然邻接矩阵方便且快)
这里我给你们看一下我的思路来源于这篇博客
-> 反向建边,超级源点,超级汇点
就是这篇博客让我发现,原来dijkstra算法的另一种开启方式,哈哈
感谢此博主,感谢感谢