最短路学习路径

一、dijkstra
dijkstra解决不了带有负权值的最短路问题,因为disjkstra源于贪心算法,它计算的是每个点的最优解,前面确定好的点就不会影响后面点的松弛。在一条到目的地的路径上,前面得出的解不会因为后面的松弛而改变。但是如果路线中有负权值,这时则起点到某点的最最优解会改变,这时前面的一些点已经松弛过来,很多值无法更新了,所有得出的解是错的。因此需要另一种写法,学会了再说。

1.Til the Cows Come Home(例题)

#include<iostream>
#include<cstdio>
#define MAX 1000+4
#define inf 99999999
using namespace std;
//用二维数组储存边,遍历时按点松弛所有两点之间,时间复杂度较大。
int map[MAX][MAX];
int t,n;
int dis[MAX];
int book[MAX];
int v;
void init()//初始化二维数组,i==j表示本身初始化为0,其他初始化为无穷大,表示无限远。
{
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            if(i==j)
            {
                map[i][j]=0;
            }
            else
                map[i][j]=inf;
        }
    }
}
void dijk()
{
    for(int i=1;i<=n;i++)//dis用于存目前有起点到i点的最优解,book标记松弛过的点。
    {
        book[i]=0;
        dis[i]=map[1][i];
    }
    for(int i=1;i<=n-1;i++)//遍历除起点外的所有点,它更新了dis上存的最优解。
    {
        int _min=inf;
        for(int j=1;j<=n;j++)//找出所有未松弛过的点中,最优的一个点(这里是最近的一个点)
        {
            if(book[j]==0&&_min>dis[j])
            {
                _min=dis[j];
                v=j;
            }
        }
        /*for(int i=1;i<=n;i++)
        {
            cout<<dis[i]<<" ";
        }cout<<endl;*/
        book[v]=1;              //标记v点并松弛v点
        for(int k=1;k<=n;k++)//遍历为被松弛的点,更新这些点的最优解。
        {
            if(book[k]==0&&dis[k]>dis[v]+map[v][k])
            {
                dis[k]=dis[v]+map[v][k];
            }
        }
    }
    printf("%d\n",dis[n]);
}
int main()
{
    while(scanf("%d%d",&t,&n)!=EOF)
    {
        init();
        for(int i=0;i<t;i++)//建立边。二维数组的i,j分别表示两点,map[i][j]存入权值。
        {
            int t1,t2,t3;
            scanf("%d%d%d",&t1,&t2,&t3);
            if(t3<map[t1][t2])
            {
                map[t1][t2]=t3;
                map[t2][t1]=t3;
            }
        }
        dijk();
    }
}

2.Frogger (例题)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#define MAX 300
#define inf 99999999
using namespace std;
int n,v,c=0;
int book[MAX];
double dis[MAX];
struct node
{
    double a,b;
}s[MAX];
void init()
{
    for(int i=1;i<=n;i++)
    {
        book[i]=0;
        dis[i]=inf;
    }
}
double D(int i,int j)//计算两点之间的距离。
{
    double d=sqrt((s[i].a-s[j].a)*(s[i].a-s[j].a)+(s[i].b-s[j].b)*(s[i].b-s[j].b));
    return d;
}
void dijk()
{
    for(int i=1;i<=n;i++)//记录起点到它所连接的点的距离,存入dis;
    {
        dis[i]=D(1,i);
    }
    //题目需要找出其中一条路径上两个石头之间的最大距离,所以松弛时,取到该点最优解(dis)和该点到下一点距离的大值————————因为它求的是一条路上的大值
    //再取{到下一点的最优解,和上述的最大值}取小值——————因为要找出所有路径中最短的值,我们可以绕过那些大距离的路径,走小距离的路,不影响到达目的地
    for(int i=1;i<=n-1;i++)
    {
        int _min=inf;
        for(int j=1;j<=n;j++)//找出dis中的最小值
        {
            if(book[j]==0&&_min>dis[j])
            {
                _min=dis[j];
                v=j;
            }
        }
        /*for(int j=1;j<=n;j++)
        {
            printf("%.3lf ",dis[j]);
        }cout<<endl;*/
        book[v]=1;          //标记v,松弛v;
        for(int j=1;j<=n;j++)
        {
            if(book[j]==0)  //在未松弛的点中。max(dis[v],dD(v,j));得出起点经v到j的最大距离。
                            //min(~~~);得出上述最大值与到j点最优解取最小值,可以理解为起点经v到j的解和不经v到j的解,取小值
            {
                dis[j]=min(dis[j],max(dis[v],D(v,j)));
            }
        }
    }
    printf("Scenario #%d\nFrog Distance = %.3lf\n\n",c,dis[2]);
}
int main()
{
    while(scanf("%d",&n))
    {
        c++;
        if(n==0)
            break;
            init();
        for(int i=1;i<=n;i++)
        {
            scanf("%lf%lf",&s[i].a,&s[i].b);
        }
        dijk();
    }
}

二、spfa(队列优化的bellman)

相对 dijkstra节省时间复杂度,时间复杂度低,比较常用,它利用了邻接链表来储存边。因为它在松弛的时候,用了队列,所以对于多重条件的题目,我们可以用优先队列来处理,对入队元素选取优值,松弛,节省时间,甚至某种情况下还必须这样做。

1.信道安全(例题)

#include <iostream>
#include<bits/stdc++.h>
using namespace std;
const int N=11000;
const int M=55000*2;
int vis[N],cnt,head[N],n,m;
double d[N];
struct node
{
    int v,next;
    double p;
}e[M];
void init()
{
    memset(head,-1,sizeof(head));
    cnt=0;
}
void add(int u,int v,double p)
{
    e[cnt].v=v;
    e[cnt].p=p;
    e[cnt].next=head[u];//head[u]是点u的上一条边。他延续了上一条u点的边,初始值为-1.记录u点上一条边的位置(类似于链表)。
    head[u]=cnt++;//cnt变量随着存入边的增加,记录着这条边存在第i个node上。更新head[u].
    cout<<head[u]<<endl;;
}
double spfa()
{
    for(int i=0;i<=n;i++)//初始化d,vis数组,d记录权值,vis标记以松弛过的点。
    {
        d[i]=0;
        vis[i]=0;
    }
    //运用类似广搜的方法,将储存点所连接的所有边入队,逐步遍历相关的边:
    /*如:    1-->3,4,5.  3,4,5-->q
              3->2        2->q
              4->5        5->q
              5->1        1->q
    */
    queue<int> q;//将起点入队。
    q.push(1);
    vis[1];
    d[1]=1;
    while(!q.empty())
    {
        int u=q.front();//取队首元素,以队首元素为点,遍历它连接的所有边。
        q.pop();
        vis[u]=0;
        for(int i=head[u];i+1;i=e[i].next)
        //取u点的head,它指向u点连接边储存的位置,在从该位置上取e[i].next。
        //next指向下一个边的点(这些边都是u点的连接边).i==-1时,u的边取尽了。这时e[i].next==-1,是第一次存入u点边的时候
        {
            /*cout<<i<<" ";
            cout<<e[i].next<<endl;*/
            int v=e[i].v;
            double p=e[i].p;
            if(d[v]<d[u]*p/100)//更新d值,取大值。如果v点(下一点)没有松弛过,则把v点入队,并标记。
            {
                d[v]=d[u]*p/100;
                if(!vis[v])
                {
                    vis[v]=1;
                    q.push(v);
                }
            }
        }
        //cout<<endl;
    }
    return d[n];
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&m);
        init();
        for(int i=0;i<m;i++)//建立边。
        {
            int u,v;
            double p;
            scanf("%d%d%lf",&u,&v,&p);
            add(u,v,p);
            add(v,u,p);
        }
        printf("%.6f\n",spfa()*100);
    }
}

原文地址:https://www.cnblogs.com/freeyouth/p/15753544.html