最短路 (九度 OJ 1447)

最短路 (九度 OJ 1447)

时间限制:1 秒 内存限制:128 兆 特殊判题:否

1.题目描述:

在每年的校赛里,所有进入决赛的同学都会获得一件很漂亮的 t-shirt。但是每当我们的工作人员把上百件的衣服从商店运回到赛场的时候,却是非常累的!所以现在他们想要寻找最短的从商店到赛场的路线,你可以帮助他们吗?输入:输入包括多组数据。每组数据第一行是两个整数 N、M(N<=100,M<=10000),N 表示成都的大街上有几个路口,标号为 1 的路口是商店所在地,标号为 N 的路口是赛场所在地, M 则表示在成都有几条路。 N=M=0 表示输入结束。接下来M 行,每行包括 3 个整数 A, B, C(1<=A,B<=N,1<=C<=1000) ,表示在路口 A与路口 B 之间有一条路,我们的工作人员需要 C 分钟的时间走过这条路。输入保证至少存在 1 条商店到赛场的路线。当输入为两个 0 时,输入结束。
输出:
对于每组输入,输出一行,表示工作人员从商店走到赛场的最短时间。

样例输入:
2 1
1 2 3
3 3
1 2 5
2 3 5
3 1 2
0 0
样例输出:
3
2

2.基本思路

由于不考虑权值为负的情况,有两种思路。一种是求解多源最短路径的Floyd算法,另一种是求解单源最短路径的Dijkstra算法。

2.1Floyd算法
这里采用Floyd算法,其算法复杂度为 O ( n 3 ) O(n^3) O(n3),由于0≤n≤100,因此采用该算法是可以符合要求的。在Floyd中,维护一个邻接矩阵ans[N][N],其中ans[i][j]表示,结点i到结点j之间的距离。算法需要对待选择的结点进行遍历,依次将结点k(编号为1…n)加入到选中的集合中。在结点k加入到选中的集合中后,求解当前选中的集合中,所有的结点之间的最短路径。即利用以下的递推方程进行更新:
a n s [ i ] [ j ] = { a n s [ i ] [ k ] + a n s [ k ] [ j ] , ans[i][k]+ans[k][j]&lt;ans[i][j] a n s [ i ] [ j ] , other ans[i][j]= \begin{cases} ans[i][k]+ans[k][j], &amp; \text{ans[i][k]+ans[k][j]&lt;ans[i][j]} \\ ans[i][j], &amp; \text{other} \end{cases} ans[i][j]={ans[i][k]+ans[k][j],ans[i][j],ans[i][k]+ans[k][j]<ans[i][j]other
直到k=n时,即所有的结点都加入到选中的结点集合中的时候,此时的ans[1][n]便是所求的答案。

2.2Dijkstra算法
Dijkstra算法主要包括三个步骤:
1>.初始化,集合K中加入结点1,结点1到结点1的最短距离为0,到其他结点的距离为无穷(或不确定)。
2>.遍历与集合K中结点直接相邻的边(U,V,C),其中U属于集合K,V不属于集合K,计算由结点1出发按照先已得到的最短路径到达结点U,再由结点U经过该边到达V时的路径长度。比较到达所有与集合K中结点直接相邻的非集合K的结点的路径长度,得到最短路径长度对应的结点,将该结点加入到集合K中。
3>.若集合K中已经包含了所有的结点,算法结束;否则重复步骤2.
为了更加直观的说明Dijkstra算法,我们以下面一个简单的例子为例:
在这里插入图片描述
采用邻接链表表示上图所示的图。其中链表中的元素为结构体元素,定义如下

struct Edge{
	int node;//vec[i][j].node,表示与第i个结点相邻的结点的第j个点的编号为node
	int cost;//表示i与编号为node的结点之间的代价为cost
}

在这里插入图片描述
算法的求解过程如下所示,其中矩阵Dis[N]的元素Dis[i]表示结点1到结点i之间的最短距离
Mark[i]用于标记结点i是否已经访问过了。
在这里插入图片描述
随后我们来讲讲关于Dijkstra怎么打印出最短的路径。如下公式:
d i s [ t ] = d i s [ n e w P ] + c P r e [ t ] = n e w P dis[t]=dis[newP]+c\\ Pre[t]=newP dis[t]=dis[newP]+cPre[t]=newP
我们可以知道更新过程中,t点的前驱结点就是newP点,那么同样的道理,我们可以采用一个数组Pre[N]来存储每个结点的前驱结点的信息,其中Pre[j]表示结点j的前驱结点的编号。随后我们可以通过递归调用的方法打印出每个结点的前驱结点(类似于并查集查找根结点的过程)。
对于Floyd算法有同样的结论:
a n s [ i ] [ j ] = a n s [ i ] [ k ] + a n s [ k ] [ j ] P r e [ j ] = k ans[i][j]=ans[i][k]+ans[k][j]\\ Pre[j]=k ans[i][j]=ans[i][k]+ans[k][j]Pre[j]=k
打印过程的递归调用函数如下,由于按照从头到尾的顺序打印路径,因此我们在递归调用的下面书写打印语句,将函数回传时带有的结点信息打印出来。

void printThePath(int s,int e){//s为起点,e为终点
    if(Pre[e]==s){
        printf("%d->",s);
    }
    else{
        printThePath(s,Pre[e]);
        printf("%d->",Pre[e]);
    }
}

3.代码实现

3.1Floyd算法

#include <iostream>
#define N 101

using namespace std;

int ans[N][N];
int n,m;
int x;
int main()
{
    int a,b,cost;
    while(scanf("%d%d",&n,&m)!=EOF){
        if(n==0&&m==0)break;

        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                if(i==j)
                    ans[i][j]=0;//对角线初始化为0

                ans[i][j]=-1;//非初始初始化为-1
            }
        }

        for(int i=0;i<m;i++){
            scanf("%d%d%d",&a,&b,&cost);
            ans[a][b]=ans[b][a]=cost;
        }

        for(int k=1;k<=n;k++){
            for(int i=1;i<=n;i++){
                for(int j=1;j<=n;j++){
                    if(ans[i][k]==-1||ans[k][j]==-1)
                        continue;
                    if(ans[i][j]==-1||ans[i][k]+ans[k][j]<ans[i][j])
                        ans[i][j]=ans[i][k]+ans[k][j];
                }
            }
        }
        printf("%d\n",ans[1][n]);
    }
    return 0;
}
/*
2 1
1 2 3
3 3
1 2 5
2 3 5
3 1 2
0 0
*/

3.2Dijkstra算法

#include <iostream>
#include <vector>
#include <climits>

#define N 101

using namespace std;

struct E{//声明链表中的结点
    int next;//存储结点的编号
    int cost;//存储前驱结点与结点相连的权重
};

vector<E> edge[N];//存储邻接链表
bool mark[N];//标记结点是否已访问过
int Dis[N];//存储从结点1到达该结点的最短路径长度

int main()
{
    int n,m;

    while(scanf("%d%d",&n,&m)!=EOF){
        if(n==0&&m==0)break;
        for(int i=1;i<=n;i++)
            edge[i].clear();

        for(int i=1;i<=m;i++){//将每一条边存储到邻接链表中
            int a,b,c;//输入的一行数据
            scanf("%d%d%d",&a,&b,&c);
            E tmp;
            tmp.next = b;
            tmp.cost = c;
            edge[a].push_back(tmp);
            tmp.next = a;
            tmp.cost = c;
            edge[b].push_back(tmp);
        }
        //初始化Dis和mark数组
        for(int i=1;i<=n;i++){
            mark[i]=false;
            Dis[i]=-1;
        }

        mark[1] = true;//初始化第1个结点
        Dis[1] = 0;

        int newP = 1;

        for(int i=1;i<n;i++){//n-1次循环,寻找剩余的n-1个结点加入到选中的集合中
                for(int j=0;j<edge[newP].size();j++){//遍历与newp结点相邻的所有结点
                    int t = edge[newP][j].next;
                    int c = edge[newP][j].cost;

                    if(mark[t]==true)
                        continue;
                    if(Dis[t]==-1||Dis[newP]+c<Dis[t])//Dis[newP]+c<Dis[t] 此时经由newP到达结点t的距离比经由K中其他结点到达t的距离更短,更新Dis[t]的值
                        Dis[t] = Dis[newP]+c;//对于到达结点t的距离,每次只利用经过新加入的点到达该点t的路径长度来更新Dis[t]即可
                }

                int min = INT_MAX;
                for(int i=1;i<=n;i++){//遍历Dis数组中不属于集合k的结点,得到对用的Dis[i]最小的结点i,该结点即为将被加入到集合K中的结点
                    if(mark[i]==true)continue;
                    if(Dis[i]==-1)continue;
                    if(Dis[i]<min){
                        min = Dis[i];
                        newP = i;
                    }
                }
                mark[newP] = true;

        }

        printf("%d\n",Dis[n]);//输出答案
    }
    return 0;
}

/*
2 1
1 2 3
3 3
1 2 5
2 3 5
3 1 2
4 5
1 2 3
1 4 2
2 3 4
4 3 4
4 2 1
0 0
*/

以下为包括打印最短路径的完整代码:

#include <iostream>
#include <climits>
#include <vector>
#define N 101

using namespace std;

struct Edge{
    int node;
    int cost;
};



vector<Edge> Map[N];//Map[i]后面接的是与其相连的所有边的信息
bool mark[N];//标记某个结点是否已经加入了集合K中
int Dis[N];//Dis[i]存储结点1到结点i的距离
int Pre[N];//记录前驱结点的编号,Pre[j]表示结点j的前驱结点编号

void printThePath(int s,int e){
    if(Pre[e]==s){
        printf("[%d]->",s);
    }
    else{
        printThePath(s,Pre[e]);
        printf("[%d]->",Pre[e]);
    }
}

int main()
{
    int n,m;
    while(~scanf("%d%d",&n,&m)){
        if(n==0&&m==0)
            break;

        for(int i=1;i<=n;i++){
            Map[i].clear();
            mark[i]=false;
            Dis[i]=-1;

        }
        int a,b,cost;
        for(int i=1;i<=m;i++){//利用邻接链表创建图
            scanf("%d%d%d",&a,&b,&cost);
            Edge tmp;
            tmp.node=b;
            tmp.cost=cost;
            Map[a].push_back(tmp);
            tmp.node=a;
            Map[b].push_back(tmp);
        }
        int newP=1;//将结点1加入到结合K中
        mark[1] = true;
        Dis[1] = 0;
        Pre[1] = 1;
        for(int i=1;i<=n-1;i++){//循环n-1次,确定1到其他n-1个结点的路径
                for(int j=0;j<Map[newP].size();j++){
                    int t = Map[newP][j].node;
                    int c = Map[newP][j].cost;
                    if(mark[t])continue;//如果该结点已经访问过了
                    if(Dis[t]==-1||Dis[t]>Dis[newP]+c){
                        Dis[t]=Dis[newP]+c;
                        Pre[t]=newP;
                    }

                }
                //确定与第m近的结点最近的第m+1个点
                int min=INT_MAX;
                for(int j=1;j<=n;j++){
                    if(mark[j])
                        continue;
                    if(Dis[j]==-1)//第一遍漏了
                        continue;
                    if(Dis[j]<min){
                        min = Dis[j];
                        newP = j;
                    }

                }
                mark[newP]=true;
        }

        printf("The shortest path is:");
        printThePath(1,n);
        printf("[%d]\n",n);
    }

    return 0;
}
/*
2 1
1 2 3
3 3
1 2 5
2 3 5
3 1 2
0 0
4 5
1 2 5
1 3 2
2 4 1
3 4 5
2 3 5
6 9
1 2 1
1 3 2
2 3 1
2 4 1
3 4 1
3 5 2
4 5 1
4 6 10
5 6 1
*/

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值