迪杰斯特拉

Dijkstra算法是典型的算法。Dijkstra算法是很有代表性的算法。Dijkstra一般的表述通常有两种方式,一种用永久和临时标号方式,一种是用OPEN, CLOSE表的方式,这里均采用永久和临时标号的方式。注意该算法要求图中不存在负权边。

基本定义

Dijkstra(迪杰斯特拉)算法是典型的单源最短路径算法,用于计算一个节点到其他所有节点的最短路径。主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。Dijkstra算法是很有代表性的最短路径算法,在很多专业课程中都作为基本内容有详细的介绍,如数据结构,图论,运筹学等等。Dijkstra一般的表述通常有两种方式,一种用永久和临时标号方式,一种是用OPEN, CLOSE表的方式,这里均采用永久和临时标号的方式。注意该算法要求图中不存在负权边。

相关原理

首先,引进一个辅助向量D,它的每个分量D表示当前所找到的从始点v到每个终点vi的最短路径的长度。如D[3]=2表示从始点v到终点3的路径相对最小长度为2。这里强调相对就是说在算法过程中D的值是在不断逼近最终结果但在过程中不一定就等于最短路径长度。它的初始状态为:若从v到vi有弧,则D为弧上的权值;否则置D为∞。显然,长度为 D[j]=Min{D | vi∈V} 的路径就是从v出发的长度最短的一条最短路径。此路径为(v,vj)。 那么,下一条长度次短的最短路径是哪一条呢?假设该次短路径的终点是vk,则可想而知,这条路径或者是(v,vk),或者是(v,vj,vk)。它的长度或者是从v到vk的弧上的权值,或者是D[j]和从vj到vk的弧上的权值之和。 一般情况下,假设S为已求得最短路径的终点的集合,则可证明:下一条最短路径(设其终点为X)或者是弧(v,x),或者是中间只经过S中的顶点而最后到达顶点X的路径。因此,下一条长度次短的最短路径的长度必是D[j]=Min{D | vi∈V-S} 其中,D或者是弧(v,vi)上的权值,或者是Dk和弧(vk,vi)上的权值之和。迪杰斯特拉算法描述如下: 1)arcs表示弧上的权值。若不存在,则置arcs为∞(在本程序中为MAXCOST)。S为已找到从v出发的最短路径的终点的集合,初始状态为空集。那么,从v出发到图上其余各顶点vi可能达到的最短路径长度的初值为D=arcs[Locate Vex(G,v),i] vi∈V 2)选择vj,使得D[j]=Min{D | vi∈V-S} 3)修改从v出发到集合V-S上任一顶点vk可达的最短路径长度。

普通迪杰斯特拉模板

时间复杂是 O(n2+m)O(n2+m), nn 表示点数,mm 表示边数

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];
}

自己的

#include<bits/stdc++.h>
using namespace std;
struct node{
int dian;
int bian;
}sss;
int n,m,s;
vector<node>adj[100001];
int d[100001];
bool dd[100001]={false};
void dijisite(int s){
fill(d,d+n+1,0x7fffffff);
d[s]=0;
for(int i=1;i<=n;i++){
int u=-1,minn=999999999;
for(int j=1;j<=n;j++){
if(dd[j]==false&&d[j]<minn){
u=j;
minn=d[j];
}
}
if(u==-1){
return ;
}
dd[u]=true;
for(int j=0;j<adj[u].size();j++){
int dian=adj[u][j].dian;
if(dd[dian]==false){
d[dian]=min(d[dian],d[u]+adj[u][j].bian);
}
}
}
}
int main(){
cin>>n>>m>>s;
for(int i=1;i<=m;i++){
int x,y,z;
cin>>x>>y>>z;
sss.dian=y;
sss.bian=z;
adj[x].push_back(sss);
}
dijisite(s);
for(int i=1;i<=n;i++){
cout<<d[i]<<" ";
}
return 0;
}

堆优化的迪杰斯特拉模板

时间复杂度 O(mlogn)O(mlogn), nn 表示点数,mm 表示边数

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];
}

 自己的代码

#include<bits/stdc++.h>
using namespace std;
long long n,m,s;
struct node{
long long to;
long long w;
inline bool operator < (const node &b)const{
return w>b.w;
}
}sss;
vector<node>adj[1000005];
priority_queue<node>q;
long long d[1000005];
bool dd[1000005]={false};
int main(){
cin>>n>>m>>s;
memset(d,127,sizeof(d));
for(long long i=1;i<=m;i++){
long long x,y,z;
cin>>x>>y>>z;
sss.to=y;
sss.w=z;
adj[x].push_back(sss);
}
d[s]=0;
q.push((node){s,0});
while(q.empty()!=true){
int x=q.top().to;
q.pop();
if(dd[x]==true){
continue;
}
dd[x]=true;
for(long long i=0;i<adj[x].size();i++){
int nx=adj[x][i].to,w=adj[x][i].w;
if(d[nx]>w+d[x]){
d[nx]=w+d[x];
q.push((node){nx,d[nx]});
}
}
}
for(long long i=1;i<=n;i++){
cout<<d[i]<<" ";
}
return 0;
}

SPFA 算法

SPFA 算法是 Bellman-Ford算法 的队列优化算法的别称,通常用于求含负权边的单源最短路径,以及判负权环。SPFA 最坏情况下复杂度和朴素 Bellman-Ford 相同,为 O(VE)。

基本信息

中文名称

贝尔曼-福特算法的队列优化形式

外文名称 Bellman-Ford using queue optimization

简称

SPFA

全称

Shortest Path Faster Algorithm

用途

若给定的图存在负权边,类似Dijkstra算法等算法便没有了用武之地,SPFA算法便派上用场了。简洁起见,我们约定加权有向图G不存在负权回路,即最短路径一定存在。用数组d记录每个结点的最短路径估计值,而且用邻接表来存储图G。我们采取的方法是动态逼近法:设立一个先进先出的队列用来保存待优化的结点,优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止。

为了避免最坏情况的出现,在正权图上应使用效率更高的Dijkstra算法。

定理:

只要最短路径存在,上述SPFA算法必定能求出最小值。证明:每次将点放入队尾,都是经过松弛操作达到的。换言之,每次的优化将会有某个点v的最短路径估计值d[v]变小。所以算法的执行会使d越来越小。由于我们假定图中不存在负权回路,所以每个结点都有最短路径值。因此,算法不会无限执行下去,随着d值的逐渐变小,直到到达最短路径值时,算法结束,这时的最短路径估计值就是对应结点的最短路径值。

实际上,如果一个点进入队列达到n次,则表明图中存在负环,没有最短路径。

对SPFA的一个很直观的理解就是由无权图的BFS转化而来。在无权图中,BFS首先到达的顶点所经历的路径一定是最短路(也就是经过的最少顶点数),所以此时利用数组记录节点访问可以使每个顶点只进队一次,但在带权图中,最先到达的顶点所计算出来的路径不一定是最短路。一个解决方法是放弃数组,此时所需时间自然就是指数级的,所以我们不能放弃数组,而是在处理一个已经在队列中且当前所得的路径比原来更好的顶点时,直接更新最优解。

SPFA算法有两个优化策略SLF和LLL--SLF:Small Label First 策略,设要加入的节点是j,队首元素为i,若dist(j)<dist(i),则将j插入队首,否则插入队尾; LLL:Large Label Last 策略,设队首元素为i,队列中所有dist值的平均值为x,若dist(i)>x则将i插入到队尾,查找下一元素,直到找到某一i使得dist(i)<=x,则将i出队进行松弛操作。SLF 和 LLF 在随机数据上表现优秀,但是在正权图上最坏情况为 O(VE),在负权图上最坏情况为达到指数级复杂度。

模板

spfa 算法(队列优化的Bellman-Ford算法)

时间复杂度 平均情况下 O(m)O(m),最坏情况下 O(nm)O(nm), nn 表示点数,mm 表示边数

int n;      // 总点数
int h[N], w[N], e[N], ne[N], idx; // 邻接表存储所有边
int dist[N]; // 存储每个点到1号点的最短距离
bool st[N]; // 存储每个点是否在队列中 // 求1号点到n号点的最短路距离,如果从1号点无法走到n号点则返回-1
int spfa()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0; queue<int> q;
q.push(1);
st[1] = true; while (q.size())
{
auto t = q.front();
q.pop(); st[t] = false; for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if (dist[j] > dist[t] + w[i])
{
dist[j] = dist[t] + w[i];
if (!st[j]) // 如果队列中已存在j,则不需要将j重复插入
{
q.push(j);
st[j] = true;
}
}
}
} if (dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}

  

自己的代码

#include<bits/stdc++.h>
using namespace std;
struct node{
int to;
int w;
}sss;
vector<node>adj[1000001];
queue<int>q;
bool visit[1000001];
int n,m,s;
long long cost[1000001];
void spfa(){
memset(visit,false,sizeof(visit));
q.push(s);
visit[s]=true;
cost[s]=0;
while(q.empty()!=true){
int t=q.front();
visit[t]=true;
for(int i=0;i<adj[t].size();i++){
int to=adj[t][i].to;
if(cost[to]>cost[t]+adj[t][i].w){
cost[to]=cost[t]+adj[t][i].w;
if(visit[to]==false){
visit[to]=true;
q.push(to);
}
}
}
visit[t]=false;
q.pop();
}
}
int main(){
cin>>n>>m>>s;
for(int i=1;i<=m;i++){
int a,b,c;
cin>>a>>b>>c;
sss.to=b;
sss.w=c;
adj[a].push_back(sss);
cost[i]=0x7fffffff;
}
spfa();
for(int i=1;i<=n;i++){
cout<<cost[i]<<" ";
}
return 0;
}
05-26 22:45