最短路算法-迪杰斯特拉算法

1.图中的最短路径求解的算法花样百出,但是最常用的就是那几种很耳熟能详的算法。今天我们来了解一下十分常用的迪杰斯特拉算法。

2.dj是一种求非负权值的单源最短路算法。通俗的讲就是求这样的问题:在图中的两个点s,t并且这个图中没有负的边权,那么求解从s到t的最短的路径权值和是多少。首先说明几个特点,注意dj的使用范围,非负权值的单源最短路。但是虽然我们求的是s到t的最短路,但是求解过程中会把s到图中所有点的最短路都求出来。这是dj的一些特性,为什么会有这样的特性,我们来看一下原理就明白了。

3.dj算法的原理:首先看图

                                                    

假设我们要求0到4的最短路,那么其实dj是这样运行的,它的基本思想就是贪心和松弛。他首先把点分为两个集合s和t,s代表已经求得的最短路径的点,t代表还未求得最短路的点。初始位置s里面只有源点,也就是0其他节点都在t里。然后会有一个dis数组,保存当前状态下源点到各个节点的最短路,dis初始化的结果就是源点到各个点之间的路径长度,如果有直接的连边,就是这个连边的长度,如果没有就是INF,例如上图中我们可以知道dis[1]=10,dis[4]=100,dis[2]=inf。然后就可以开始工作了。工作的流程是这样的,首先我们从源点出发,找到距离最近的点(贪心),没错,我们在这个图中找到的就是10,找到之后更新dis对应的值(当然这里的更新是没有意义的,因为是一样的),然后我们松弛这是很重要的,或者说这就是dj的精髓。怎么松弛呢?是这样工作的,我们现在已经有了1点,1被加入s集合中,我们通过1可以到达2,并且路径的长度是50,所以0到达2的路径长度就是60,因为之前0到2是没有直接的路径的,所以我们这个时候就可以把路径更新到60,这样就完成了松弛。当然了,dj并不仅仅是这么运作的,当找到1之后,他会计算发现到2的长度是60,但是到3的长度是30(这里的到指的是源点到其他点),所以他会选择小的去,所以第二个被加入的节点是3而不是2(这也是一种贪心的策略)。然后这个时候我们依然使用上边的方法,我们发现这个时候可以到达的点有2,4,但是到达4的最短路径是90,到达2的是50(通过3来松弛),这个时候2倍加入s集合,2被加入s集合之后,我们又可以通过2来松弛4,所以到4的最短路径就是60了。这样所有点被加入集合。最短路算法完成。

4.疑惑的解决:开始的时候我们就提出了两个问题。1.dj是基与非负权值的。2.dj虽然是求两个点之间的最短路,但是会把源点到其他所有点的最短路都求出来。现在看完原理之后我想就很好回答这个问题了,首先为什么是非负权值,因为dj的松弛过程决定的,如果出现负边,那么松弛的时候就会在哪个具有负边的地方循环。其次,虽然是求两点之间的最短路,但是任何一点的加入都有可能会对这两点之间的最短路产生松弛。所以我们需要把所有的点都加进去试一遍。

5.dj的复杂度是n方的,这个与图是怎么存的没有关系。下面给出实现的代码:

/*Dijkstra求单源最短路径 */
 
#include <iostream>
#include<stack>
#define M 100
#define N 100
using namespace std;

typedef struct node
{
    int matrix[N][M];      //邻接矩阵 
    int n;                 //顶点数 
    int e;                 //边数 
}MGraph; 

void DijkstraPath(MGraph g,int *dist,int *path,int v0)   //v0表示源顶点 
{
    int i,j,k;
    bool *visited=(bool *)malloc(sizeof(bool)*g.n);
    for(i=0;i<g.n;i++)     //初始化 
    {
        if(g.matrix[v0][i]>0&&i!=v0)
        {
            dist[i]=g.matrix[v0][i];
            path[i]=v0;     //path记录最短路径上从v0到i的前一个顶点 
        }
        else
        {
            dist[i]=INT_MAX;    //若i不与v0直接相邻,则权值置为无穷大 
            path[i]=-1;
        }
        visited[i]=false;
        path[v0]=v0;
        dist[v0]=0;
    }
    visited[v0]=true;
    for(i=1;i<g.n;i++)     //循环扩展n-1次 
    {
        int min=INT_MAX;
        int u;
        for(j=0;j<g.n;j++)    //寻找未被扩展的权值最小的顶点 
        {
            if(visited[j]==false&&dist[j]<min)
            {
                min=dist[j];
                u=j;        
            }
        } 
        visited[u]=true;
        for(k=0;k<g.n;k++)   //更新dist数组的值和路径的值 
        {
            if(visited[k]==false&&g.matrix[u][k]>0&&min+g.matrix[u][k]<dist[k])
            {
                dist[k]=min+g.matrix[u][k];
                path[k]=u; 
            }
        }        
    }    
}

void showPath(int *path,int v,int v0)   //打印最短路径上的各个顶点 
{
    stack<int> s;
    int u=v;
    while(v!=v0)
    {
        s.push(v);
        v=path[v];
    }
    s.push(v);
    while(!s.empty())
    {
        cout<<s.top()<<" ";
        s.pop();
    }
} 

int main(int argc, char *argv[])
{
    int n,e;     //表示输入的顶点数和边数 
    while(cin>>n>>e&&e!=0)
    {
        int i,j;
        int s,t,w;      //表示存在一条边s->t,权值为w
        MGraph g;
        int v0;
        int *dist=(int *)malloc(sizeof(int)*n);
        int *path=(int *)malloc(sizeof(int)*n);
        for(i=0;i<N;i++)
            for(j=0;j<M;j++)
                g.matrix[i][j]=0;
        g.n=n;
        g.e=e;
        for(i=0;i<e;i++)
        {
            cin>>s>>t>>w;
            g.matrix[s][t]=w;
        }
        cin>>v0;        //输入源顶点 
        DijkstraPath(g,dist,path,v0);
        for(i=0;i<n;i++)
        {
            if(i!=v0)
            {
                showPath(path,i,v0);
                cout<<dist[i]<<endl;
            }
        }
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_41863129/article/details/85222298