图的最短路径算法

图的最短路径算法

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也是可以获得最短路径的

  1. 以一个节点为起点,放入队列
  2. 取出队首节点node,然后在对应的位置,加入他的路径上一个节点path
  3. 把他的邻接节点放入队列
  4. 如果队列不为空,回到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

迪杰斯特拉算法(解决单源最短路径)

  1. 把所有节点到达的路径设为 无穷大

  2. 最开始选择一个最短的路径点(起点,为0)

  3. 以最短的路径为起点更新能够到达的点的路径

  4. 在除去刚刚选择过的节点之外的节点中找到一个最小的节点X

    1. 刚刚取了起点,这次就在以起点为源更新了的节点里面找最短的路径节点
  5. 以X为起点更新能够到达的带你的路径

  6. 继续4,直到取完所有的节点了

算法
  1. 设置标志数组book[]标记哪些路径已经求得了最短路径了,已知的假如在P中,未知的加入在Q中
  2. 设置最短路劲数组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))
  3. 再找一个离源点s最近的顶点v加入,已v中转对每一条松弛
  4. 重复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算法

  • 可以用于含有负权值的图

算法过程

  1. 初始化所有点。每个点保存一个值,表示源点到达这个点的距离,将源点的值设为0,其他的点的值设为无穷大
  2. 激进型循环,循环下标为从1到n-1(n为图中点的个数)。在循环内部,遍历所有的边,进行松弛计算
  3. 遍历图中所有的边(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 5

output
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;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值