单源最短路问题:
在无向图 G=(V,E) 中,假设每条边E[i] 的长度为w[i],找到由顶点V0 到其余各点的最短路径。
Dijkstra(迪杰斯特拉)算法是典型的单源最短路径算法,注意该算法要求图中不存在负权边。
基本思路:
a.初始时,S只包含源点,即S={v},v的距离为0。U包含除v外的其他顶点,即:U={其余顶点},若v与U中顶点u有边,则<u,v>正常有权值,若u不是v的出边邻接点,则<u,v>权值为∞。
b.从U中选取一个距离v最小的顶点k,把k,加入S中(该选定的距离就是v到k的最短路径长度)。
c.以k为新考虑的中间点,修改U中各顶点的距离;若从源点v到顶点u的距离(经过顶点k)比原来距离(不经过顶点k)短,则修改顶点u的距离值,修改后的距离值的顶点k的距离加上边上的权。
d.重复步骤b和c直到所有顶点都包含在S中。
算法概要:
伪代码:
算法分析:
主要的耗费在第8步,查找最小标记的顶点耗费时间Θ(n),又第8步进行了n次,总的耗费是Θ(n2)。所以算法的时间复杂度是Θ(n2)。
C++代码:
const int mxn = 200;
const int mxe = 10050;
int dis[mxn]; //点到起始点的距离。
int vis[mxn]; //标记点是否在s集合中。
int g[mxn][mxn]; //用邻接矩阵表示边。
int n, m;
void dijkstra(int s) //s为源点结果得出的dis[k]为s点到k点的距离
{
memset(vis, 0, sizeof(vis)); //也可循环只初始化0到n的点,节省时间
memset(dis, 0x3f, sizeof(dis)); //0x3f不好用于判断,可改为10e6
vis[s] = 1;
dis[s] = 0; //初始化1为起始点,在集合s中,与起始点距离为0.出1外的都置初值vis为0,dis为正无穷。
int now = s;
for (int i = 1; i < n; ++i)
{
//更新与now点邻接的点的标记
for (int j = 1; j <=n; ++j)
if (g[now][j]) //now与j点邻接
dis[j] = min(dis[j], dis[now] + g[now][j]); //更新j到起始点的距离
//查找未访问过的标记最小的点作为now点
for (int j = 1; j <= n; ++j)
if (!vis[j] && (dis[j] < dis[now]||vis[now]) ) //若j点不在集合s中,且j到起始点的距离小于当前到起始点的距离(或now点已经在集合中)
now = j; //更新当前的点
vis[now] = true; //放入集合s
}
}
堆优化:
堆优化可以把时间复杂度从Θ(n2)减少到O(mlogn)。
我们用一个小顶堆来存储顶点的标记,主要的耗费在堆的操作上,每个堆运算耗费O(logn),算法共进行了n-1次插入、n-1次删除、m-n+1次Siftup运算。总的时间复杂度为O(mlogn)。
注意输入应该为邻接表,如果输入是邻接矩阵,得到的依旧是Θ(n2)的算法。
伪代码:
C++代码:
/*
node结构既用来保存边,也用来保存点编号-标记对。
*/
#include <iostream>
#include <cstdio>
#include <queue>
#include <vector>
using namespace std;
const int Ni = 10000;
const int INF = 1<<27;
struct node{
int x,d;
node(){}
node(int a,int b){x=a;d=b;}
bool operator < (const node & a) const
{
if(d==a.d) return x<a.x;
else return d > a.d;
}
};
vector<node> eg[Ni];
int dis[Ni],n;
void Dijkstra(int s)
{
int i;
for(i=0;i<=n;i++) dis[i]=INF;
dis[s]=0;
//用优先队列优化
priority_queue<node> q;
q.push(node(s,dis[s]));
while(!q.empty())
{
node x=q.top();q.pop();
for(i=0;i<eg[x.x].size();i++)
{
node y=eg[x.x][i];
if(dis[y.x]>x.d+y.d)
{
dis[y.x]=x.d+y.d;
q.push(node(y.x,dis[y.x]));
}
}
}
}
int main()
{
int a,b,d,m;
while(scanf("%d%d",&n,&m),n+m)
{
for(int i=0;i<=n;i++) eg[i].clear();
while(m--)
{
scanf("%d%d%d",&a,&b,&d);
eg[a].push_back(node(b,d));
eg[b].push_back(node(a,d));
}
Dijkstra(1);
printf("%d\n",dis[n]);
}
return 0;
}