C++实现图 - 04 最短路径

简介: 今天我们来看看图论中另一个非常重要的问题 —— 最短路径,正如其名就是要再图中找到起点到终点的最短路径,这就需要不断地去比较每条边的权值。这一讲我们将会具体介绍迪杰斯特拉算法和弗洛伊德算法的实现。
写在前面:
今天我们来看看图论中另一个非常重要的问题 —— 最短路径,正如其名就是要再图中找到起点到终点的最短路径,这就需要不断地去比较每条边的权值。
这一讲我们将会具体介绍迪杰斯特拉算法和弗洛伊德算法的实现。

迪杰斯特拉算法

迪杰斯特拉算法是一个单源点的一个最短路径算法,也就是说,我们这个算法会求得从一个顶点到其所有顶点的最短路径。

这个算法需要用到邻接矩阵来存储所有边值,并且需要一个辅助数组来更新最短路径,需要一个路径数组存储最短路径的结点,还需要一个状态数组来判断当前结点是否已经加入最短路径。

这样说可能会有点晕,我们还是先来看图,假设有这样一张图:

1.jpg

现在,我们要找到结点 0 到结点 8 的最短路径,步骤如下:

辅助数组 D:存储结点 0 到当前结点的最短路径权值。
路径数组 P:存储当前最短路径到该点的父结点。
状态数组 Final :记录当前结点是否已经加入到最短路径当中(0 代表还没有加入,1 代表已经加入)。

第一步:初始化我们上述提到的三个数组,将结点 0 加入数组中,并更新辅助数组即结点 0 连出去的边的权值。

2.jpg

第二步:找到当前辅助数组中的边权最小值,即结点 0 到结点 1 ,故加入结点 1 ,并且更新结点 1 连出去的边值。
可以发现之前更新过结点 2 的辅助数组,现在又需要更新,因为结点 0 到结点 2 的最短路径不再是 0 -> 2 ,而是 0 -> 1 -> 2 ,即最短路径值从 5 变为 1+3 = 4 ,并且结点 2 的 P 不再是 0 ,它在最短路中要先经过结点 1 ,故 P 改成 1 。

3.jpg

第三步:更上面一样,找到结点 2 加入最短路,并且更新。

4.jpg

第四步:找到结点 4 加入最短路,并且更新。

5.jpg

第五步:找到结点 3 加入最短路,并且更新。

6.jpg

第六步:找到结点 5 加入最短路,并且更新。

7.jpg

第七步:找到结点 6 加入最短路,并且更新。

8.jpg

第八步:找到结点 7 加入最短路,并且更新。

9.jpg

第九步:到达终点,生成最短路径。

10.jpg

全部代码

#include <bits/stdc++.h>
using namespace std;
#define MaxVertices 100    //假设包含的最大结点数
#define MaxWeight 99999    //假设两点不邻接的正无穷值

/*
9
16
0 1 1
0 2 5
1 2 3
1 3 7
1 4 5
2 4 1
2 5 7
3 4 2
4 5 3
3 6 3
4 6 6
4 7 9
5 7 5
6 7 2
6 8 7
7 8 4
*/

//定义邻接矩阵
struct AdjMarix {
    int Edge[MaxVertices][MaxVertices] = { 0 };
    int numV;    //当前顶点的个数
    int numE;    //当前边的个数
};

//构建图
void CreatGraph(AdjMarix *G) {
    int vi, vj, w;
    cout << "请输入顶点数量:" << endl;
    cin >> G->numV;
    for (int i = 0; i < G->numV; i++) {
        for (int j = 0; j < G->numV; j++) {
            G->Edge[i][j] = MaxWeight;
        }
    }
    cout << "请输入边的数量:" << endl;
    cin >> G->numE;
    cout << "请输入边的信息:" << endl;
    for (int i = 0; i < G->numE; i++) {
        cin >> vi >> vj >> w;
        G->Edge[vi][vj] = w;
        G->Edge[vj][vi] = w;
    }
}

//迪杰斯特拉算法
void Dijkstra(AdjMarix &M, int v0, int *P, int *D) {
    //初始化
    int Final[MaxVertices] = { 0 };
    for (int i = 0; i < M.numV; i++) {
        P[i] = -1;
        D[i] = M.Edge[v0][i];
    }
    //加入结点V0
    D[v0] = 0;
    Final[v0] = 1;
    
    int k = v0;
    for (int i = 0; i < M.numV; i++) {
        int MIN = MaxWeight;    //找到目前数组中权值最小的一条边
        for (int j = 0; j < M.numV; j++) {
            if (Final[j] == 0 && D[j] < MIN) {
                MIN = D[j];
                k = j;
            }
        }
        Final[k] = 1;    //标记该结点已经加入最短路径中
        for (int w = 0; w < M.numV; w++) {
            if (Final[w] == 0 && (MIN + M.Edge[k][w] < D[w])) {
                D[w] = MIN + M.Edge[k][w];    //更新最小值
                P[w] = k;    //记录路径(说明最短路径中到w的要经过k)
            }
        }
    }
}

int main() {
    AdjMarix AM;
    CreatGraph(&AM);
    int P[MaxVertices], D[MaxVertices];
    Dijkstra(AM, 0, P, D);
    cout << "V0到各个结点的最短路径:" << endl;
    for (int i = 0; i < AM.numV; i++) {
        cout << "V" << i << ":";
        int j = i;
        while (j != -1) {
            cout << "V" << j << "->";
            j = P[j];
        }
        cout << "V0    【最短路径为" << D[i] << "】" << endl;
    }
}

image.png

弗洛伊德算法

这个算法相对于上一个就十分暴力了,直接三层循环,遍历所有情况,每次都去试图找到一个中间结点,判断是否能使结点 i 到结点 j 经过结点 k 能缩短路径长度。这里我就直接展示代码啦,因为实质算法三层循环就搞定了。

#include <bits/stdc++.h>
using namespace std;
#define MaxVertices 100    //假设包含的最大结点数
#define MaxWeight 99999    //假设两点不邻接的正无穷值

/*
9
16
0 1 1
0 2 5
1 2 3
1 3 7
1 4 5
2 4 1
2 5 7
3 4 2
4 5 3
3 6 3
4 6 6
4 7 9
5 7 5
6 7 2
6 8 7
7 8 4
*/

//定义邻接矩阵
struct AdjMarix {
    int Edge[MaxVertices][MaxVertices] = { 0 };
    int numV;    //当前顶点的个数
    int numE;    //当前边的个数
};

//创建图
void CreatGraph(AdjMarix *G) {
    int vi, vj, w;
    cout << "请输入顶点数量:" << endl;
    cin >> G->numV;
    for (int i = 0; i < G->numV; i++) {
        for (int j = 0; j < G->numV; j++) {
            if (i == j) {
                G->Edge[i][j] == 0;
            } else {
                G->Edge[i][j] = MaxWeight;
            }
        }
    }
    cout << "请输入边的数量:" << endl;
    cin >> G->numE;
    cout << "请输入边的信息:" << endl;
    for (int i = 0; i < G->numE; i++) {
        cin >> vi >> vj >> w;
        G->Edge[vi][vj] = w;
        G->Edge[vj][vi] = w;
    }
}

//弗洛伊德算法
void Floyd(AdjMarix &M, int P[][MaxVertices], int D[][MaxVertices]) {
    for (int i = 0; i < M.numV; i++) {
        for (int j = 0; j < M.numV; j++) {
            P[i][j] = j;
            D[i][j] = M.Edge[i][j];
        }
    }
    //遍历所有结点所有边并更新,找到最短路径
    for (int i = 0; i < M.numV; i++) {
        for (int j = 0; j < M.numV; j++) {
            for (int k = 0; k < M.numV; k++) {
                int temp = (D[j][i] == MaxWeight || D[i][k] == MaxWeight) ? MaxWeight : D[j][i] + D[i][k];
                if (temp < D[j][k]) {
                    D[j][k] = temp;
                    P[j][k] = P[j][i];
                }
            }
        }
    }
    cout << "最小路径:" << endl;
    for (int i = 0; i < M.numV; i++) {
        for (int j = 0; j < M.numV; j++) {
            cout << P[i][j] << " ";
        }
        cout << endl;
    }
    cout << endl;
    cout << "最小路径值:" << endl;
    for (int i = 0; i < M.numV; i++) {
        for (int j = 0; j < M.numV; j++) {
            printf("%2d ", D[i][j]);
        }
        cout << endl;
    }
}

int main() {
    AdjMarix AM;
    CreatGraph(&AM);
    int P[MaxVertices][MaxVertices], D[MaxVertices][MaxVertices];
    Floyd(AM, P, D);
}

image.png

如果大家有什么问题的话,欢迎在下方评论区进行讨论哦~

目录
相关文章
|
算法 C# C++
C++算法:多源最短路径的原理及实现
C++算法:多源最短路径的原理及实现
|
6月前
|
存储 算法 C++
c++算法学习笔记 (8) 树与图部分
c++算法学习笔记 (8) 树与图部分
|
6月前
|
算法 C++ 开发者
【C/C++ 数据结构 】图顶点个数和边的关系
【C/C++ 数据结构 】图顶点个数和边的关系
207 0
|
算法 测试技术 C#
C++算法:存在负权边的单源最短路径的原理和实现
C++算法:存在负权边的单源最短路径的原理和实现
|
数据可视化 C++
【影像配准】配准之棋盘网格图(镶嵌图像)(附有 C++ 代码)
【影像配准】配准之棋盘网格图(镶嵌图像)(附有 C++ 代码)
|
存储 C++ 容器
使用C++编写一个图的深度和广度优先遍历的代码
使用C++编写一个图的深度和广度优先遍历的代码
114 0
|
编译器 C++
C++ 类和对象(静态的static、友元、内部类、匿名对象、explicit)知识点+完整思维导图+实操图+深入细节通俗易懂建议收藏(二)
C++ 类和对象(静态的static、友元、内部类、匿名对象、explicit)知识点+完整思维导图+实操图+深入细节通俗易懂建议收藏(二)
|
编译器 C++
C++ 类和对象(静态的static、友元、内部类、匿名对象、explicit)知识点+完整思维导图+实操图+深入细节通俗易懂建议收藏(一)
C++ 类和对象(静态的static、友元、内部类、匿名对象、explicit)知识点+完整思维导图+实操图+深入细节通俗易懂建议收藏(一)
|
存储 C++
C++异常处理机制由浅入深, 以及函数调用汇编过程底层刨析. C++11智能指针底层模拟实现
C++异常处理机制由浅入深, 以及函数调用汇编过程底层刨析. C++11智能指针底层模拟实现
C++异常处理机制由浅入深, 以及函数调用汇编过程底层刨析. C++11智能指针底层模拟实现
|
存储 Linux C语言
生产者消费者模式保姆级教程 (阻塞队列解除耦合性) 一文帮你从C语言版本到C++ 版本, 从理论到实现 (一文足以)
生产者消费者模式保姆级教程 (阻塞队列解除耦合性) 一文帮你从C语言版本到C++ 版本, 从理论到实现 (一文足以)
生产者消费者模式保姆级教程 (阻塞队列解除耦合性) 一文帮你从C语言版本到C++ 版本, 从理论到实现 (一文足以)