最短路径
1.Dijkstra算法
主要解决单源最短路径问题:
void Dijkstra(int s){
for(int i = 0;i < maxn;i++)
dis[i] = INF;
dis[s] = 0;
for(int i = 0;i < n;i++){
int u = -1,min = INF;
for(int j = 0;j < n;j++){
if(!visit[j] && dis[j] < INF){
u = j;
min = dis[j];
}
}
if(u == -1) return;
visit[u] = true;
for(int j = 0;j < n;j++){
if(!visit[j] && G[u][j] != INF){
if(dis[j] > dis[u] + G[u][j]){
dis[j] = dis[u] + G[u][j];
}
}
}
}
}
当有两种或以上的最短路径时,一般情况下题目就会给出一个第二标尺,常见如下:
- 新增边权,以新增的边权代表花费为例,用cost[u][v]表示 U -> V 的花费,并新增一个数组c[],令从起点s到达u的最小花费为c[u],初始化时只有c[s]为0、其余均为INF。
当有两种或以上的最短路径时,一般情况下题目就会给出一个第二标尺,常见如下:
- 新增边权,以新增的边权代表花费为例,用cost[u][v]表示 U -> V 的花费,并新增一个数组c[],令从起点s到达u的最小花费为c[u],初始化时只有c[s]为0、其余均为INF。
for(int v = 0;v < n;v++){
if(visit[v] == false && G[u][v] != INF){
if(d[u] + G[u][v] < d[u]){
d[v] = d[u] + G[u][v];
cost[v] = c[u] + cost[u][v];
}
else if(d[u] + G[u][v] == d[u] && c[u] + cost[u][v] < c[v]){
c[v] = c[u] + cost[u][v];
}
}
}
- 新增点权,以新增的点权代表城市中能收集到的物资为例,用weight[u]表示城市 u 中的物资数目,并新增一个数组w[],令从起点s到达u的能收集的最大物资为w[u],初始化时只有w[s] = weight[s],其余均为0 。
for(int v = 0;v < n;v++){
if(visit[v] == false && G[u][v] != INF){
if(d[u] + G[u][v] < d[u]){
d[v] = d[u] + G[u][v];
w[v] = w[u] + weight[v];
}
else if(d[u] + G[u][v] == d[u] && c[u] + cost[u][v] < c[v]){
w[v] = w[u] + weight[v];
}
}
}
- 求最短路径条数,只需要增加一个数组num[],令起点s到达顶点u的最短路径条数为num[u],这样初始化时只有num[s] = 1、其余 num[s] 均为0 。
for(int j = 0;j < n;j++){
if(visit[j] == false && G[u][j] != INF){
if(dis[j] > dis[u] + G[u][j]){ //如果能找到更短的路径就更新 dis 数组
dis[j] = dis[u] + G[u][j];
num[j] = num[u];
}else if(dis[j] == dis[u] + G[u][j]){ //找到一条长度相等的路径
num[j] += num[u];
}
}
}
2.Bellman-Ford算法
Dijkstra 算法可以很好的解决无负权图的最短路径问题,但如果出现了负边权,Dijkstra就会失效,为了更好地求解有负权的最短路径问题,需要使用Bellman-Ford算法,Bellman-Ford算法可以解决单源最短路径问题,但也能处理有负权边的情况。
#include <iostream>
#include <vector>
using namespace std;
const int maxn = 500 + 5;
const int INF = 1000000000;
int dis[maxn];
struct node{
int v,d;//邻结点和邻接边的边权
node(int v1,int d1):v(v1),d(d1) {}
};
int n,m;
vector <node> Adj[maxn];
bool Ford(int s){
for(int i = 0;i < maxn;i++)
dis[i] = INF;
dis[s] = 0;
for(int i = 0;i < n-1;i++){
for(int u = 0;u < n;u++)
for(int j = 0;j < Adj[u].size();j++){
int v = Adj[u][j].v;//邻接边的顶点
int d = Adj[u][j].d;//邻接边的边权
if(dis[u] + d < dis[v]){
dis[v] = dis[u] + d;
}
}
}
//以下为判断负环的代码
for(int u = 0;u < n;u++)
for(int j = 0;j < Adj[u].size();j++){
int v = Adj[u][j].v;
int d = Adj[u][j].d;
if(dis[u] + d < dis[v]){//如果仍可以被松弛
return false; //说明图中有从源点可达的负环
}
}
return true;
}
int main(void){
int v1,v2,s,d;
cin>>n>>m>>s;
cin>>v1>>v2>>d; //负权值是单向的
Adj[v1].push_back(node(v2,d));
for(int i = 0;i < m-1;i++){
cin>>v1>>v2>>d;
Adj[v1].push_back(node(v2,d));
Adj[v2].push_back(node(v1,d));
}
if(Ford(s)){
cout<<"该图中没有负环。"<<endl;
}else{
cout<<"该图中存在负环。"<<endl;
}
for(int i = 0;i < n;i++)
cout<<dis[i]<<" ";
cout<<endl;
return 0;
}
/*
3 3 0
0 1 -3
0 2 1
1 2 5
3 3 0
0 1 -3
0 2 1
1 2 2
3 3 0
0 1 -3
0 2 1
1 2 1
*/
3.SPFA算法
SPFA算法是Bellman-Ford算法优化的结果
vector <node> Adj[maxn];
bool SPFA(int s){
for(int i = 0;i < maxn;i++)
dis[i] = INF;
dis[s] = 0;
memset(num,0,sizeof(num);
queue <int> q;
q.push(s);
visit[s] = true;
num[s]++;
while(!q.empty()){
int u = q.front();
q.pop();
visit[u] = false; //不在队列中
for(int i = 0;i < Adj[u].size();i++){
int v = Adj[u][i].v;//邻接边的顶点
int d = Adj[u][i].d;//邻接边的边权
//松弛操作
if(dis[v] > dis[u] + d){
dis[v] = dis[u] + d;
if(!visit[v]){
q.push(v);
num[v]++;//当前结点入队次数加一
visit[v] = true;
if(num[v] >= n) return false; //有可达的负环
}
}
}
}
return true;
}
4.Floyd算法
Floyd 算法用来解决全源最短路径问题,即对给定的图G(V,E),求任意两点u,v之间的最短路径长度。
void Floyd(){
for(int k = 0;k < n;k++) //辅助顶点 k (一定要放在最外层)
for(int i = 0;i < n;i++)
for(int j = 0;j < n;j++){
if(dis[i][k] != INF && dis[k][j] != INF){
if(dis[i][j] > dis[i][k] + dis[k][j]){
dis[i][j] = dis[i][k] + dis[k][j];
}
}
}
}