从某顶点出发,沿图的边到达另一顶点所经过的路径中,各边上权值之和最小的一条路径——最短路径。
现主要应用两种算法 单元最短路(spfa),任意两点间的最短路(floyd)
spfa
1.算法思想:(动态逼近法)
设立一个先进先出的队列q用来保存待优化的结点,优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止。
松弛操作的原理是著名的定理:“三角形两边之和大于第三边”,在信息学中我们叫它三角不等式。所谓对结点i,j进行松弛,就是判定是否dis[j]>dis[i]+w[i,j],如果该式成立则将dis[j]减小到dis[i]+w[i,j],否则不动。
void spfa(s);//求单源点到其他点的最近距离
queue<int>q;//建立队列存放松驰过的点
for i=1 to n do{dis[i]=INF,vis[i]=false;}//初始化每点到s点的距离以及不在队列
dis[s]=0;q.push(s);vis[s]=true;//初始化dis[s],源点入队并标记
while(!q.empty())
u=q.front();q.pop();vis[u]=false;//队首出队,并取消标记
for each edge(v,i)
if(dis[i]>dis[v]+mp[v][i])//判断是否被一条新的路径松弛
dis[i]=dis[v]+mp[v][i];
if(!vis[i]){q.push(i),vis[i]=true;}//如果不在队列中,则进队
2.判断负环:如果不再存在负环(即存在最短路径),则一定能求出最短路径。
bfs :判断某一点进队次数是否大于等于总顶点数
if(!vis[i]){q.push(i),cnt[i]++;if(cnt[i]>=n) return false;vis[i]=true;}
dfs:留坑
3.输出路径:运用前缀数组的写法,即path【i】是i的前一个节点(父节点);输出时运用递归即可,注意为倒叙输出
if(dis[i]>dis[v]+mp[v][i])//判断是否被一条新的路径松弛
dis[i]=dis[v]+mp[v][i];
path[i]=v;
void printpath(int k){
while(k!=0){
printf(k);
k=path[k];
}
}
4.模板:
有两种形式,一种是用邻接矩阵存储,另一种是用邻接表vector存储
void spfa(int s) { queue<int>q; memset(dis,0x3f,sizeof(dis)); memset(vis,0,sizeof(vis)); dis[s]=0;vis[s]=true;q.push(s); while(!q.empty()) { int now=q.front();q.pop();vis[now]=false; for(int i=0;i<G[now].size();i++) { int to=G[now][i]; if(dis[to]>dis[now]+mp[now][to]) { dis[to]=dis[now]+mp[now][to]; if(!vis[to]) { vis[to]=true; q.push(to); } } } } }
void spfa(int s) { memset(dis,0x3f,sizeof(dis)); memset(vis,0,sizeof(vis)); queue<int>q; q.push(s);dis[s]=0;vis[s]=1; while(!q.empty()) { int now=q.front();q.pop();vis[now]=0; for(int i=1;i<=n;i++) { if(dis[i]>dis[now]+mp[now][i]) { dis[i]=dis[now]+mp[now][i]; if(!vis[i]){ vis[i]=1; q.push(i); } } } } }
floyd
1.算法思路:
Floyd算法是一个经典的动态规划算法。用通俗的语言来描述的话,首先我们的目标是寻找从点i到点j的最短路径。从动态规划的角度看问题,我们需要为这个目标重新做一个诠释(这个诠释正是动态规划最富创造力的精华所在)
从任意节点i到任意节点j的最短路径不外乎2种可能,1是直接从i到j,2是从i经过若干个节点k到j。所以,我们假设Dis(i,j)为节点u到节点v的最短路径的距离,对于每一个节点k,我们检查Dis(i,k) + Dis(k,j) < Dis(i,j)是否成立,如果成立,证明从i到k再到j的路径比i直接到j的路径短,我们便设置Dis(i,j) = Dis(i,k) + Dis(k,j),这样一来,当我们遍历完所有节点k,Dis(i,j)中记录的便是i到j的最短路径的距离。
void init() { for(int i=0;i<n;i++) for(int j=0;j<n;j++) mp[i][j]=(i==j)?0:INF; }
for(k=1;k<=n;k++) for(i=1;i<=n;i++) for(j=1;j<=n;j++) if(dp[i][j]>dp[i][k]+dp[k][j]) dp[i][j]=dp[i][k]+dp[k][j];
2.输出路径:path[i][j]代表从i到j要走的下一步;
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) path[i][j]=j; if(dp[i][j]>dp[i][k]+dp[k][j]) dp[i][j]=dp[i][k]+dp[k][j]; path[i][j]=path[i][k]; void printfpath(int i,int j) { int tmp=i; printf("Path: %d",tmp); while(tmp!=j) { printf("-->%d",path[tmp][j]); tmp=path[tmp][j]; } printf("\n"); }