首页 > 其他分享 >38. 最短路径

38. 最短路径

时间:2023-06-29 22:55:40浏览次数:46  
标签:distance 38 int graph 路径 vertex 最短 顶点

一、什么是最短路径

  在网络中,求两个不同顶点之间的所有路径中,边的权值之和最小的那一条路径,这条路径就是两点之间的 最短路径(Shortest Path),其中第一个顶点为 源点(Source),最后一个顶点为 终点(Destination)。

  • 单源最短路径问题:从某固定源点触发,求其到所有其它顶点的最短路径;
  • 多源最短路径问题:求任意两顶点间的最短路径;

二、无权图的单源最短路径

  对于无权图的单源最短路径,我们可以使用广度优先搜索的方法遍历。该方法按层处理顶点,距开始顶点最近的那些顶点首先被赋值,而最短的那些顶点最后被赋值。

  首先,我们把从 vertex 开始到顶点的距离放到 distance[] 数组中。开始的时候,除 vertex 外所有的顶点都是不可到达的,而 vertex 的路径长为 0。path[] 用来表示最短路径从哪个顶点过来。visited[] 用来表示节点是否被访问过。起初,所有的顶点都是还没访问的,当一个顶点被表示已知时,我们就有了不会再找到更短的路径的保证,因此对该顶点的处理实质上已经完成了。

img

img

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int i = 0,j = 0,k = 0;
    char data[] = {'A','B','C','D','E','F','G'};

    int weight[][sizeof(data)] = {
        {0,1,0,1,0,0,0},
        {0,0,0,1,1,0,0},
        {1,0,0,0,0,1,0},
        {0,0,1,0,1,1,1},
        {0,0,0,0,0,0,1},
        {0,0,0,0,0,0,0},
        {0,0,0,0,0,1,0}
    };

    Graph graph = StructureGraph(sizeof(data),data,weight);

    Unweighted(graph,0);

    return 0;
}
/**
 * @brief 构建一个图
 * 
 * @param N 顶点个数
 * @param data 顶点数据数组
 * @param weight 边的权值数组
 * @return Graph 返回构建的图
 */
Graph StructureGraph(int N,int data[N],int weight[N][N])
{
    int i = 0, j = 0;

    Graph graph = CreateGraph(N);

    for(i = 0; i < graph->vertexNum; i++)
    {
        graph->vertex[i] = data[i];
    }

    for(i = 0; i < graph->vertexNum; i++)
    {
        for(j = 0; j < graph->vertexNum; j++)
        {
      
            if(weight[i][j] != 0)
            {
                Edge edge = malloc(sizeof(struct ENode));
                edge->v1 = i;
                edge->v2 = j;
                edge->weight = weight[i][j];
                InsertEdge(graph,edge);
            }
        }
    }

    return graph;
}
/**
 * @brief 无权图的单源最短路
 * 
 * @param graph 待操作的图
 * @param vertex 待访问的第一个节点
 */
void Unweighted(Graph graph,int vertex)
{
    int distance[graph->vertexNum];                     // 表示从vertex到第i个节点的最短路径
    int path[graph->vertexNum];                         // 最短路径从哪个顶点过来

    int i = 0,j = 0;
    int v = 0,w = 0;
    Queue Q = CreateQueue();

    for(i = 0; i < graph->vertexNum; i++)
    {
        distance[i] = -1;
        path[i] = -1;
    }

    distance[vertex] = 0;

    Enqueue(Q,vertex);
    while(!QueueIsEmpty(Q))
    {
        v = Dequeue(Q);

        for(w = GetFirstNeighbor(graph,v); w >= 0; w = GetNextNeighbor(graph,v,w))
        {
            if(distance[w] == -1)                       // w为s尚未访问的邻接顶点
            {
                distance[w] = distance[v]+1;            // 最短长度加1
                path[w] = v;                            // 最短路径应从v到w
                Enqueue(Q,w);                           // 顶点w入队
            }
        }
    }

    // 程序执行到此,从vertex到图中其它节点的最短路径保存再path数组,最短路径长度保存在distance数组中
    PrintPath(graph,vertex,distance,path);
}
/**
 * @brief 得到第一个邻接节点的下标
 * 
 * @param graph 待操作的图
 * @param index 对应节点的下标 
 * @return int 如果存在返回对应的下标,否则返回-1
 */
int GetFirstNeighbor(Graph graph,int index)
{
    int j = 0;
    for(j = 0; j < graph->vertexNum; j++)
    {
        if(graph->edge[index][j] > 0)
        {
            return j;
        }
    }
    return -1;
}
/**
 * @brief 根据前一个邻接节点的下标获取下一个邻接节点
 * 
 * @param graph 待操作的图
 * @param v1 对应节点的下标 
 * @param v2 上一个邻接节点的下标
 * @return int 
 */
int GetNextNeighbor(Graph graph,int v1,int v2)
{
    int j = 0;
    for(j = v2+1; j < graph->vertexNum; j++)
    {
        if(graph->edge[v1][j] > 0)
        {
            return j;
        }
    }
    return -1;
}
/**
 * @brief 输出vertex节点到图中其它节点的最短路径
 * 
 * @param graph 待操作的图
 * @param vertex 开始的节点的下标
 * @param distance vertex到图中其它节点距离的数组
 * @param path vertex到图中其它节点路径的数组
 */
void PrintPath(Graph graph,int vertex,int distance[],int path[])
{
    int i = 0,j = 0;
    for(i = 0; i < graph->vertexNum; i++)
    {
        j = i;
        if(i != vertex)
        {
            Stack S = CreateStack();

            printf("%c → %c : ",graph->vertex[vertex],graph->vertex[i]);
            printf("distance = %d,",distance[i]);
            printf("path = { ");

            while(path[j] != -1)
            {
                Push(S,path[j]);
                j = path[j];
            }

            while(!StackIsEmpty(S))
            {
                j = Pop(S);
                printf("%c → ",graph->vertex[j]);
            }
            printf("%c }\n",graph->vertex[i]);                  // 终点
        }
    }
}

时间复杂度 \(T = O(|V| + |E|)\)

三、有权图的单源最短路径

  有权图的单元最短路径可以用 Dijkstra(迪杰斯特拉)算法解决,他啊用于计算一个节点到其它节点的最短路径。它的主要特点是以起始点为中心向外层扩展(广度优先搜索思想),直到扩展到终点为止。

  设置出发节点为 vertex,顶点集合 \(V\{v_{1},v_{2},...\}\),vertex 到 V 中各顶点的距离构成距离集合 distance,\(distance\{d_{1},d_{2},...\}\),distance 集合记录着 vertex 到图中各顶点的距离(到自身的距离可以看做 0,vertex 到 \(v_{i}\) 的距离对应 \(d_{i}\)。

  Dijkstra 算法按阶段进行,正像无权最短路径算法。在每个阶段,Dijkstra 算法选择一个顶点 v,它在所有位置未知顶点中具有最小的 \(d_{v}\),同时算法声明从 s 到 v 的最短路径是已知的。阶段的其余部分有 \(d_{w}\) 的值的更新工作组成。

  1. 从 distance 中选择值最小的 \(d_{i}\) 并移出 distance 集合,同时移出 V 集合中对应的顶点 \(v_{i}\),此时的 vertex 到 \(v_{i}\) 即为最短路径;
  2. 更新 distance 集合,更新规则为:比较 vertex 到 V 集合中顶点的距离值,与 vertex 通过 \(v_{i}\) 到 V 集合中顶点的距离值,保留值较小的一个(同时也应该更新顶点的前驱节点为 \(v_{i}\),表示是通过 \(v_{i}\) 到达的);
  3. 重复执行两步骤,直到最短路径顶点为目标顶点即可结束;

img

img

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int i = 0,j = 0,k = 0;
    char data[] = {'A','B','C','D','E','F','G'};

    int weight[][sizeof(data)] = {
        {0,2,0,1,0,0,0},
        {0,0,0,3,10,0,0},
        {4,0,0,0,0,5,0},
        {0,0,2,0,2,8,4},
        {0,0,0,0,0,0,6},
        {0,0,0,0,0,0,0},
        {0,0,0,0,0,1,0}
    };

    Graph graph = StructureGraph(sizeof(data),data,weight);

    Dijkstra(graph,0);

    return 0;
}
/**
 * @brief Dijkstra算法
 * 
 * @param graph 
 * @param vertex 
 */
void Dijkstra(Graph graph,int vertex)
{
    int distance[graph->vertexNum];                                         // 表示从vertex到第i个节点的最短路径
    int path[graph->vertexNum];                                             // 最短路径从哪个顶点过来
    int collected[graph->vertexNum];                                        // 表示节点是否被收集过

    int i = 0,j = 0;
    int min = 9999;
    int v = 0,w = 0,s = 0;

    for(i = 0; i < graph->vertexNum; i++)
    {
        distance[i] = 65535;
        path[i] = -1;
        collected[i] = 0;
    }

    distance[vertex] = 0;
    collected[vertex] = 1;

    for(i = 0; i < graph->vertexNum; i++)
    {
        min = 9999;
        for (s = 0; s < graph->vertexNum; s++)                              //选择到各顶点权值最小的顶点,即为本次能确定最短路径的顶点
        {
            if (!collected[s])                                              // 如果该顶点还未被收集
            {
                if (distance[s] < min) 
                {
                    v = s;
                    min = distance[s];
                }
            }
        }

        collected[v] = 1;                                                   // 标记v为已经被收集
        for(w = GetFirstNeighbor(graph,v); w >= 0; w = GetNextNeighbor(graph,v,w))
        {
            if(!collected[w])                                               // w为v尚未访问的邻接顶点
            {
                if(distance[v] + graph->edge[v][w] < distance[w])
                {
                    distance[w] = distance[v] + graph->edge[v][w];          // 最短长度加1
                    path[w] = v;                                            // 最短路径应从v到w
                }
            }
        }
    }

    // 程序执行到此,从vertex到图中其它节点的最短路径保存再path数组,最短路径长度保存在distance数组中
    PrintPath(graph,vertex,distance,path);
}

如果直接扫描搜友未收录顶点,它的时间复杂度为 \(T = O(|V|^{2} + |E|)\)

如果将 dist 在最小堆中,它的时间复杂度为 \(T = O(|E| + log|v|)\)

四、多源最短路径

  我们可以直接将单源最短路径算法调用 |V| 遍,此时它的时间复杂度为 \(T = O(|V|^{3} + |E|*|V|)\),对于稀疏图的效果比较好。对于稠密图来说,推荐使用 Floyd 算法,它的时间复杂度为 \(T = O(|V|^3)\)。

  Floyd 算法中每一个顶点都是出发访问节点,所以需要将每一个节点看做被访问顶点,求出从每一个顶点到其它顶点的最短路径。Floyd 算法的步骤如下:

  1. 设置顶点 \(v_{i}\) 到顶点 \(v_{k}\) 的最短路径已知为 lik,顶点 \(v_{k}\) 到 \(v_{j}\) 的最短路径已知 lkj,顶点 \(v_{i}\) 到 \(v_{j}\) 的路径为 lij,则 \(v_{i}\) 到 \(v_{j}\) 的最短路径为:min((lik,+lij),lij),\(v_{k}\) 的取值为图中所有顶点,则可获得 \(v_{i}\) 到 \(v_{j}\) 的最短路径;
  2. 至于 \(v_{i}\) 和 \(v_{k}\) 的最短路径 lik 或者 \(v_{k}\) 到 \(v_{j}\) 的最短路径为 lkj,是以同样的方式获得;

  初始化时设置一个 n 阶方阵,另其对角元素为 0,若存在边 \(<v_{i},v_{j}>\) ,则对应元素为权值,否则为 ∞。逐步试着在原直接路径中增加中间顶点,若加入中间顶点后路径变短,则修改之。否则,维持原值。所有顶点试探完毕,算法结束。

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int i = 0,j = 0,k = 0;
    char data[] = {'A','B','C','D','E','F','G'};

    int weight[][sizeof(data)] = {
        {0,5,7,0,0,0,2},
        {5,0,0,9,0,0,3},
        {7,0,0,0,8,0,0},
        {0,9,0,0,0,4,0},
        {0,0,8,0,0,5,4},
        {0,0,0,4,5,0,6},
        {2,3,0,0,4,6,0}
    };

    Graph graph = StructureGraph(sizeof(data),data,weight);

    Floyd(graph);

    return 0;
}
/**
 * @brief Floyd算法
 * 
 * @param graph 待操作的图
 */
void Floyd(Graph graph)
{
    int i = 0,j = 0,k = 0;

    int distance[graph->vertexNum][graph->vertexNum];
    int path[graph->vertexNum][graph->vertexNum];

    for(i = 0; i < graph->vertexNum; i++)
    {
        for(j = 0; j < graph->vertexNum; j++)
        {
            distance[i][j] = graph->edge[i][j];
            if((graph->edge[i][j] == 0) && (i != j))
            {
                distance[i][j] = 65535;
            }
            path[i][j] = -1;
        }
    }

    for(k = 0; k < graph->vertexNum; k++)                               // 中间顶点
    {
        for(i = 0; i < graph->vertexNum; i++)                           // 出发顶点
        {
            for(j = 0; j < graph->vertexNum; j++)                       // 终点顶点
            {
                // distance[i][k] + distance[k][j] 为从 i 顶点出发,经过 k 顶点,到达 j 顶点的距离
                // distance[i][j] 为 i 顶点和 j 顶点直连的距离
                if(distance[i][k] + distance[k][j] < distance[i][j])
                {
                    distance[i][j] = distance[i][k] + distance[k][j];   // 更新距离
                    path[i][j] = k;                                     // 更新前驱顶点
                }
            }
        }
    }

    for(i = 0; i < graph->vertexNum; i++)
    {
        PrintPath(graph,i,distance[i],path[i]);
    }
}

标签:distance,38,int,graph,路径,vertex,最短,顶点
From: https://www.cnblogs.com/kurome/p/17515407.html

相关文章

  • 【雕爷学编程】Arduino动手做(138)---64位WS2812点阵屏模块
    37款传感器与执行器的提法,在网络上广泛流传,其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块,依照实践出真知(一定要动手做)的理念,以学习和交流为目的,这里准备逐一动手尝试系列实验,不管成功(程序走通)与否,都会记录下来—小小的进步或是搞......
  • 【改进蚁群算法】 蚁群算法 Dijkstra算法 遗传算法 人工势场法实现二维 三维空间路径
    【改进蚁群算法】蚁群算法Dijkstra算法遗传算法人工势场法实现二维三维空间路径规划本程序为改进蚁群算法+Dijkstra算法+MAKLINK图理论实现的二维空间路径规划 算法实现:原创文章,转载请说明出处,资料来源:http://imgcs.cn/5c/636749258569.html1)基于MAKLINK图理论生成地图,并对......
  • 代码随想录算法训练营第二十天| 669. 修剪二叉搜索树 108.将有序数组转换为二叉搜索
    669.修剪二叉搜索树思路递归法: 需要思考清楚,如果当前节点<low,那么就返回递归它的右节点,而不是自己取判断,找出来一个合适的节点,这样的话会越想越乱代码:1TreeNode*trimBST_cursor(TreeNode*root,intlow,inthigh){2if(!root)returnnullptr;34if......
  • C# 根据设备实例路径,获取父节点(父系)的设备实例路径
    我们知道有时候系统的某些设备异常,可以通过(禁用启用)重启该设备。但是某些设备操作当前设备是没起作用的,例如扬声器设备,禁用后扬声器仍然可以播放声音,但是如果禁用了该设备的父节点则不再可以播放声音。可以从设备管理器中查看 这里就是父节点如果通过C#怎么获取该设备的父......
  • ligntOj 1038(期望)
    题意:给出一个n,一步操作是让n除n的一个随机因子得到新的n,问可以得到新的n是1的步数期望。题解:因为n/1=n这种选择会造成循环,所以需要用到递推,令n变成1的步数期望是f[n],比如n是2,f[2]=1/2(f[2]+1)+1/2(f[1]+1),加1是因为2变成2也需要一步,那么移项后,f[2]=2+f[1]=2+0=......
  • Tomcat通过setenv.bat指定jdk和jre(相对路径)
    Tomcat通过setenv.bat指定jdk和jre(相对路径)1.在Tomcat的bin目录下,创建一个名为setenv.bat的文件。2.编辑setenv.bat,set"JAVA_HOME=E:\ProgramFiles\jdk1.8"set"CLASSPATH=%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar"set"GRADLE_HOME=%cd%\gradle&......
  • 解决TrueNAS中Smb共享文件路径不区分大小写的问题
    问题在Truenas中,默认的smb文件分享中,文件夹是不区分大小写的.这在一些情况下会导致无法重命名等问题,严重时可能会造成拷贝文件时的全文件夹文件丢失.这是linux下的情况,在已存在others文件夹的情况下,若再新建Others文件夹,会提示目录已存在,但实际上两个目录大小写......
  • vue3+vite+js配置路径别名
    1、让vscode认识@符号项目下新建jsconfig.json,配置baseUrl,paths参数{"compilerOptions":{"target":"esnext","useDefineForClassFields":true,"module":"esnext","moduleResolution&q......
  • nodeJS常用路径API示例简记
    常用API汇总:process.cwd():返回当前执行node命令时的所在目录path.dirname():返回当前执行文件的所在目录__dirname:返回当前执行文件的所在目录(只能在CommonJS规范下使用)__filename:返回当前执行文件的绝对路径(只能在CommonJS规范下使用......
  • 再思linux内核在中断路径内不能睡眠/调度的原因(2010)
    Linux内核中断路径中不能睡眠,为什么? 这里就行了很深入的讨论,值得一看:http://bbs.chinaunix.net/viewthread.php?tid=1618430 但是,他们的讨论最后没有得出一个明确的结论。其中,cskyrain在8楼的思考触及到了一个要点,但是没有深入展开: 1楼发表于2009-11-2420:36|只看该作者......