最短路径的定义
最短路径问题是给定两个顶点,在以这两个点为起点和终点的路径中,边权和最小的路径。如果把权值当作距离,考虑最短距离就好理解了。
单源最短路问题是固定一个起点,求它到其他所有点的最短路问题。终点也固定的问题叫做两点之间最短路径问题。但其复杂度与与单源路问题一样,因此我们也看作单源最短路问题求解。
负权与负环:如果说有一个环他们的权之和小于0就是负环
单源最短路问题1(Bellman-Ford算法)
Bellman-Ford的改进是SPFA算法
题目:设有n个点m条边,每条边的起始点是u终点是v,边权w。
思路:我们设一个dis[]数组,表示固定点到所有点的距离。
初始化
我们遍历每一条边,如果说dis[v]+w<dis[u],那么dis[u]=dis[v]+w(松弛操作)
struct edge{
int from,to,cost
};
edge es[maxn];
int d[maxn];
int V,E;
void shortest_path(int s){
for(int i=0;i<V;i++)d[i]=INF;
d[s]=0;
while(true){
bool update=false;
for(int i=0;i<E;i++){
edge e=es[i];
if(d[e.from]!=INF&&d[e.to]>d[e.from]+e.cost){
d[e.to]=d[e.from]+e.cost;
update=true;
}
}
if(!update)break;
}
}
单源最短问题2(Dijkstra算法)
如果题目中没有负边权,那么就用Dijkstra算法
Dijkstra算法不断选择全局最小值进行标记和拓展,最终可以得到起点到每个节点的最短路径的长度
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
const int INF=2147483647;
int n,m,s,dis[maxn],vis[maxn];
struct node{
int to,w;
}edge;
vector<node>e[maxn];
priority_queue<pair<int,int> >q;
void init(){
for(int i=1;i<=n;i++){
e[i].clear();
dis[i]=INF;
}
}
void Dijkstra(){
dis[s]=0;
q.push(make_pair(-dis[s],s));
while(!q.empty()){
int now=q.top().second;
q.pop();
if(vis[now])
continue;
vis[now]=1;
for(int i=0;i<e[now].size();i++){
int to=e[now][i].to;
if(dis[to]>dis[now]+e[now][i].w){
dis[to]=dis[now]+e[now][i].w;
q.push(make_pair(-dis[to],to));
}
}
}
}
int main(){
scanf("%d%d%d",&n,&m,&s);
init();
for(int i=1;i<=m;i++){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
e[x].push_back((node){y,z});
}
Dijkstra();
for(int i=1;i<=n;i++)
cout<<dis[i]<<" ";
}
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
const int INF=2147483647;
int n,m,s,dis[maxn],vis[maxn];
vector<pair<int,int> >e[maxn];
priority_queue<pair<int,int> >q;
void init(){
for(int i=1;i<=n;i++){
e[i].clear();
dis[i]=INF;
}
}
void Dijkstra(){
dis[s]=0;
q.push(make_pair(-dis[s],s));
while(!q.empty()){
int now=q.top().second;
q.pop();
if(vis[now])
continue;
vis[now]=1;
for(int i=0;i<e[now].size();i++){
int to=e[now][i].first;
if(dis[to]>dis[now]+e[now][i].second){
dis[to]=dis[now]+e[now][i].second;
q.push(make_pair(-dis[to],to));
}
}
}
}
int main(){
scanf("%d%d%d",&n,&m,&s);
init();
for(int i=1;i<=m;i++){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
e[x].push_back(make_pair(y,z));
}
Dijkstra();
for(int i=1;i<=n;i++)
cout<<dis[i]<<" ";
}
单源最短问题3(SPFA算法)
Bellman-Ford的改进是SPFA算法,Ford每次都把所有的边遍历一遍,其实是一种浪费。我们只需要把一个顶点的dis[]改变的情况下,进行松弛操作即可。
慎用,在毒瘤数据中可退化到O(nm)。
思路:我们用到STL队列,用数组dis记录最短路径,用邻接表存图,用vis数组记录该节点是否在队列中。
步骤:用队列来存储待优化的节点(类似BFS),优化时每次取出队首节点,并且用队首节点来作为最短路径进行更新并进行松弛操作
如果需要对最短路进行更新,且该点不在队列中,我们把它加入到队列中
不断进行松弛操作,直至队列为空。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=5e5+10;
const int INF=2147483647;
int n,m,s,vis[maxn],dis[maxn];
struct node{
int to,w;
}p;
vector<node>e[maxn];
void init(){
for(int i=1;i<=n;i++){
e[i].clear();
dis[i]=INF;
vis[i]=0;
}
}
void spfa(){
queue<int> q;
q.push(s);dis[s]=0;vis[s]=1;
while(!q.empty()){
int now=q.front(); vis[now]=1;
for(int i=0;i<e[now].size();i++){
int to=e[now][i].to;
if(dis[to]>dis[now]+e[now][i].w){
dis[to]=dis[now]+e[now][i].w;
if(!vis[to]){
q.push(to);
vis[to]=1;
}
}
}
q.pop();
vis[now]=0;
}
}
int main(){
scanf("%d%d%d",&n,&m,&s);
init();
while(m--){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
p.to=y;p.w=z;
e[x].push_back(p);
}
spfa();
for(int i=1;i<=n;i++)
printf("%d ",dis[i]);
cout<<endl;
}
#include<bits/stdc++.h>
using namespace std;
const int maxn=5e5+10;
const int INF=2147483647;
int n,m,s,vis[maxn],dis[maxn];
vector<pair<int,int> >e[maxn];//tu
queue<int> q;
void init(){
for(int i=1;i<=n;i++){
e[i].clear();
vis[i]=0;
dis[i]=INF;
}
}
void spfa(){
q.push(s);vis[s]=1;dis[s]=0;
while(!q.empty()){
int now=q.front();
q.pop();vis[now]=1;
for(int i=0;i<e[now].size();i++){
int to=e[now][i].first;
if(dis[to]>dis[now]+e[now][i].second){
dis[to]=dis[now]+e[now][i].second;
if(!vis[to]){
vis[to]=1;
q.push(to);
}
}
}
vis[now]=0;
}
}
int main(){
scanf("%d%d%d",&n,&m,&s);
init();
for(int i=1;i<=m;i++){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
e[x].push_back(make_pair(y,z));
}
spfa();
for(int i=1;i<=n;i++)
cout<<dis[i]<<" ";
}
任意两点间的最短路问题(Floyd算法)
求解带权图的多源最短路问题,算法原理是动态规划,复杂度O(n^3),不能有负环。
我们首先问从i号顶点到j号顶点的距离,是否可以让i到1再到j的距离代替直接从i到j呢。e[i][1]+e[1][j]<e[i][j].那么i、j经过2顶点呢,三顶点呢
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++)
d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
}
}
P2910 [USACO08OPEN]Clear And Present Danger S
#include<bits/stdc++.h>
using namespace std;
const int maxn=110;
const int maxn1=1e4+10;
int n,m,sum;
int order[maxn1],dis[maxn][maxn];
void floyd(){
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
}
}
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
scanf("%d",&order[i]);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
scanf("%d",&dis[i][j]);
floyd();
int pre=1;
for(int i=1;i<=m;i++){
sum+=dis[pre][order[i]];
pre=order[i];
}
sum+=dis[pre][n];
cout<<sum<<endl;
}