Dijkstra算法用于计算一个有权图中某一点到图中任意一点的最短路径
使用前提 :该图没有负权边
复杂度 :O(V^2) V为图中的顶点个数,优化后是Elog(E)
算法思想
使用一个cost数组,cost[i]表示从起点到编号为i这个点的最短路径花费
使用一个visited数组,用来表示cost[i]是否计算完成
从起始点开始,更新与它直接相邻的节点的cost数组,然后在未计算完成的剩余的点找到花费最少的点,这个点的cost数组代表的就是从起点到这一个点的最短路径花费,然后以这个点继续更新与之相邻的点,直至找到所有的点
实现过程:
根据输入的数据建图,然后从起始点开始,经过n-1次循环,在每一轮循环中,更新与该点相邻边的dist值。更新完后找到花费最小的点,更新cost数组,然后下一次循环以该点继续重复循环。
未经优化的代码:
#include <iostream>
#include <vector>
#include <algorithm>
#define maxn 10
using namespace std;
int dist[maxn],vis[maxn] = {0};
struct node{
int value;
int num;
};
vector<node> g[maxn];
int main()
{
int n,m;
cin >> n >> m;
for( int i = 0 ; i < n ; i++ )
{
dist[i] = 0x7fffffff;
}
for( int i = 0 ; i < m ; i++ ) //输入数据表示建立一张图
{
int x,y,v;
cin >> x >> y >> v;
node t;
t.value = v;
t.num = y;
g[x].push_back(t);
t.num = x;
g[y].push_back(t);
}
int begin = 0,minx = 0;
dist[begin] = 0;
for( int i = 0 ; i < n-1 ; i++ ) //一共要遍历n-1次,找到n-1个点与起始点的最短花费
{
vis[begin] = 1;
for(int j = 0 ; j < g[begin].size() ; j++ ) //遍历这一个点所有与它相邻的点
{
int t = g[begin][j].num;
dist[t] = min(dist[t],dist[begin] + g[begin][j].value); //更新它的数据
}
minx = 0x7fffffff;
for( int j = 0 ; j < n ; j++ ) //在n个点中找到花费最少且没有计算完成的点
{
if( !vis[j] && minx > dist[j] )
{
begin = j; //记录这个点的编号
minx = dist[j];
}
}
}
for( int i = 0 ; i < n ; i++ )
{
cout << dist[i] << endl;
}
return 0;
}
/*
5 8
0 1 5
0 2 2
0 3 6
2 1 1
2 3 3
2 4 5
1 4 1
3 4 2
*/
显然上述算法在查找值最小的点时用了O(V)的时间,导致整体的复杂度变成了O(V^2);其实我们可以用一个优先队列来优化,用log(V)的时间找到权值最小的点
实现过程 :先建图,把起点入队,然后松弛队首元素,把vis数组置1,松弛成功后,更新dist数组,把节点入队(注意入队的节点value值等于dist的值),直到队列为空
优化代码:
#include <iostream>
#include <vector>
#include <algorithm>
#include <queue>
#include <cstring>
#define maxn 100005
using namespace std;
typedef long long ll;
ll dist[maxn],vis[maxn];
struct node{
int num;
ll val; //对于在个g[i]的0vector里的node,value是存放i到这个点需要的权值。在优先队列的node,value代表的是从起点到这个点的值
node(int a,ll b)
{
num = a;
val = b;
}
bool operator<(const node&n) const
{
return val > n.val;
}
};
vector<node> g[maxn];
void dij(int begin,int n)
{
priority_queue<node> q;
memset(vis,0,sizeof(vis));
for( int i = 1 ; i <= n ; i++ )
{
dist[i] = 1e18; //设为无穷大
}
dist[begin] = 0;
q.push(node(begin,0)); //把开始节点入队
while( !q.empty() ) //while到队列为空
{
int x = q.top().num;
q.pop();
if( vis[x] ) continue; //如果这个点已经被访问过了,直接跳过
vis[x] = 1;
for( int i = 0 ; i < g[x].size() ; i++ )
{
node t = g[x][i];
if( !vis[t.num] && dist[t.num] > dist[x] + t.val ) //更新花费
{
dist[t.num] = dist[x] + t.val;
t.val = dist[t.num]; //这一步一定要更新入队的节点的value值
q.push(t);
}
}
}
}
int main()
{
int n,m,begin;
cin >> n >> m >> begin;
for( int i = 0 ; i < m ; i++ )
{
int x,y,v;
cin >> x >> y >> v;
g[x].push_back(node(y,v)); //建图
}
dij(1,n);
for( int i = 1 ; i <= n ; i++ )
{
if( i!=n ) cout << dist[i] << " " ;
else cout << dist[i] << endl;
}
return 0;
}
在这个代码中,我们跑了n-1个点,并且遍历了它们的所有边,在最坏情况下,每一次的遍历边都会往队列里push进节点。所以复杂度是ElogE。