最短路径-Dijkstra算法

最短路径

网图和非网图中,最短路径的含义是不同的。由于非网图它没有边上的权值,所谓的最短路径,其实就是指两顶点之间经过的边数最少的路径;

而对于网图来说,最短路径,是指两顶点之间经过的边上权值之和最少的路径,并且我们称路径上的第一个顶点是源点,最后一个顶点是终点。显然,研究网图更具有实际意义,就地图来说,距离就是两顶点间的权值之和。而非网图完全可以理解为所有边的权值都为1的网。


Dijkstra算法

Dijkstra算法通常以邻接矩阵为存储结构。

它的基本思路是:设G=(V,E)是一个带权有向图,把图中顶点集合V分成两组,第一组为已求出最短路径的顶点集合(用S表示,初始时S中只有一个源点,以后每求得一条最短路径 , 就将加入到集合S中,直到全部顶点都加入到S中,算法就结束了),第二组为其余未确定最短路径的顶点集合(用U表示)按最短路径长度的递增次序依次把第二组的顶点加入S中。在加入的过程中,总保持从源点v到S中各顶点的最短路径长度不大于从源点v到U中任何顶点的最短路径长度。此外,每个顶点对应一个距离,S中的顶点的距离就是从v到此顶点的最短路径长度,U中的顶点的距离,是从v到此顶点只包括S中的顶点为中间顶点的当前最短路径长度。可以看出是一种贪心的思想。


下面看具体数据和模拟过程


typedef struct{
    VertexType vexs[MAXVEX];//顶点信息,string类型
    EdgeType arc[MAXVEX][MAXVEX];//边的信息
    int numVertexes,numEdges;//顶点数和边数
}MGraph;

如上图所示的邻接矩阵,其中MAXVEX为最大顶点数,定义参数V0为0,Dijkstra算法代码如下:

#define MAXVEX 9
#define INFINITY 0x3fffffff  
typedef int Patharc[MAXVEX];//用于存储最短路径下标的数组
typedef int ShortPathTable[MAXVEX];//用于存储到各点最短路径的权值之和
void ShortestPath_Dijkstra(MGraph G,int v0,Patharc *p,ShortPathTable *D)//最短路求解,这里V0为0 
{
    int v,w,k,Min;
    int Final[MAXVEX];//标记,=1表示求得顶点V0至Vw的最短路径
    for(v=0;v<G.numVertexes;v++){//初始化数据
        Final[v]=0;//全部顶点初始化为未知最短路径状态
        (*D)[v]=G.arc[v0][v];//将与V0有连线的顶点加上权值
        (*P)[v]=0;//初始化路径数组pre顶点均为起始点V0
    }
    (*D)[v0]=0;//v0至v0路径为0
    Final[v0]=1;//v0至v0不需要求路径
    for(v=1;v<G.numVertexes;v++){
        Min=INFINITY;//初始化最小值为INF
        for(w=0;w<G.numVertexes;w++){
            if(!Final[w]&&(*D)[w]<Min){
                k=w;
                Min=(*D)[w];//k顶点离v0顶点最近
            }
        }
        Final[k]=1;//将目前找到的最近的顶点位置置为1
        for(w=0;w<G.numVertexes;++w){//修正当前最短路径及距离
            //如果经过k顶点的路径比现在已有的路径的长度短的话
            if(!Final[w]&&(Min+G.arc[k][w]<(*D)[w])){
                (*D)[w]=Min+G.arc[k][w];//修改当前路径长度
                (*P)[w]=k;
            }
        }
    }
}

1.程序开始运行,Final数组是为了V0到某顶点是否已经求得最短路径的标记,如果V0到Vw已经有结果,则Final[w]=1


2.第9~13行对数据初始化。此时Final数组值均为0,表示所有的点都未求得最短路径。D数组为{inf,1,5,inf,inf,inf,inf,inf,inf}。因为V0与V1和V2的边权值为1和5。P数组全为0,表示目前没有路径。


3.第14行,表示V0到V0自身,权值和结果为0。D数组为{0,1,5,inf,inf,inf,inf,inf,inf}。第15行,表示V0点算是已经求得最短路径,因此Final[0]=1。此时Final数组为{1,0,0,0,0,0,0,0,0}。此时整个初始化工作完成。


4.下面是主循环,每次循环求得V0与一个顶点的最短路径。因此V从1而不是0开始。


5.先令Min为inf,通过w循环,与D[w]比较找到最小值Min=1,k=1。


6.第24行,由k=1,表示与V0最近的顶点是V1,并且由D[1]=1,知道此时V0到V1的最短距离是1。因此将V1对应的Final[1]设置为1。此时Final数组为{1,1,0,0,0,0,0,0,0}。


7.第25~32行是一循环,此循环甚为关键。它的目的是在刚才已经找到V0与V1的最短路径的基础上,对V1与其他顶点的边进行计算,得到V0与它们的当前最短距离,如下图所示。因为min=1,所以本来D[2]=5,现在v0->v1->v2=D[2]=min+3=4,v0->v1->v3=D[3]=min+7=8,v0->v1->v4=D[4]=min+5=6,因此,D数组当前值为{0,1,4,8,6,inf,inf,inf,inf}。而P[2]=1,P[3]=1,P[4]=1,它表示的意思是V0到V2、V3、V4点的最短路径它们的前驱均是V1。此时P[]数组值为:{0,0,1,1,1,0,0,0,0}。


8.重新开始循环,此时V=2。下面对w循环,注意因为Final[0]=1和Final[1]=1,由!Final[w]可知,V0与V1并不参与最小值的获取。通过循环比较,找到最小值min=4,k=2。


9.第24行,由k=2,表示已经求出V0到V2的最短路径,并且由D[2]=4,知道最短距离是4。因此将V2对应的Final[2]设置为1,此时Final数组为:{1,1,1,0,0,0,0,0,0}。


10.第25~32行。在刚才已经找到V0与V2的最短路径的基础上,对V2与其他顶点的边,进行计算,得到V0与它们的当前最短距离,如下图所示。因为Min=4,所以本来D[4]=6,现在v0->v2->v4=D[4]=min+1=5,v0->v2->v5=D[5]=min+7=11,因此,D数组当前值为:{0,1,4,8,5,11,inf,inf,inf}。而原本P[4]=1,此时P[4]=2,P[5]=2,它表示V0到V4、V5点的最短路径它们的前驱均是V2。此时P数组值为:{0,0,1,1,2,2,0,0,0}。


11.重新开始循环,此时V=3。第17~21行,通过对w循环比较找到最小值min=5,k=4。


12.第24行,由k=4,表示已经求出V0到V4的最短路径,并且由D[4]=5,知道最短距离是5。因此将v4对应的Final[4]设置为1。此时Final数组为:{1,1,1,0,1,0,0,0,0}。


13.第25~32行。对V4与其他顶点的边进行计算,得到V0与它们的当前最短距离,如下图所示。因为Min=5,所以本来D[3]=8,现在v0->v4->v3=D[3]=min+2=7,本来D[5]=11,现在v0->v4->v5=D[5]=min+3=8,另外v0->v4->v6=D[6]=min+6=11,v0->v4->v7=D[7]=min+9=14,因此,D数组当前值为:{0,1,4,7,5,8,11,14,inf}。而原本P[3]=1,此时P[3]=4,原本P[5]=4,另外P[6]=4,P[7]=4,它表示v0到v3、v5、v6、v7点的最短路径它们的前驱均是v4。此时P数组值为:{0,0,1,4,2,4,4,4,0}。


14.之后的循环就完全类似了。得到最终的结果,如下图所示。此时Final数组为{1,1,1,1,1,1,1,1,1},它表示所有的顶点均完成了最短路径的查找工作。此时D数组为:{0,1,4,7,5,8,10,12,16},它表示V0到各个顶点的最短路径数,比如D[8]=1+3+1+2+3+2+4=16。此时的P数组为:{0,0,1,4,2,4,3,6,7},这串数字可能略微难理解一点。比如P[8]=7,它的意思是v0到v8的最短路径,顶点v8的前驱顶点是v7,再由P[7]=6表示v7的前驱是v6,P[6]=3,表示v6的前驱是v3。这样就可以得到,v0到v8的最短路径为v0->v1->v2->v4->v3->v6->v7->v8。



总结

       上面的算法最终返回的数组D和数组P,是可以得到V0到任意一个顶点的最短路径和路径长度的。例如v0到v8的最短路径并没有经过v5,但我们已经知道v0到v5的最短路径了。由D[5]=8可知它的路径长度为8,由P[5]=4可知v5的前驱顶点是v4,所以v0到v5的最短路径是v0->v1->v2->v4->v5。

       也就是说我们通过Dijkstra算法解决了从某个源点到其余各顶点的最短路径问题。从循环嵌套可以得到此算法的时间复杂度为O(n^2)

       如果我们还需要知道v3到v5、v1到v7这样的任意一顶点到其余所有顶点的最短路径怎么办呢?可以再原来Dijkstra算法的基础上,再来一次循环,此时算法的复杂度为O(n^3)

       求所有顶点到所有顶点还有一种非常简洁优雅的算法------Floyd算法,Dijkstra算法使用的前提是图中路径长度必须大于等于0但是Floyd算法则仅仅要求没有总和小于0的环路就可以了。



例:POJ - 1502

求v1到其他点最短路径中longest path

#include<iostream>
#include<string.h>
#include<cstdio>
#include<stdlib.h>
using namespace std;


const int maxn=1000,inf=0x3f3f3f3f;


int dis[maxn];//记录从源点到某一点的最短距离
int G[maxn][maxn],ans;
bool v[maxn];//标记是否求得到某一点的最短距离


int n;


void dijkstra()
{
    for(int i=1;i<=n;++i) dis[i]=inf;
    dis[1]=0;
    memset(v,0,sizeof(v));
    for(int i=1;i<=n;++i){
        int mark=-1,mindist=inf;
        for(int j=1;j<=n;++j){
            //printf("%d ",dis[j]);
            if(!v[j]&&dis[j]<mindist){
                mindist=dis[j];
                mark=j;
            }
        }
        //printf("\n%d\n",mark);
        v[mark]=1;
        ans=max(ans,mindist);
        for(int j=1;j<=n;++j){
            if(!v[j]&&dis[mark]+G[mark][j]<dis[j]){
                dis[j]=dis[mark]+G[mark][j];
            }
        }
    }
}


int main()
{
    //ios::sync_with_stdio(false);
    string s;
    s.resize(10);
    scanf("%d",&n);
    ans=-inf;
    for(int i=1;i<=n;++i){
        G[i][i]=0;
    }
    for(int i=2;i<=n;++i){
        for(int j=1;j<i;++j){
            scanf("%s",&s[0]);
            //printf("%s\n",s.c_str());
            if(s[0]=='x') G[i][j]=inf;
            else G[i][j]=atoi(s.c_str());//printf("%d\n",G[i][j]);
            G[j][i]=G[i][j];
        }
    }
    dijkstra();
    printf("%d\n",ans);
    return 0;
}


优先队列优化

//求v1到其他点最短路径中longest path
//优先队列优化O(O(MlogN))
#include<iostream>
#include<string.h>
#include<cstdio>
#include<stdlib.h>
#include<queue>
using namespace std;
#define mp make_pair

typedef pair<int,int> node;
const int maxn=1000,inf=0x3f3f3f3f;

vector<node> G[maxn];//first 点的编号 second 距离

void addEdge(int u,int v,int w){
    G[u].push_back(mp(v,w));
    G[v].push_back(mp(u,w));
}

int dis[maxn];//记录从源点到某一点的最短距离
int n;//点数
int ans;

void dijkstra(int s)
{
    priority_queue<node,vector<node>,greater<node> > que;//小根堆 默认比较first
    //first存起点到该点最短距离 second是点的编号
    for(int i=1;i<=n;++i) dis[i]=inf;
    dis[s]=0;
    que.push(mp(0,s));//起点
    while(!que.empty())
    {
        node p=que.top();que.pop();
        int v=p.second;//顶点的编号
        if(dis[v]<p.first) continue;
        //否则更新
        ans=max(ans,p.first);
        for(int i=0;i<G[v].size();++i){//松弛
            node e=G[v][i];
            if(dis[v]+e.second<dis[e.first]){
                dis[e.first]=dis[v]+e.second;
                que.push(mp(dis[e.first],e.first));
            }
        }
    }
}

int main()
{
    //ios::sync_with_stdio(false);
    string s;
    s.resize(10);
    scanf("%d",&n);
    ans=-inf;
    for(int i=2;i<=n;++i){
        for(int j=1;j<i;++j){
            scanf("%s",&s[0]);
            //printf("%s\n",s.c_str());
            if(s[0]=='x') continue;
            else addEdge(i,j,atoi(s.c_str()));
        }
    }
    dijkstra(1);
    printf("%d\n",ans);
    return 0;
}






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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值