图的最短路径算法
dfs
使用dfs遍历计算最短路径
void dfs(int cur,int dst){
//当前的路劲大于之前的最短路劲就不用走了
if(minpath < dst) return;
//临界条件,当走到终点n
if(cur == en){
if(minpath > dst){
minpath = dst;
return;
}
}
for(int i=1;i<=n;i++){
//i节点未访问过,cur->i有边,则进入
if(mark[i] == 0 && edge[cur][i] != inf && edge[cur][i] != 0){
mark[i] = 1;
dfs(i,dst+edge[cur][i]);
mark[i] = 0;
}
}
return ;
}
例子,输入n和m然后输入三个数,分别表示a->b的边和权重edge,然后根据dfs找到0到n的最短路径
#include <btis/stdc++.h>
using namespace std;
#define nmax 110
#define inf 999999999
//最短路劲,节点数,边数,终点,邻接矩阵,节点访问标记
int minpath,n,m,en,edge[nmax][nmax],mark[nmax];
void dfs(int cur,int dst){
//当前走过的已经超过了minpath则直接返回
if(minpath < dst)
return;
//临界条件,当走到终点n
if(cur == en){
if(minpath > dst){
minpath = dst;
return ;
}
}
for(int i=1;i<=n;i++){
if(mark[i] == 0 && edge[cur][i]] != inf && edge[cur][i]){
mark[i] = 1;
dfs(i,dst+edge[cur][i]);
mark[i] = 0; //dfs之后访问标志重置为0
}
}
return;
}
int mian(){
while(cin >> n >> m && n != 0){
//初始化邻接矩阵
for(int i=1;i <= n;i++){
for(int j=1;j <= n;j++){
edge[i][j] = inf;
}
edge[i][i] = 0;
}
//写入m条边
int a,b;
while(m--){
cin >> a >> b;
cin >> edge[a][b];
}
minpath = inf;
memset(mark,0,sizeof(mark));
mark[1] = 1;
//设定了结束的节点就是n
en = n;
dfs(1,0);
cout << minpath << endl;
}
}
BFS
利用BFS也是可以获得最短路径的
- 以一个节点为起点,放入队列
- 取出队首节点node,然后在对应的位置,加入他的路径上一个节点path
- 把他的邻接节点放入队列
- 如果队列不为空,回到2
Folyed佛洛依德算法(解决多远最短路劲)
思想
最开始只允许经过1号顶点进行中转,接下来只允许进过1号和2号顶点进行中转。。。允许经过1~n号所有顶点进行中转,来不断动态更新任意两点之间的最短距离,即求从i好顶点到j顶点只经过前k号顶点的最短距离
算法分析
- 首先构造邻接矩阵edge[n+1][n+1],假如只经过1号节点,求任意两点最短距离,显然edge[i][j] = min(edge[i][j],edge[i][1]+edge[1][j])
- 接下来允许经过1号和2号顶点,在已经经过1号节点前提下插入2号节点啦更新,
- 显然n次更新之后,最后求得的edge[i][j]就是从i号顶点到J号顶点只经过n号点的最短路径
核心代码
#include <iostream>
using namespace std;
#define nmax 110
#define inf 999999999
//最短路劲,节点数,边数,终点,邻接矩阵,节点访问标记
int minpath,n,m,en,edge[nmax][nmax],mark[nmax];
//取k=1时即所有节点经过1节点到后面所有节点的距离
//k为每次经过前k个节点到达的最短路径
// k是每次使用哪个节点作为中转
for(int k=1;k <= n;k++){
for(int i=1;i <= n;i++){ //i为原来节点的
// j 为目标节点的终点
for(int j=1;j <= n;j++){
//k可以到j,i可以到k,i到j的权大于i到k,再从k到j
if(edge[k][j] < inf && edge[i][k] < inf && edge[i][j] > edge[i][k]+edge[k][j]){
edge[i][j] = edge[i][k] + edge[k][j];
}
}
}
}
例子
寻找最短的从商场到赛场的路线。其中商店在1号节点处,赛场在n号节点处,1~n节点中有m条线路双向连接。
/*先输入n,m,在输入m个三元组,n为路口数,m表示有几条路,其中1为商店,n为赛场,三元组分别表示起点终点,和该路径长,输出1到n的最短距离*/
#include<bits/stdc++.h>
using namespace std;
#define inf 999999999
#define nmax 110
int n,m,edge[nmax][nmax];
int main ()
{
int a,b;
while(cin>>n>>m&&n!=0){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
edge[i][j]=inf;
}
edge[i][i]=0;
}
while(m--){
cin>>a>>b;
cin>>edge[a][b];
edge[b][a]=edge[a][b];
}
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(edge[k][j]<inf&&edge[i][k]<inf&&edge[i][j]>edge[i][k]+edge[k][j]){
edge[i][j]=edge[i][k]+edge[k][j];
}
}
}
}
cout<<edge[1][n]<<endl;
}
}
2 1
1 2 3
3
3 3
1 2 5
2 3 5
3 1 2
2
0 0
迪杰斯特拉算法(解决单源最短路径)
把所有节点到达的路径设为 无穷大
最开始选择一个最短的路径点(起点,为0)
以最短的路径为起点更新能够到达的点的路径
在除去刚刚选择过的节点之外的节点中找到一个最小的节点X
- 刚刚取了起点,这次就在以起点为源更新了的节点里面找最短的路径节点
以X为起点更新能够到达的带你的路径
继续4,直到取完所有的节点了
算法
- 设置标志数组book[]标记哪些路径已经求得了最短路径了,已知的假如在P中,未知的加入在Q中
- 设置最短路劲数组dst[],刚开始时dst[i]=edge[s][i],(s为源点)即都是从源点到i的长度,此时dst[s]=0,book[s]=1,在book里面找一个里源点最近的顶点u加入P,已u为中转点对每一条边松弛(即s–>j途中经过u,dst[j]=min(dst[j],dst[u]+edge[u][j],并令book[u]=1))
- 再找一个离源点s最近的顶点v加入,已v中转对每一条松弛
- 重复3,知道集合Q为空
核心代码
#include<bits/stdc++.h>
using namespace std;
#define nmax 110
#define inf 999999999
/***构建所有点最短路径数组dst[],且1为源点***/
int u;/***离源点最近的点***/
int minx;
// 初始化(第一次以起点能到达的距离)
for(int i=1;i<=n;i++) dst[i]=edge[1][i];
// 初始化访问数组
for(int i=1;i<=n;i++) book[i]=0;
book[1]=1;
// 正式开始松弛
for(int i=1;i<=n-1;i++){
// 先要从剩下的没有松弛的节点里面找到最小的节点
minx=inf;
for(int j=1;j<=n;j++){
if(book[j]==0&&dst[j]<minx){
minx=dst[j];
u=j;
}
}
book[u]=1;
/***更新最短路径数组***/
for(int k=1;k<=n;k++){
if(book[k]==0&&dst[k]>dst[u]+edge[u][k]&&edge[u][k]<inf){
dst[k]=dst[u]+edge[u][k];
}
}
}
例题
例1:给你n个点,m条无向边,每条边都有长度d和花费p,给你起点s,终点t,要求输出起点到终点的最短距离及其花费,如果最短距离是有多条路线,则输出花费最少的。
输入:输入n,m,点的编号是1~n,然后是m行,每行4个数a,b,d,p,表示a和b之间有一条边,且长度为d,花费为p。最后一行是两个数s,t,起点s,终点t。n和m为0时输入结束。(1<n<=1000,0<m<100000,s!=t)
输出:输出一行,有两个数,最短距离及其花费。
分析:由于每条边有长度d和花费p,最好构建变结构体存放。
3 2
1 2 5 6
2 3 4 5
1 3
9 11
#include<bits/stdc++.h>
using namespace std;
#define nmax 110
#define inf 999999999
struct Edge{
int len;
int cost;
}edge[nmax][nmax];
int u,n,m,book[nmax],s,t,dst[nmax],spend[nmax];
int minx;
int main (){
while(cin>>n>>m&&n!=0&&m!=0){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
edge[i][j].len=inf;
edge[i][j].cost=0;
}
edge[i][i].len=0;
}
int a,b;
while(m--){
cin>>a>>b;
cin>>edge[a][b].len>>edge[a][b].cost;
edge[b][a].len=edge[a][b].len;
edge[b][a].cost=edge[a][b].cost;
}
cin>>s>>t;
for(int i=1;i<=n;i++) {dst[i]=edge[s][i].len;spend[i]=edge[s][i].cost;}
for(int i=1;i<=n;i++) book[i]=0;
book[s]=1;
for(int i=1;i<=n-1;i++){
minx=inf;
for(int j=1;j<=n;j++){
if(book[j]==0&&dst[j]<minx){
minx=dst[j];
u=j;
}
}
book[u]=1;
for(int k=1;k<=n;k++){
if(book[k]==0&&(dst[k]>dst[u]+edge[u][k].len||(dst[k]==dst[u]+edge[u][k].len&&spend[k]>spend[u]+edge[u][k].cost))&&edge[u][k].len<inf){
dst[k]=dst[u]+edge[u][k].len;
spend[k]=spend[u]+edge[u][k].cost;
}
}
}
cout<<dst[t]<<' '<<spend[t]<<endl;
}
}
Bellman-Ford算法
- 可以用于含有负权值的图
算法过程
- 初始化所有点。每个点保存一个值,表示源点到达这个点的距离,将源点的值设为0,其他的点的值设为无穷大
- 激进型循环,循环下标为从1到n-1(n为图中点的个数)。在循环内部,遍历所有的边,进行松弛计算
- 遍历图中所有的边(edge(u,v)),判断是否存在这一的情况: d(v)>d(u)+edge(u,v),若存在,则返回false,表示图中存在从源点可达的权为负的回路(可能因为无法收敛导致不能求出最短路径)
#include <iostream>
#include <iomanip>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <stack>
using namespace std;
#define MAX 10000
struct Edge{
int u;
int v;
int w;
};
Edge edge[10000];
int dist[100];
int path[100];
int vertex_num;
int edge_num;
int source;
bool BellmanFord(){
// 初始化
for(int i=0;i<vertex_num;i++)
dist[i] = (i == source) ? 0 : MAX;
//n-1只是次数,并不需要使用节点的值,循环求最短路径
for(int i=1;i<=vertex_num;i++){
for(int j=0;j<edge_num;j++){
if(dist[edge[j].v] > dist[edge[j].u] + edge[j].w){
dist[edge[j].v] = dist[edge[j].u] + edge[j].w;
path[edge[j].v] = edge[j].u;
}
}
}
// 标记是否有负权回路
bool flag = true;
//第N次判断负权
for(int i=0;i<edge_num;i++){
if(dist[edge[i].v] > dist[edge[i].u] + edge[i].w){
flag = false;
break;
}
}
return flag;
}
void Print(){
for(int i=0;i<vertex_num;i++){
if(i != source){
int p = i;
stack<int> s;
cout << "顶点" << source << "到顶点" << p << " 的最短路径是:";
// 路径逆向,要借助栈
while(source != p){
s.push(p);
p = path[p];
}
cout << source;
// 一次从栈里取出来
while (!s.empty()){
cout << "--" << s.top();
s.pop();
}
cout << " 最短路径是:" << dist[i] << endl;
}
}
}
int main(){
cout << "输入顶点数,边数,源点";
cin >> vertex_num >> edge_num >> source;
cout << "请输入" << edge_num << "条边的信息:\n";
for(int i=0;i<edge_num;i++)
cin >> edge[i].u >> edge[i].v >>edge[i].w;
if(BellmanFord())
Print();
else
cout << "Sorry, it have negative circle!\n";
return 0;
}
SPFA算法
对Bellman-Ford算法的改进
还没看
例题
题目
平面上有n nn个点 ( N < = 100 ) (N <= 100 )(N<=100),每个点的坐标均在-10000~10000之间。其中的一些点之间有连线。若有连线,则表示可从一个点到达另一个点,即两点间有通路,通路的距离为两点直线的距离。现在的任务是找出从一点到另一点之间的最短路径。输入
共有n + m + 3 n+m+3n+m+3行,其中:
第一行为一个整数n nn。
第2行到第n + 1 n+1n+1行(共n nn行),每行的两个整数x xx和y yy,描述一个点的坐标(以一个空格隔开)。
第n + 2 n+2n+2行为一个整数m mm,表示图中的连线个数。
此后的m mm行,每行描述一条连线,由两个整数I , j I,jI,j组成,表示第i ii个点和第j jj个点之间有连线。
最后一行:两个整数s ss和t tt,分别表示源点和目标点。输出
一个实数(保留两位小数),表示从 S SS 到 T TT 的最短路径的长度。样例
input
5
0 0
2 0
2 2
0 2
3 1
5
1 2
1 3
1 4
2 5
3 5
1 5output
3.41
Floyed算法
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<cmath>
using namespace std;
int n,m,x,y;
double d[120][120];
struct c{
double x,y;
}a[120];
int main()
{
memset(d,0x7f,sizeof(d));
scanf("%d",&n);
for (int i=1;i<=n;i++)
cin>>a[i].x>>a[i].y;
scanf("%d",&m);
for (int i=1;i<=m;i++)
{
cin>>x>>y;
d[y][x]=d[x][y]=sqrt(pow(a[x].x-a[y].x,2)+pow(a[x].y-a[y].y,2));
}
cin>>x>>y;
// 开始Floyed算法
for (int k=1;k<=n;k++)
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
if (i!=k&&j!=k&&i!=j&&d[i][j]>d[i][k]+d[k][j])
d[i][j]=d[i][k]+d[k][j];
cout<<setprecision(2)<<fixed<<d[x][y]<<endl;
return 0;
}
Dijkstra算法
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
const double maxn=0x7fffffff;
double d[10020][10020],dis[120];
int p[120],n,m,x2,y2,x[120],y[120];
int main()
{
memset(dis,0x7f*1.0,sizeof(dis));
scanf("%d",&n);
for (int i=1;i<=n;i++)
cin>>x[i]>>y[i];
scanf("%d",&m);
for (int i=1;i<=m;i++)
{
cin>>x2>>y2;
d[y2][x2]=d[x2][y2]=sqrt(pow(abs(x[x2]-x[y2]),2)+pow(abs(y[x2]-y[y2]),2));
}
cin>>x2>>y2;
// 设置初始的起点为0,因为单源最短路径算法,需要一个起点
dis[x2]=0;
// 开始Dijkstra算法
for (int i=1;i<=n;i++)
{
// 先找到剩余的节点中的离源点最短路径的节点
int k;
double mi=maxn;
k=0;
for (int j=1;j<=n;j++)
if (p[j]==0&&mi>dis[j])
{
k=j;
mi=dis[j];
}
// 如果k是0,则说明没有更短的了,则全部松弛完了,结束
if(!k) break;
p[k]=1;
// 松弛操作
for (int j=1;j<=n;j++)
if (p[j]==0&&d[j][k]!=0)
dis[j]=min(dis[j], dis[k]+d[j][k]);
}
printf("%.2lf",dis[y2]);
return 0;
}
Ford算法
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
int n,m,x2,y2;
int x[15000],y[15000],dx[15000],dy[15000];
double dis[15000],l[15000];
int main()
{
memset(dis,0x7f*1.0,sizeof(dis));
scanf("%d",&n);
for (int i=1;i<=n;i++)
cin>>x[i]>>y[i];
scanf("%d",&m);
for (int i=1;i<=m;i++)
{
cin>>dx[i]>>dy[i];
l[i]=sqrt((abs(x[dx[i]]-x[dy[i]]))*(abs(x[dx[i]]-x[dy[i]]))+(abs(y[dx[i]]-y[dy[i]]))*(abs(y[dx[i]]-y[dy[i]])));
}
cin>>x2>>y2;
// 也是单源的,需要一个起点的初始距离
dis[x2]=0;
// Bellman-Ford算法
for (int i=1;i<n;i++)
{
int p=0;
for (int j=1;j<=m;j++)
{
// x->y 来更新 dis[y]
if (dis[dx[j]]+l[j]<dis[dy[j]])
{
dis[dy[j]]=dis[dx[j]]+l[j];
p=1;
}
// y -> x 更新 dis[x]
if (dis[dy[j]]+l[j]<dis[dx[j]])
{
dis[dx[j]]=dis[dy[j]]+l[j];
p=1;
}
}
if (p==0) break;
}
printf("%.2lf",dis[y2]);
return 0;
}