好久没写博客了,今天就来回忆一下最短路问题的Dijkstra算法。
这是一种怎样的算法呢?
首先,我们要先搞清楚它可以解决什么样的问题:单源最短路。
那么,现在我们就先来看看它的思想吧。
在有n个点,m条边的图中,给出起点s,可以求出到达其他每个点的最短路(如果有的话)。那么,怎么来解决呢?
首先,为了表达方便,我们不妨用邻接矩阵a[i][j],来表达i到j有一条权值为a[i][j]的边。
我们的最短路可以通过n次更新每个点到起点的距离得到(想想为啥?)。
那么,如何更新呢?
每次拿出 还没有确定最短路的所有点 中离 已经确定最短路的所有点所构成的集合 距离最近的那个点 ,那么,剩下还没有更新的所有点都可以通过选出的这个点来更新最短的距离,还有,这个选出来的点的最短路到现在就已经确定了最短路径了。
上代码吧:
#include
using namespace std ;
const int maxn = 3000 , maxm = maxn*maxn << 1;
int a[maxn][maxn];//邻接矩阵
const int zhf = 1000000010 ;
bool p[maxn] ;//表示是否确定最短路
int d[maxn] ;//表示离起点最近的距离(当然这就是我们的答案)
int main () {
int i, s, t, j, k, m, n ;
scanf ( "%d%d", &n, &m ) ;//n个点,m条边
scanf ( "%d%d", &s, &t ) ;//起点和终点
for ( i = 1 ; i <= n ; i ++ )
for ( j = 1 ; j <= n ; j ++ )
if ( i == j )
a[i][j] = 0 ;
else a[i][j] = zhf ;
//对于初始情况下,每个点之间的最短距离都是无穷大,每个点到自己的距离都是0(其实不管是不是最初情况自己到自己都是0)
for ( i = 1 ; i <= m ; i ++ ) {
int x, y, w;
scanf ( "%d%d%d" , &x, &y, &w ) ;//表示点x到点y有一条权值为w的路径
if ( a[x][y] > w )a[x][y] = w ;//去重边
if ( a[y][x] > w )a[y][x] = w ;//去重边
}
for ( i = 1 ; i <= n ; i ++ )
d[i] = zhf ;
//初始化,每个点的最短路一开始都是无穷大
d[s] = 0 ;//起点显然是0
for ( i = 1 ; i <= n ; i ++ ) {
int minn = zhf ;//用来迭代当前还没有确定最短路的所有点中的最小距离
for ( j = 1 ; j <= n ; j ++ ) {
if ( !p[j] && d[j] < minn ) {
minn = d[j] ;
k = j ; //k用来记录
}
}
p[k] = 1 ;//代表当前k点的最短路是可以确定了的
for ( j = 1 ; j <= n ; j ++ ) {
if ( a[k][j] != zhf && d[j] > a[k][j] + d[k] )
d[j] = a[k][j] + d[k] ;
//如果点k到点j有一条边,而且点k的距离可以更新j的距离
}
}
printf ( "%d\n" , d[t] ) ;//输出终点的最短路
/*
for ( i = 1 ; i <= n ; i ++ )
printf ( "%d\n" , d[i] ) ;
其实可以这样输出所有点的最短路
*/
return 0 ;
}
同时,也附上链式前向星的存图方式的代码:
#include
#include
#include
#include
using namespace std;
const int maxn = 10010 ;
const int maxm = 500010 ;
const int zhf = 10000000 ;
int e, to[maxm], nxt[maxm], w[maxm], be[maxn], n, m, d[maxn] ;
bool p[maxn] ;
void add ( int x, int y, int z ) {
to[++e] = y ;
nxt[e] = be[x] ;
be[x] = e ;
w[e] = z ;
}
int main(){
int i, x, y, z, j, k ;
scanf ( "%d%d" , &n, &m ) ;
int s, t ;
for ( i = 1 ; i <= n ; i ++ ) d[i] = zhf ;
scanf ( "%d" , &s ) ;
d[s] = 0 ;
for ( i = 1 ; i <= m ; i ++ ) {
scanf ( "%d%d%d" , &x, &y, &z );
add ( x,y,z ) ;
}
for ( i = 1 ; i <= n ; i ++ ) {
int minn = zhf ;
for ( j = 1 ; j <= n ; j ++ )
if ( !p[j] && d[j] < minn ) {
minn = d[j] ;
k = j ;
}
p[k] = 1 ;
for ( j = be[k] ; j ; j = nxt[j] ) {
int u = to[j] ;
if ( d[u] > d[k] + w[j] ) d[u] = d[k] + w[j] ;
}
}
for ( i = 1 ; i <= n ; i ++ )
printf ( "%d " , d[i] == zhf ? 2147483647 : d[i] ) ;
return 0;
}
当然,可以发现,时间复杂度是O(n^2)的。
有没有可以优化的方法呢?
其实是有的 :)
(想想看,可以在哪里优化呢?)
显然,我们可以在每次迭代找出最小距离的时候进行优化!
用什么进行优化呢?
堆!(priority_queue)
别的都差不多,所以直接上代码!
#include
using namespace std ;
const int zhf = 1000000010 ;
const int maxn = 3010 ;
struct node{
int id, d ;
}e[3010];
bool operator < ( node a, node b ) {
if ( a.d == b.d ) return a.id < b.id ;
return a.d > b.d ;
}
bool p[maxn];
int a[maxn][maxn] ;
priority_queue q ;
node mk ( int x, int y ) {
node k ;
k.id = x ;
k.d = y ;
return k ;
}
int main () {
int i, j, k, m, n, x, y, w, s, t ;
scanf ( "%d%d" , &n, &m ) ;
scanf ( "%d%d" , &s, &t ) ;
for ( i = 1 ; i <= n ; i ++ )
for ( j = 1 ; j <= n ; j ++ )
if ( i == j ) a[i][j] = 0 ;
else a[i][j] = zhf ;
for ( i = 1 ; i <= m ; i ++ ) {
scanf ( "%d%d%d" , &x, &y, &w ) ;
if ( a[x][y] > w ) a[x][y] = w ;
if ( a[y][x] > w ) a[y][x] = w ;
}
for ( i = 1 ; i <= n ; i ++ )
e[i] = mk( i,zhf ) ;
e[s] = mk ( s,0 ) ;
q.push(e[s]) ;
while ( !q.empty() ) {
node tmp = q.top() ;
q.pop() ;
k = tmp.id ;
p[k] = 1 ;
for ( i = 1 ; i <= n ; i ++ ) {
if ( a[k][i] != 0 && e[i].d > a[k][i] + e[k].d ) {
e[i].d = a[k][i] + e[k].d ;
q.push( mk(i,e[i].d) ) ;
}
}
}
printf ( "%d\n" , e[n].d ) ;
return 0 ;
}
你,懂了吗?