浅谈数据结构-最短路径

最短路径和最小生成树在应用很是不同的,比如:一开始修建一条地铁,然后在地铁点上有多个点,需要修建一个路程最短的地铁线,将这些地铁点连接起来,这就是最小生成树(点与点之间距离是已知的)。小强需要从A点去B点旅游,中间会经过好几个点,需要找出条最短路径到达B点。从应用上明显看出,两者的目的不同、初始化条件也是不同的。

一、Dijkstra(迪杰斯特拉)算法

Dijkstra(迪杰斯特拉)算法是典型的最短路径路由算法,用于计算一个节点到其他所有节点的最短路径。主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。

1、算法思想

令G = (V,E)为一个带权有向图,把图中的顶点集合V分成两组,第一组为已求出最短路径的顶点集合S(初始时S中只有源节点,以后每求得一条最短路径,就将它对应的顶点加入到集合S中,直到全部顶点都加入到S中);第二组是未确定最短路径的顶点集合U。在加入过程中,总保持从源节点v到S中各顶点的最短路径长度不大于从源节点v到U中任何顶点的最短路径长度。

针对上图建立的两个集合,之后运用Dijkstra算法运行后,两个集合中元素为将ABCDEF在数组中坐标为1,2,3,4,5,6:

迭代 S U dist[2] dist[3] dist[4] dist[5] dist[6]
1 A - 6 3 MAX MAX MAX
2 A,C C 5 3 6 7 MAX
3 A,C,B B 5 3 6 7 MAX
4 A,C,B,D D 5 3 6 7 9
5 A,C,B,D,E E 5 3 6 7 9
6 A,B,C,D,E,F F 5 3 6 7 9

2、算法分析

根据上面分析,得知需要创建两个数组,创建顶点集合,还有边集合,用于保存点到各个边的权重,然后在权重集合选取最小的权值边对的顶点,然后继续循环。

  1. 创建顶点结合nNodeIndex,初始化为0,数组中为1是,表示对应的顶点已经添加到最短路径顶点集合S了。
  2. 创建初始顶点到各个顶点的边集合,保存此顶点到各个顶点的距离(权重),用邻接矩阵中行元素初始化(类似最小生成树)。
  3. 循环计算此顶点到各个顶点的最小值,得知后nNodeIndex[i] = 1,同时更新边集合 的数值。
  4. 总体上讲还是比较容易的。

3、例图解释

4、代码

//Dijkstra算法
void GraphData::ShortPath_Dijkstra(GraphArray *pArray)
{
    //Dijkstra算法和最小生成树的算法,在某种程度是相似的
    int min,i,j,k;
    int nNodeIndex[MAXVEX];      //保存相关顶点坐标,1就是已经遍历访问的过结点(在最小生成树中为数值表示遍历过同时值与坐标是一条边)
    int nNodeWeight[MAXVEX];     //保存某个顶点到各个顶点的权值,为不为0和最大值表示遍历过了。
    int nPathLength[MAXVEX];     //坐标和元素表示为同时值和坐标表示一边,与Primes有相同的
    //两个数组的初始化
    printf("开始初始化,当前顶点边的权值为:");
    for(i = 0;i<pArray->numVertexes;i++)
    {
        nNodeIndex[i] = 0;
        nNodeWeight[i] = pArray->arg[0][i];//设定在矩阵中第一个顶点为初始点。
        nPathLength[i] = 0;
        printf(" %c",nNodeWeight[i]);
    }
    nNodeWeight[0] = 0;  //选取坐标点0为起始点。
    nNodeIndex[0] = 1;  //这样0就是起始点,设为1.(和Prime的不同)
    //算法思想
    for (i = 1;i< pArray->numVertexes;i++)
    {
        min = INFINITY;    //初始化权值为最大值;
        j = 1;
        k = 0;
        // 循环全部顶点,寻找与初始点边权值最小的顶点,记下权值和坐标
        while(j < pArray->numVertexes)
        {
            //如果权值不为0,且权值小于min,为0表示本身
            if (!nNodeIndex[j]&&nNodeWeight[j] < min)  //这里Prime是权重中不为0,
            {
                min     = nNodeWeight[j];
                k = j;     //保存上述顶点的坐标值
            }
            j++;
        }
        printf("当前顶点边中权值最小边(%d,%d)\n",nNodeIndex[k] , k); //打印当前顶点边中权值最小
        //nNodeWeight[k] = 0; //将当前顶点的权值设置为0,表示此顶点已经完成任务
        nNodeIndex[k] = 1;     //将目前找到的最近的顶点置为1

        //for (j = 1;j< pArray->numVertexes;j++)  //循环所有顶点,查找与k顶点的最小边
        //{
        //    //若下标为k的顶点各边权值小于此前这些顶点未被加入的生成树权值
        //    if (nNodeWeight[j] != 0&&pArray->arg[k][j] < nNodeWeight[j])
        //    {
        //        nNodeWeight[j] = pArray->arg[k][j];
        //        nNodeIndex[j] = k;     //将下标为k的顶点存入adjvex
        //    }
        //}
        //修正当前最短路径及距离
        for (j = 1;j< pArray->numVertexes;j++)  //循环所有顶点,查找与k顶点的最小边
        {
            //若下标为k的顶点各边权值小于此前这些顶点未被加入的生成树权值
            if (!nNodeIndex[j] && pArray->arg[k][j] + min< nNodeWeight[j])
            {
                nNodeWeight[j] = pArray->arg[k][j] + min;
                nPathLength[j] = k;     //将下标为k的顶点存入adjvex
            }
        }
        //打印当前顶点状况
        printf("坐标点数组为:");
        for(j = 0;j< pArray->numVertexes;j++)
        {
            printf("%3d ",nPathLength[j]);
        }
        printf("\n");
        printf("权重数组为:");
        for(j = 0;j< pArray->numVertexes;j++)
        {
            printf("%3d ",nNodeWeight[j]);
        }
        printf("\n");
    }

}

5、代码分析

上图就是图的邻接矩阵,对应上面的例图分析,我们分析下,代码运算结果。

 

上图是代码运算后的结果,首先是第一次,最小边为(0,2),加入的顶点是C。第二次最小边是(0,1),加入的结果是B。等等,会发现结果同前面例图分析结果一样。

最后的权重数组为(0,5,3,6,7,9)意味着顶点A到B,C,D,E,F的距离是5,3,6,7,9。A->B:5,根据坐标点数组nPathLength[1] = 2,意味着经过坐标为2的顶点C,再看nPathLength[2] = 0,结束。再比如A->D:6,nPathLength[4] = 2,所以为A->C->D.同理其他路劲也是如此寻找。

二、Floyd算法

Dijkstra算法解决了某个源点到其余各个顶点的最短距离问题。从循环语句上判断,算法的时间复杂度是O(n2)。在循环的外面再加一个循环,也就成了所有顶点的最短距离。此时算法的复杂度就是O(n3).

弗洛依德(Floyd)算法就是一个事件复杂度为O(n)的算法,只不过算法非常简洁优雅。

1、算法思想

Floyd算法是一个经典的动态规划算法。用通俗的语言来描述的话,首先我们的目标是寻找从点i到点j的最短路径。从动态规划的角度看问题,我们需要为这个目标重新做一个诠释(这个诠释正是动态规划最富创造力的精华所在)。

      从任意节点i到任意节点j的最短路径不外乎2种可能,1是直接从i到j,2是从i经过若干个节点k到j。所以,我们假设Dis(i,j)为节点u到节点v的最短路径的距离,对于每一个节点k,我们检查Dis(i,k) + Dis(k,j) < Dis(i,j)是否成立,如果成立,证明从i到k再到j的路径比i直接到j的路径短,我们便设置Dis(i,j) = Dis(i,k) + Dis(k,j),这样一来,当我们遍历完所有节点k,Dis(i,j)中记录的便是i到j的最短路径的距离。

2、算法分析

  1. 创建一个矩阵,记录两个顶点的权值。在DIjkstra算法中是记录一个顶点到其他顶点的路径长度,声明一个数组,此处是各个顶点,所以为矩阵。
  2. 创建一个矩阵,记录顶点到另个顶点路径的走法,这个后面会讲解。
  3. Floyd算法思想:对于每一对顶点 u 和 v,看看是否存在一个顶点 w 使得从 u 到 w 再到 v 比己知的路径更短。将权值鞠振宁更新,同样还有路径矩阵。

3、例图解释

(1)程序开始运行,第4-11行就是初始化了D和P,使得它们成为   上图    的两个矩阵。从矩阵也得到,v0->v1路径权值为1,v0->v2路径权值为5,v0->v3无边连线,所以路径权值为极大值65535。

    (2)第12~25行,是算法的主循环,一共三层嵌套,k代表的就是中转顶点的下标。v代表起始顶点,w代表结束顶点。

    (3)当k = 0时,也就是所有的顶点都经过v0中转,计算是否有最短路径的变化。可惜结果是,没有任何变化,如下图所示。

(4)当k = 1时,也就是所有的顶点都经过v1中转。此时,当v = 0 时,原本D[0][2] = 5,现在由于D[0][1] + D[1][2] = 4。因此由代码的的第20行,二者取其最小值,得到D[0][2] = 4,同理可得D[0][3] = 8、D[0][4] = 6,当v = 2、3、4时,也修改了一些数据,请看下图左图中虚线框数据。由于这些最小权值的修正,所以在路径矩阵P上,也要做处理,将它们都改为当前的P[v][k]值,见代码第21行。

  (5)接下来就是k = 2,一直到8结束,表示针对每个顶点做中转得到的计算结果,当然,我们也要清楚,D0是以D-1为基础,D1是以D0为基础,......,D8是以D7为基础的。最终,当k = 8时,两个矩阵数据如下图所示。

至此,我们的最短路径就算是完成了。可以看到矩阵第v0行的数值与迪杰斯特拉算法求得的D数组的数值是完全相同。而且这里是所有顶点到所有顶点的最短路径权值和都可以计算出。

    那么如何由P这个路径数组得出具体的最短路径呢?以v0到v8为例,从上图的右图第v8列,P[0][8]= 1,得到要经过顶点v1,然后将1取代0,得到P[1][8] = 2,说明要经过v2,然后2取代1得到P[2][8] = 4,说明要经过v4,然后4取代2,得到P[4][8]= 3,说明要经过3,........,这样很容易就推倒出最终的最短路径值为v0->v1->v2->v4->v3->v6->v7->v8。

 

4、示例代码

//Floyd算法
void GraphData::ShortPath_Floyd(GraphArray *pArray)
{
    int i,j,m,k;
    int nNodeIndex[MAXVEX][MAXVEX];
    int nNodeWeight[MAXVEX][MAXVEX];

    for ( i = 0;i< pArray->numVertexes;i++)
    {
        for (j = 0;j< pArray->numVertexes;j++)
        {
            nNodeIndex[i][j] = j;                         /* 初始化 */
            nNodeWeight[i][j] = pArray->arg[i][j];  /* [i][j]值即为对应点间的权值 */
        }
    }
    for (i = 0;i< pArray->numVertexes;i++)
    {
        for (j = 0;j< pArray->numVertexes;j++)
        {
            for (k = 0;k<pArray->numVertexes;k++)
            {
                if (pArray->arg[j][k] > pArray->arg[j][i] + pArray->arg[i][k])
                {
                    /* 如果经过下标为k顶点路径比原两点间路径更短 */
                    nNodeWeight[j][k] = pArray->arg[j][i] + pArray->arg[i][k];  /* 将当前两点间权值设为更小的一个 */
                    nNodeIndex[j][k] = nNodeIndex[j][i];  /* 路径设置为经过下标为k的顶点 */
                }
            }
        }
    }
    for (i = 0; i< pArray->numVertexes;i++)
    {
        for (j = 0;j< pArray->numVertexes;j++)
        {
            printf("v%d-v%d weight: %d",i,j,nNodeWeight[i][j]);
                m = nNodeIndex[i][j];    //获得第一个路径点的顶点下标
            printf("path :%d",i);    //打印源点
            while(m!=j)
            {
                printf(" -> %d",m);   //打印路径顶点
                m = nNodeIndex[m][j];   //获取下一个路径顶点下标
            }
            printf(" -> %d\n",m);    //打印路径终点。
        }

        printf("\n");
    }
}

5、代码分析

 

同样对于Dijkstra算法中,同样的邻接矩阵,我们最后发现其中v0到各个顶点数据与Dijkstra中数据一样奥,同时显示出路径中通过的顶点。

时间: 2024-11-05 22:39:30

浅谈数据结构-最短路径的相关文章

浅谈数据结构-二叉树

浅谈数据结构-二叉树 二叉树是树的特殊一种,具有如下特点:1.每个结点最多有两颗子树,结点的度最大为2.2.左子树和右子树是有顺序的,次序不能颠倒.3.即使某结点只有一个子树,也要区分左右子树. 一.特殊的二叉树及特点 1.斜树 所有的结点都只有左子树(左斜树),或者只有右子树(右斜树).这就是斜树,应用较少 2.满二叉树 所有的分支结点都存在左子树和右子树,并且所有的叶子结点都在同一层上,这样就是满二叉树.就是完美圆满的意思,关键在于树的平衡. 根据满二叉树的定义,得到其特点为: 叶子只能出现

浅谈数据结构-树

树是一种数据结构,其中一个元素可以有两个或者多个数据元素,具有一对多的特点,用树结构来存储文件. 树的概念 结点的度:子结点的个数.例如结点1中有3个子结点,结点1的度是3. 树的度:树的度等于所有结点度中度最高的值.结点最高的度为3,树的度为3. 叶子结点:度为0的结点,即没有子结点的结点.例如:上图中3,5,6,7,9,10. 分支结点:除了叶子结点以外的结点,即度不为0的结点.例如:上面树的分支结点为1,2,4,8. 内部结点:除了根结点以及叶子结点或在分支结点的基础之上在去掉根结点.例如

浅谈数据结构之线性表顺序存储(一)

 首先,数据结构是由某一数据元素集合及该集合中所有数据元素之间的关系组成.具体来说,数据结构应当包含三方面的内容:(1).数据的逻辑结构:(2).数据的存储结构:(3).对数据所施加的操作.而数据的存储结构形式有两种:顺序存储与链式存储.在这里,先谈一谈线性表的顺序存储. 线性表:零个或多个数据元素的有限序列.第一,它是一个序列,也就是说,元素之间是有顺序的:第二,它是有限的,即元素个数是有限的.而线性表的顺序存储结构,说白了,就是在内存中找块地,通过占位的形式把一定的内存空间给占了,然后把相同

浅谈数据结构:哈希表

一.  基本概念 哈希表(hash table )是一种根据关键字直接访问内存存储位置的数据结构,通过哈希表,数据元素的存放位置和数据元素的关键字之间建立起某种对应关系,建立这种对应关系的函数称为哈希函数 二.哈希表的构造方法 假设要存储的数据元素个数是n,设置一个长度为m(m > n)的连续存储单元,分别以每个数据元素的关键字Ki(0<=i<=n-1)为自变量,通过哈希函数hash(Ki),把Ki映射为内存单元的某个地址hash(Ki),并将数据元素存储在内存单元中 从数学的角度看,哈

浅谈数据结构系列 栈和队列

计算机程序离不开算法和数据结构,在数据结构算法应用中,栈和队列应用你比较广泛,因为两者在数据存放和读取方面效率比较高,本章节重点讲解两者的基本概念和实现. 基本概念 栈:是一种先进后出,后进先出的数据结构,本质上是线性表,只是限制仅允许在表的一段进行插入和删除工作.此端为栈顶,这是在栈中应用很关键的概念.所有数据的处理都是在栈顶进行的,进栈时,栈中元素增加,栈顶上移一位,出栈时栈顶下移一位.应用中比如:洗碗,每次洗干净的碗放在上面-进栈,取碗,从顶上取出一个-出栈:装子弹-进栈,开枪-出栈. 队

浅谈数据结构-堆

在数据结构的世界中有一个叫堆的玩意,这玩意有什么用呢?无用,都去pq了 堆,其实就是一棵完全二叉树. “若设二叉树的深度为h,除第 h 层外,其它各层 (1-h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树”  by 谋财害命公司 百度 ↑清真的 完全二叉树 ↓ 啊那么为什么会闲的无聊出现这种奇怪的数据结构呢? 因为我们的某些算法可能需要堆来进行优化,如dj,prim. 堆可以在O(1)的时间取出最优值,但是需要O(logn)的时间修改和O(nlogn)

python学习-09(查找、排序和浅谈数据结构)

查找的方法: 排序的方法: 简单的数据结构: 一.算计基础 1.1.什么是算法: 算法(Algorithm)是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机制.也就是说,能够对一定规范的输入,在有限时间内获得所要求的输出.如果一个算法有缺陷,或不适合于某个问题,执行这个算法将不会解决这个问题.不同的算法可能用不同的时间.空间或效率来完成同样的任务.一个算法的优劣可以用空间复杂度与时间复杂度来衡量. 简单的说,算法就是一个计算的过程,解决问题的

浅谈数据结构-关键路径

上一章节讲解了拓扑排序问题,拓扑排序是解决一个工程能否顺序解决的问题,本质是一个广度层次遍历的过程,通过记录顶点入度问题,进行逐步输出的工作.在实际生活中,往往是求解工程完成需要最短时间问题.比如生活中生产一辆汽车,需要生产各种各样的零件,最终组装成车.例如生产轮子0.5天,发动机3天,底盘2天,其他部件2天,集中全部零件0.5天,组装需要2天.请问组装一辆汽车,最短需要多长时间.根据前面描述,我们构造这样的AOV网络图,一看便知. 通过网络中,我们很清晰的知道,关键路径是5.5,如果发动机提高

Java学习笔记——浅谈数据结构与Java集合框架(第一篇、List)

横看成岭侧成峰,远近高低各不同.不识庐山真面目,只缘身在此山中. --苏轼 这一块儿学的是云里雾里,咱们先从简单的入手.逐渐的拨开迷雾见太阳.本次先做List集合的三个实现类的学习笔记 List特点:有序,元素可重复.其实它的本质就是一个线性表(下面会说到) 先上图,Java集合有Collection体系和Map体系: 然后简单介绍一下数据结构和算法: 数据结构就是数据和数据之间的关系,好比分子结构,晶体结构.碳原子按照一定的方式组合在一起形成碳分子,碳分子再按照一定方式形成晶体. 算法是对解题