单源最短路径的矩阵实现

前言 

在一个带权值的有向图中,将一个顶点设为源点,假设需要计算源点到其他所有点的最短路长度,即使源点到达其他所有点路径权值和最短,这个问题称作单源最短路径问题。该用什么方法去完成这个问题?在学习贪心算法的过程中,有一种选择方式叫做局部最优选择,即在当前的状态下做出最好的选择。在这个选择之后,产生了一个新的子问题,而针对这个新的子问题,需要进一步地去做该状态下的最优选择。迪杰斯特拉算法便利用局部最优选择思想来解决单源最短路径问题。

正文

假设一个带权有向图记作s,s具有5个顶点,连接每两个点的边的权值均不同,假设点1为源点,使得点1到达其他点的路径权值和最短。s的具体表示如下:

s示意图

如何才可以求出源点1到其他点的最短路径长度,基本思路如下:

1)设两个点的集合为S,V,S代表已求出最短路径长度的点集合,V代表12345所有点的集合,集合V-S代表未进入集合S的点集合。先把源点1放入点集合S中,点2345处于V-S中。

2)查看V-S中与集合S中的点直接相连的点,在这里是点245,选择权值最小的边,即边12,将点2加入集合S中。此时集合S中具有元素12,而V-S中具有元素345。

3)查看V-S中与点1 2直接相连的点,在这种情况下选择为345,而此时权值最小的边为边14。将点4加入集合S中。此时集合S中具有的元素为124,而V-S中具有元素3 5。

4)查看V-S中与点124直接相连的点,在这种情况下选择为3 5.其中23边的权值为50,43边的权值为20,15边的权值为100,权值最小的边为边43。将点3加入集合S中,V-S中具有元素5。

5)查看V-S中与点1243直接相连的点,选择为点5,权值最小的边为边35,将点5加入集合中。S集合中元素为12345,V-S中元素为空。

由以上的想法不难看出,在具有n个顶点的情况下,进行n-1次选择,每一次选择都将一个顶点加入集合S。而选择的原则便是局部最优,在集合S中的点,源点到它们的路径必定是最短最优的,而由集合S中的点向外挑选点时,选取点到源点以外点的路径长度最短必定会使得源点到其的路径长度也最短。

基本思路完成后,需要开始完善代码实现。由集合的表达,不难看出需要建立一个布尔类型数组s[n],判断除源点外的点处在集合S或集合V-S中。在建立完成布尔数组后,需要有一个二维数组来表达带权有向图,二维数组的下标由0开始,为了方便起见,具有五个点的图需要设置为6*6的数组c。c[1][2]表示由点1到点2的路径权值。若点 i 点 j 之间相互不连通则用一个大数字进行表示,这儿假定为200。即c[i][j]=200。有向图的矩阵表示如下:

有向图矩阵

建立两个一维数组,其中dist[]数组表示源点到其他点的最短路径长度,prev[]数组表示到达某点的路径的前驱结点。对于两个数组的初始化代码如下:

void Dij(int prev[],int dist[],int n,int v,int c[6][6])
{
    //v代表源点的下标,这里直接用1来代替
    bool s[6]={false};//判断是否在集合S中的数组s初始化为false
    for(int i=1;i<=n;i++)
    {
        if(c[v][i]<200)
        {
            prev[i]=1;//若点i与点1直接相连,点i的前驱结点都直接初始化为点1
            dist[i]=c[v][i];//此状态下点1到点i的距离初始化为数组c中的元素c[1][i]
        }
        else
        {
            prev[i]=0;//c[1][i]等于200,代表点1与点i不相连
            dist[i]=200;
        }
    }
    prev[v]=0;
    s[v]=true;//将源点1加入集合s中
    dist[v]=0;
}

在两个数组初始化完成后,需要开始对未进入集合S的点进行遍历寻找,通过for循环对于点进行查找。由之前的思路不难得出,需要进行4次循环以加入4个点,最外层的循环控制4次。开始内层循环,假设一个点的下标为u,用u来记录路径最短的点的下标。u初始化为源点下标1,既然寻找最小值,必须定义一个变量temp,初始化为200。在不属于集合S的点内(s[j] ! = true),若源点到该点的距离dist[ j ]小于temp,则用dist [ j ]的值去代替temp的值,同时用 j去给u赋值。然后经过剩余4个点的循环,可以挑选出这次循环的最小值。同时最短路径点的下标u也被获得了。将u加入集合S(s[ u ] = true)。代码如下:

for(int i=1;i<=n;i++)
{
    int temp=200;
    int u=v; 
    for(int j=1;j<n;j++)
   {
        if((s[j]!=true)&&(dist[j]<temp))
       {
          temp=dist[j];
          u=j;
       }
   }
   s[u]=true;
}
        

在结点u被加入后,需要更新dist[ j ]的值,其他未进入集合S的点 j 可以与源点直接相连,也可以与刚加入的结点u相连,若出现源点经过点u到达点 j 的距离小于此时的dist [j],则需要用c[u][j]+dist[u]的值去代替dist[j],该部分代码放置于for(int i=1;i<=n;i++)循环,代码如下:

for(int j=1;j<=n;j++)
{
    int newdist=dist[u]+c[u][j];
    if((s[j]!=true)&&(newdist<dist[j]))
   {
        dist[j]=newdist;
        prev[j]=u;
   } 
}

经过以上循环后,主程序如下:

int main()
{
    int prev[6]={0};
    int dist[6]={0};
    int c[6][6];
    for(int i=0;i<6;i++)
    {
        for(int j=0;j<6;j++)
        {
            cin>>c[i][j];
        }
    }
    Dij(prev,dist,5,1,c);
    for(int i=0;i<6;i++)
    {
        cout<<prev[i]<<" ";
    }
    cout<<endl;
    for(int j=0;j<6;j++)
    {
        cout<<dist[j]<<" ";
    }
    return 0;
}

输出结果如下所示,prev数组值代表的是每个下标的前驱结点下标,dist数组值代表源点1到各个结点的最短路径。

单源最短路径结果
#include <iostream>

using namespace std;

void Dij(int prev[],int dist[],int n,int v,int c[6][6])
{

    bool s[6]={false};
    for(int i=1;i<=n;i++)
    {
        if(c[v][i]<200)
        {
            prev[i]=1;
            dist[i]=c[v][i];
        }
        else
        {
            prev[i]=0;
            dist[i]=200;
        }
    }
    prev[v]=0;
    s[v]=true;
    dist[v]=0;
    for(int i=1;i<=n;i++)
    {
        int temp=200;
        int u=v;
        for(int j=1;j<=n;j++)
        {
            if((s[j]!=true)&&(dist[j]<temp))
            {
                temp=dist[j];
                u=j;
            }
        }
        s[u]=true;
        for(int j=1;j<=n;j++)
        {
            int newdist=dist[u]+c[u][j];
            if((s[j]!=true)&&(newdist<dist[j]))
            {
                dist[j]=newdist;
                prev[j]=u;
            }
        }
    }
}

int main()
{
    int prev[6]={0};
    int dist[6]={0};
    int c[6][6];
    for(int i=0;i<6;i++)
    {
        for(int j=0;j<6;j++)
        {
            cin>>c[i][j];
        }
    }
    Dij(prev,dist,5,1,c);
    for(int i=0;i<6;i++)
    {
        cout<<prev[i]<<" ";
    }
    cout<<endl;
    for(int j=0;j<6;j++)
    {
        cout<<dist[j]<<" ";
    }
    return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值