Dijkstra 算法小结

Dijkstra是图论中用来求特定结点到其他所有结点的最短路径长度,即单源最短路问题的算法。


个人总结思路:

基本原理是按照最短路径长度递增的顺序确定每一个结点的最短路径长度,即先确定的结点的最短路径长度不大于后确定的结点的最短路径长度。根据结点数量进行一个大循环,每将一个新的结点加入已知路径集合中,都检查一遍所有与它相连的结点,更新从源点出发到达它们的距离向量(重要,根据结点是否在已知路径集合中有两种意义),选择其中最近的结点作为新加入已知路径集合的点并作为下次循环考查的结点,当所有结点考查完毕,则大循环结束。

(以下图片来自http://www.cnblogs.com/biyeymyhjob/archive/2012/07/31/2615833.html,其中的"S集合"下文中以"K集合"代替,"U集合"即"非K集合")






(动图可在理解算法之后看,或者用 gif分解工具拆解后慢慢理解)


算法流程:

(1)初始化,集合K中加入结点S,结点S到结点S本身的最短距离是0,到其他结点距离设为无穷。

(2)遍历与集合K中结点直接相邻的边(U,V,C),其中U属于集合K;V不属于集合K。计算由源结点S出发按照已经得到的最短路到达U,再由U经过这些边到达V时的路径长度,选择其中距离最短的结点作为下一个加入集合K的结点。

(3)若集合K中已经包含了所有的点,算法结束;否则重复步骤(2)。



程序输入输出格式:

输入:第一行输入n和m,n是点数,m是无向边数。接下来是m行,每行3个数a,b,c,表示a和b之间有一条边,权重(长度)为c。最后一行是两个数,起点S和终点T。n和m均为0时输入结束。

输出:S到T的最短距离。

#include<cstdio>
#include<vector>

using namespace std;

struct E//邻接链表中的链表元素结构体(U,V,C)
{
    int next;//结点V
    int c;//该边权值C
};

vector<E> edge[101];
bool mark[101];
int Dis[101];//距离向量
int main()
{
    int n,m;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        if(n==0&&m==0)break;
        for(int i=0;i<n;++i)
        {
            edge[i].clear();
            Dis[i]=-1;
            mark[i]=false;
        }
        while(m--)
        {
            int a,b,c;
            scanf("%d%d%d",&a,&b,&c);
            E tmp;
            tmp.next=b;
            tmp.c=c;
            edge[a].push_back(tmp);
            tmp.next=a;
            edge[b].push_back(tmp);
            //将邻接信息加入邻接链表,由于原图为无向图,
            //故每条边信息都要添加到其两个顶点的两条单链表中
        }
        int S,T;
        scanf("%d%d",&S,&T);
        Dis[S]=0;
        mark[S]=true;
        int newP=S;
        for(int i=0;i<n;++i)
        {
            //循环n-1次,按照最短路径递增的顺序确定其他n-1个点的最短路径长度
            for(int j=0;j<edge[newP].size();++j)
            {
                //遍历与该新加入集合K中的结点直接相邻的边
                if(mark[j]==true)continue;
                int t=edge[newP][j].next;
                int c=edge[newP][j].c;
                if(Dis[t]==-1||Dis[t]>Dis[newP]+c)
                {
                    //若该结点尚不可达,或者该结点从新加入的结点经过一条边到达时比以往距离更短
                    Dis[t]=Dis[newP]+c;
                }
            }
            int min=13212313;//最小值初始化为一个大整数,为找最小值做准备
            for(int j=0;j<n;++j)
            {
                //遍历所有结点,用打擂法确定要新加入集合K的点
                //同时注意跳过集合K中已有的点和不可达的点
                if(mark[j]==true)continue;
                if(Dis[j]==-1);continue;
                if(Dis[j]<min)
                {
                    min=Dis[j];
                    newP=j;
                }
            }
            mark[newP]=true;
        }
        printf("%d\n",Dis[T]);
    }
    return 0;
}

代码重点强调:

使用C++中STL的vector模拟邻接链表,每个结点有一个vector数组,其中保存了该结点邻接其它结点的(U,V,C)。vector是一个动态数组,详情可查手册,当然这里也可以用二维数组来表示;

结点距离初始化设为-1,因为该算法无法处理权值为负的图,故可将-1视为无穷;

mark[i]用来表示结点i是否属于集合K,即结点i是否已包含在得到的最短路中;

Dis[i]有两种意义:当结点i属于集合K时,它表示结点i已经被确定的最短路径长度;当结点i还不属于集合K时,它表示所有从源节点出发先按照某条最短路径到达某已经在集合K中的结点,并由该结点经过一条边到达结点i路径中的最短距离

若要输出最短路沿路经过的各个中间点,可维护一个记录路径中各结点前驱的数组pred[i],表示到达i结点的最短路径上前一个点,输出时可倒序输出或压入栈中再正序输出。



参考资料:

王道论坛 组编,《计算机考研——机试指南》,电子工业出版社

猜你喜欢

转载自blog.csdn.net/Tele_Anti_Nomy/article/details/79862359
今日推荐