最短路径问题学习心得

最短路径问题(浙大MOOC学习笔记)

单源最短路(时间最短,路程最短,边的个数。。。):源点固定的

​ 无权图

​ 有权图

多源最短路:任意两个顶点之间

无权图的单源最短路

就是一次B F S,借助队列,很好实现

当然了,我们需要把BFS里面的Visitied 改一下,改为源点到各个点的最短距离dist

(初始化:正无穷,负无穷,-1)(与visitied的作用类似)

还有一个细节,我们需要用一个数组path,来存储路径

path存什么?

前一个顶点!

我们最后求解结束以后,从V点倒推到原点,利用堆栈,可以很好的实现反向。

伪码如下:

void Unweighted ( Vertex S ) 
{ Enqueue(S, Q);
  while(!IsEmpty(Q)){
    V = Dequeue(Q); 
    for ( V 的每个邻接点 W )
      if ( dist[W] == -1  ) {
        dist[W] = dist[V]+1;
        path[W] = V;
        Enqueue(W, Q);
      }
  }
}

这种方法的时间复杂度应该是O(V+E)

有权图的单源最短路

(首先声明:图里面没有负值圈,因为会挂,也暂时没有负边)

与无权图的算法的类似之处:路径的长度是依次递增的。

Dijkstra算法的基本描述

设V为点集,E为带权值的边集,v0 为原点,那么,我们进行如下的操作:

每次更新集合V的划分V1, V2 (其中V1是已经确定最短距离的点集)V2是待确定的点集

我们要做的:从V2里面取一个与v0距离最短的点p,放到V1里面。

更新所有与p邻接的点的与v0距离(即考虑以p作为中介顶点带来的影响)

若V2为空集,结束

算法细节的简化版证明
  1. 最短路径一定仅经过V1里面的顶点

    pf:如果在V2里面存在顶点m,使得V1- m-p的长度小于V1-p,那么由于路径长度是从小到大递增的,导出m-V1的距离,小于p-V1,m应该在先于p出现在V1里面,矛盾!

  2. 每次p收到V1里面后,仅对V2里面与p邻接的点进行更新就可以了

    pf:因为V1里面的点的距离已经是最短了,无需更新,而V2里面的点,由于p的最短距离的确定,会影响与p邻接的顶点与v0的已知最短距离,所以,更新与p邻接的顶点。

    dist[W]=min(dist[W],dist[p]+<p,W>)

算法的实现细节
  1. V1 与V2的划分,我们利用collected数组实现
  2. 距离的存储我们利用dist数组实现,初始化应该利用正无穷
  3. path[W]=V ,来存路径。

伪码描述:

void Dijkstra( Vertex s )
{ while (1) {
    V = 未收录顶点中dist最小者;
    if ( 这样的V不存在 )
      break; 
    collected[V] = true;
    for ( V 的每个邻接点 W )
      if ( collected[W] == false ) 
    if ( dist[V]+E<V,W> < dist[W] ) {
         dist[W] = dist[V] + E<V,W> ;
         path[W] = V;
    }
  }
}

有权图的多源最短路

引入floyd算法

for(i=1; i<=v; i++)
    {
        for(j=1;j<=v;j++)
        {D[i][j]=g[i][j];}//初始化D(-1)
    }

    for(k=1; k<=v; k++)
    {
        for(i=1; i<=v; i++)
        {
            for(j=1; j<=v; j++)
            {
                if((D[i][k]+D[k][j])<D[i][j])
                {
                    D[i][j]=D[i][k]+D[k][j];
                }
            }
        }
    }

其中,如果想要导出路径,我们利用path来解决问题

i-j经过k

所以,找i-j转化为i-k与k-j

问题解决

最后,咱还得那几道题目来检验下自己的水平:

第一题哈利波特的考试

07-图4 哈利·波特的考试 (25分)

哈利·波特要考试了,他需要你的帮助。这门课学的是用魔咒将一种动物变成另一种动物的本事。例如将猫变成老鼠的魔咒是haha,将老鼠变成鱼的魔咒是hehe等等。反方向变化的魔咒就是简单地将原来的魔咒倒过来念,例如ahah可以将老鼠变成猫。另外,如果想把猫变成鱼,可以通过念一个直接魔咒lalala,也可以将猫变老鼠、老鼠变鱼的魔咒连起来念:hahahehe。

现在哈利·波特的手里有一本教材,里面列出了所有的变形魔咒和能变的动物。老师允许他自己带一只动物去考场,要考察他把这只动物变成任意一只指定动物的本事。于是他来问你:带什么动物去可以让最难变的那种动物(即该动物变为哈利·波特自己带去的动物所需要的魔咒最长)需要的魔咒最短?例如:如果只有猫、鼠、鱼,则显然哈利·波特应该带鼠去,因为鼠变成另外两种动物都只需要念4个字符;而如果带猫去,则至少需要念6个字符才能把猫变成鱼;同理,带鱼去也不是最好的选择。

输入格式:

输入说明:输入第1行给出两个正整数N (≤100)和M,其中N是考试涉及的动物总数,M是用于直接变形的魔咒条数。为简单起见,我们将动物按1~N编号。随后M行,每行给出了3个正整数,分别是两种动物的编号、以及它们之间变形需要的魔咒的长度(≤100),数字之间用空格分隔。

输出格式:

输出哈利·波特应该带去考场的动物的编号、以及最长的变形魔咒的长度,中间以空格分隔。如果只带1只动物是不可能完成所有变形要求的,则输出0。如果有若干只动物都可以备选,则输出编号最小的那只。

输入样例:

6 11
3 4 70
1 2 1
5 4 50
2 6 50
5 6 60
1 3 70
4 6 60
3 6 80
5 1 100
2 4 60
5 2 80

输出样例:

4 70

蛮简单的,直接floyd一遍,找出每行最小,再找出所有的最小

代码见下:

#include <iostream>
#include <queue>
#include <stdio.h>
#define MAXVALUE 99999
using namespace std;
int g[120][120];
int v,e;
void start()
{
    cin>>v>>e;
    int i,j,x,y,value;
    for(i=0; i<e; i++)
    {
        cin>>x>>y>>value;
        g[x][y]=value;
        g[y][x]=value;
    }
    for(x=1; x<=v; x++)
    {
        for(y=1; y<=v; y++)
        {
            if(g[x][y]==0)
            {
                g[x][y]=MAXVALUE;
            }
        }
    }

}

void floyd()
{
    int i,j,k,n;
    int D[120][120];
    for(i=1; i<=v; i++)
    {
        for(j=1;j<=v;j++)
        {D[i][j]=g[i][j];}//初始化D(-1)
    }

    for(k=1; k<=v; k++)
    {
        for(i=1; i<=v; i++)
        {
            for(j=1; j<=v; j++)
            {
                if((D[i][k]+D[k][j])<D[i][j])
                {
                    D[i][j]=D[i][k]+D[k][j];
                }
            }
        }
    }
    int animals[120],tempmax;
    for(i=1; i<=v; i++)
    {
        tempmax=0;
        for(j=1; j<=v; j++)
        {
            if(i!=j)
            {
                if(D[i][j]>tempmax)
                {
                    tempmax=D[i][j];
                }
            }
        }
        animals[i]=tempmax;
    }

    int minvalue=MAXVALUE,minloca=-1,temp;
    for(i=1; i<=v; i++)
    {
        if(minvalue>animals[i])
        {
            minvalue=animals[i];
            minloca=i;
        }
    }
    if(minloca==-1)
    {
        cout<<"0";
    }
    else
    {
        cout<<minloca<<" "<<minvalue;
    }
}



int main()
{
    start();
    floyd();
}


练习2旅游规划

(居然一遍编译就AC了,233333)

蛮简单,把正常的Dij算法里面的path变成money,问题就结束了。。。

07-图6 旅游规划 (25分)

有了一张自驾旅游路线图,你会知道城市间的高速公路长度、以及该公路要收取的过路费。现在需要你写一个程序,帮助前来咨询的游客找一条出发地和目的地之间的最短路径。如果有若干条路径都是最短的,那么需要输出最便宜的一条路径。

输入格式:

输入说明:输入数据的第1行给出4个正整数NMSD,其中N(2≤N≤500)是城市的个数,顺便假设城市的编号为0~(N−1);M是高速公路的条数;S是出发地的城市编号;D是目的地的城市编号。随后的M行中,每行给出一条高速公路的信息,分别是:城市1、城市2、高速公路长度、收费额,中间用空格分开,数字均为整数且不超过500。输入保证解的存在。

输出格式:

在一行里输出路径的长度和收费总额,数字间以空格分隔,输出结尾不能有多余空格。

输入样例:

4 5 0 3
0 1 1 20
1 3 2 30
0 3 4 10
0 2 2 20
2 3 1 20

输出样例:

3 40

代码如下:

#include <iostream>
#define inf 9999999

using namespace std;
struct way
{
    int lenth;
    int price;
    /* data */
} graph[530][530];
int N,M,S,D;
int money[530];
int dist[530];
int collect[530];
void ini()
{
    cin>>N>>M>>S>>D;
    int i;
    int x,y,l,p;
    for(i=0;i<M;i++)
    {
        cin>>x>>y>>l>>p;
        graph[x][y].lenth=l;
        graph[x][y].price=p;
        graph[y][x].lenth=l;
        graph[y][x].price=p;
    }
}
int find_mindist()
{
    int i,tem=inf-5,rem=-1;
    for(i=0;i<N;i++)
    {
        if(dist[i]<tem&&collect[i]==0)
        {
            tem=dist[i];
            rem=i;
        }
    }
    return rem;
}
int min(int a,int b)
{
    if(a<b)
    {return a;}
    else
    {
        return b;
    }

}
void da()
{
    int i,j,k;
    int p;
    for(i=0;i<N;i++)
    {
        dist[i]=inf;
    }
    dist[S]=0;
    money[S]=0;
    while (1)
    {
        p=find_mindist();
        if(p==-1)
        {
            break;
        }
        else
        {
            collect[p]=1;
            for(i=0;i<N;i++)
            {
                if(graph[p][i].lenth!=0&&collect[i]==0)
                {
                    if ((graph[p][i].lenth+dist[p])<dist[i])
                     {
                            dist[i]=graph[p][i].lenth+dist[p];

                            money[i]=money[p]+graph[p][i].price;
                    }
                    if ((graph[p][i].lenth+dist[p])==dist[i]&&money[i]>(money[p]+graph[p][i].price))
                    {

                        dist[i]=graph[p][i].lenth+dist[p];

                        money[i]=money[p]+graph[p][i].price;
                    }
                }
            }
        }
    }
    cout<<dist[D]<<" "<<money[D];
}


int main()
{
    ini();
    da();


}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值