一.Dijkstra算法
1.概述
一种运用贪心策略的单源最短路径算法。
2.基本思路
(1)使用数组dist,G,dist[i]表示目标为顶点 i的最短路径,起始点的dist为0,其余各点为+∞;G为用链式向前星存储的图,它包括三个成员:
当然,也可以用邻接矩阵等其他方式存图。
(2)遍历过程中不断查找到当前点(设为cur)的最短路+与它邻接的某条边(设为i)的权值是否小于这条边的目标结点(即G[i].to)的最短路。即是否有dist[i]+G[i].weight<dist[G[i].to]。如果有,则该算式左边部分可取代当前dist[G[i].to]。(个人认为这里是运用了DP的思想)
(3)点cur遍历完毕后,继续路径中的下一个点。那么该如何选取这个点呢? 很显然如果我们每次都选从起点开始能到达的路径最短的点是最划算的。这就是贪心的思想。
(4)还要注意必须用+∞初始化,原因参见第二条的算式。另外必须保证每个点都只能途径一次,所以要有vis。
核心代码如下:
struct forward_star {
int to; //终点位置
int weight; //权
int next;
forward_star() {weight=INF;next=to=-1;}
};
int find_min() {
int minn=INF,mini=0;
for(int i=1;i<=n;i++) { //n为结点数
if(!vis[i]&&minn>dist[i]) { //必须从未访问过的结点中选取
minn=dist[i];
mini=i;
}
}
return mini;
}
void Dijkstra(int st,forward_star *G) {
dist[st]=0;
while(!vis[st]) { //如果未“绕回”
for(int i=first[st]; i!=-1; i=G[i].next) {
if(!vis[G[i].to]&&dist[st]+G[i].weight<dist[G[i].to]) { //如果到点G[i].to不是最短路径
dist[G[i].to]=dist[st]+G[i].weight; //更新到i的最小距离
}
}
vis[st]=1;
st=find_min(); //从当前点寻找最小边
}
}
可以看到:每次find_min比较耗时,因此可以用一些数据结构来优化。
这里选取比较好用的优先队列。(毕竟也只会这个) 但应注意
1.要在入队前就将队头元素出队,因为优先队列本身的性质,新入队的元素可能会取代原有的队头,而非像一般的FIFO队列那样只在队尾;
2.优先队列默认从大到小排序,用greater可实现从小到大;
3.用一个pair存储距离,其中第一个为距离,第二个为对应的点,至于为什么不能倒过来就不再赘述了。
代码如下:
typedef pair<long long,int> pii;
void Dijkstra(int st,forward_star *G) {
priority_queue<pii,vector<pii>,greater<pii> > q;
dist[st]=0;
q.push(make_pair(dist[st],st)); //两个元素依次是距离、编号,注意距离为long long
while(!q.empty()) {
int cur=q.top().second; //取出队头,就不用下面find_min了
q.pop();
if(vis[cur]) continue;//访问过则直接出队
for(int i=first[cur]; i!=-1; i=G[i].next) {
if(!vis[G[i].to]&&dist[cur]+G[i].weight<dist[G[i].to]) { //如果到点G[i].to不是最短路径
dist[G[i].to]=dist[cur]+G[i].weight; //更新到i的最小距离
q.push(make_pair(dist[G[i].to],G[i].to));
}
}
vis[cur]=1;
}
}
例题1:2021蓝桥杯省赛第5题。因为数据较小,就不优化了。
#include <cstdio>
#include <algorithm>
#include <vector>
#define INF 0x7f7f7f7f
const int MAX_EDGE=(2021*2020),MAX_NODE=2022;
using namespace std;
struct graph {
int next,to,weight;
graph() {next=to=-1,weight=INF;}
graph(int n,int t,int w) {next=n,to=t,weight=w;}
};
vector<graph> G;
int first[MAX_NODE],dist[MAX_NODE];
bool vis[MAX_NODE];
int find_min(int n) {
int minn=INF,mini=0;
for(int i=1;i<=n;i++) {
if(!vis[i]&&dist[i]<minn) {
mini=i;
minn=dist[i];
}
}
return mini;
}
void dijkstra(int st,const int n) {
dist[st]=0;
while(!vis[st]) {
for(int i=first[st];i!=-1;i=G[i].next) {
int tar=G[i].to;
if(!vis[tar]&&dist[st]+G[i].weight<dist[tar]) {
dist[tar]=dist[st]+G[i].weight;
}
}
vis[st]=1;
st=find_min(n);
}
}
int main(){
int n=2021,m=0,lim=21;
fill(dist,dist+n+1,INF);
fill(first,first+n+1,-1);
for(int i=1;i<=n;i++) {
for(int j=i+1;j<=i+lim;j++) {//条件别看反
int len=(i*j)/__gcd(i,j);
graph a1(first[i],j,len);
G.push_back(a1);
first[i]=m++;
graph a2(first[j],i,len);
G.push_back(a2);
first[j]=m++;
}
}
dijkstra(1,n);
printf("%d",dist[2021]);
return 0;
}
二.Floyd算法
1.概述:
一种利用动态规划思想求解多源最短路的算法,可以求解图中任意两点的最短路。
2.基本思想:
两点i,j之间的最短路,可能有两种情况
(1)两点之间原来的最短路;
(2)经过引入的新点k的最短路。
利用动态规划思想,对i,j两点的最短路不断更新。
3.核心代码:
for(int k=1;k<=n;k++) {
//最开始只允许经过1号顶点进行中转,接下来只允许经过1和2号顶点进行中转……允许经过1~n号所有顶点进行中转
//edge[i][j]为i到j的最短路
for(int i=1;i<=n;i++) {
for(int j=1;j<=n;j++) {
edge[i][j]=min(edge[i][k]+edge[k][j],edge[i][j]);
//将当前最短路与引入点k后的最短路比较
}
}
}
可以看出,Floyd算法的时间复杂度为 O ( n 3 ) O(n^3) O(n3),是比较低效的算法。 用此算法求解上面的题目,在开优化的条件下仍需要18s+,比Dijkstra慢很多。但它可以一次性求出任意两点之间的最短路,并能够求解带负权边的图。
例题
因为要回去,所以就是Σ(从1点出发到某一点的最短路+那个点到1点的最短路),即Σ(从1点出发到其他任意一点的最短路)+Σ(从其他任意一点出发到1点的最短路)。
传统做法:跑n次算法,可以用效率较高的dijkstra+优化,但仍会超时。所以可以把回程的路当作原图的一部分,如下:
这是原图
这是“新”图
建图时,每个点+n即可,这样只要跑2遍dijkstra。
#include <cstdio>
#include <algorithm>
#include <queue>
#define INF 0x3f3f3f3f
using namespace std;
typedef pair<int,int> pii;
const int MAX_EDGE=100001*2,MAX_NODE=1001*2;
struct graph {
int next,to,wei;
graph() {next=to=-1;wei=INF;}
};
graph G[MAX_EDGE];
int dist[MAX_NODE],first[MAX_NODE];
bool vis[MAX_NODE];
pii mp(int x,int y) {return make_pair(x,y);}
void add(int u,int v,int w,int &m) {
G[m].to=v;
G[m].wei=w;
G[m].next=first[u];
first[u]=m++;
}
void dijkstra(int st) {
priority_queue<pii,vector<pii>,greater<pii> > q;
dist[st]=0;
q.push(mp(dist[st],st));
while(!q.empty()) {
st=q.top().second;
q.pop();
if(vis[st]) continue;
for(int i=first[st];i!=-1;i=G[i].next) {
int to=G[i].to;
if(!vis[to]&&dist[to]>dist[st]+G[i].wei) {
dist[to]=dist[st]+G[i].wei;
q.push(mp(dist[to],to));
}
}
vis[st]=1;
}
}
int main(){
int n,m;
scanf("%d%d",&n,&m);
long long res=0;
fill(dist+1,dist+2*n+1,INF);
fill(first+1,first+2*n+1,-1);
int t=0;
for(int i=0;i<m;i++) {
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add(u,v,w,t);
//倒着建边
add(v+n,u+n,w,t);
}
dijkstra(1);
for(int i=1;i<=n;i++) res+=(long long)dist[i];
fill(dist+1,dist+2*n+1,INF);
fill(vis+1,vis+2*n+1,0);
dijkstra(1+n);
for(int i=n+1;i<=2*n;i++) res+=(long long)dist[i];
printf("%lld",res);
return 0;
}