Dijkstra算法图解,C++实现Dijkstra算法

2 篇文章 0 订阅

Dijkstra算法简介

Dijkstra算法计算是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题。迪杰斯特拉算法主要特点是从起始点开始,采用贪心算法的策略,每次遍历到起点距离最近且未访问过的顶点的邻接节点,直到扩展到所有终点为止。

数据结构抽象

现在我们有一个景点地图,假设从1号顶点出发,求到其他各个顶点的最短路径。算法步骤如下:
设置地图的带权邻接矩阵为map[][],如果从顶点 i 到顶点 j 有边,则map[i][j] == <i,j>的权值,否则map[i][j] == (无穷大)。
在这里插入图片描述

邻接矩阵为map[ ][ ]抽象为一个二维数组,表示每两个顶点之间的权值,[ i ][ j ]和[ j ][ i ]表示两个顶点之间不同方向的权值。
在这里插入图片描述

初始化

令集合S={ 1 },S初始化只包含起点u,集合V-S = { 2,3,4,5 },对于集合V-S中的所有顶点x,初始化最短距离数组dist[ i ] = map[ 1 ] [ i ],dist[ u ]=0(起点到起点为0)如图3所示。如果源点1到顶点 i 有边相连,初始化前驱数组p[ i ] = 1,否则p[ i ] = -1,如图4所示。
最短距离数组dist[ i ] ,表示顶点1到其他顶点之间的最短距离,该数组经过每次计算会动态变化。
在这里插入图片描述

前驱数组p[ i ],表示到达该顶点的前一个顶点编号,也是动态变化的。
在这里插入图片描述

开始计算

第一轮计算

1.找最小
在集合V-S = {2,3,4,5} 中,依照贪心策略来寻找 V-S集合中dist[ ]最小的顶点 T,如图3,找到最小值为2,对应的顶点 T = 2。
2. 加入S集合
将顶点T = 2加入集合S中,S={1, 2},同时更新V-S = {3,4,5},如图5所示。
在这里插入图片描述

3. 走捷径
刚刚找到了起点到 T = 2的最短路径,那么对集合 V-S中所有 T 的邻接点,都可以借助 T 走捷径。我们从图或邻接矩阵都可以看出,2号顶点的邻接点是3和4号顶点,如图2所示。

先看3号结点能否借助2号走捷径: dist[2] + map[2][3] = 2+2 = 4,而当前dist[3] = 5>4,因此可以走捷径即2–3,更新dist[3] = 4,记录顶点3的前驱为2,即p[3] = 2。

再看4号结点能否借助2号走捷径:如果dist[2] + map[2][4] = 2+6 = 8,而当前dist[4] = ∞ > 8.因此可以走捷径即2-4,更新dist[4] = 8,记录顶点4的前驱为2,即p[4] = 2。
更新后如图6和图7所示。

在这里插入图片描述

第二轮计算

1.找最小
在集合 V-S = {3,4,5)中,依照贪心策略来寻找dist[]中具有最小值的顶点T,找到最小值为4,对应的结点T = 3,如图6。
2. 加入S集合
将顶点T = 3加入集合S中,S={1,2,3},同时更新V-S = {4,5},如图8所示。
在这里插入图片描述
3. 走捷径
刚刚找到了源点到T = 3 的最短路径,那么对集合 V-S中所有T的邻接点 ,都可以借助T走捷径。我们从图或邻接矩阵可以看出,3号结点的邻接点是4和5号结点

先看4号结点能否借助3号走捷径:dist[3]+map[3][4] = 4+7 = 11,而当前dist[4] = 8 < 11,比当前路径还长,因此不更新。

再看5号结点能否借助3号走捷径:dist[3]+map3][5] = 4+1 = 5,而当前dis[5] = ∞ > 5,因此可以走捷径,即3一5,更新dist[5] = 5,记录顶点5的前驱为3,即p[5] = 3,更新后如图9和图10 所示。
在这里插入图片描述

第三轮计算

1.找最小
在集合 V-S = {4,5}中,依照贪心策略来寻找V- S 集合中dist[ ] 最小的顶点T,如图9所示,找到最小值为5,对应的结点T = 5。
2. 加入S集合
将顶点T = 5加入集合S中,S = {1,2,3,5},同时更新V-S = {4},如图11所示。
在这里插入图片描述
3. 走捷径
刚刚找到了源点到T = 5的最短路径,那么对集合V-S中所有T = 5的邻接点,都可以借助T = 5走捷径。我们从图或邻接矩阵可以看出,5 号结点没有邻接点,因此不更新,如图9 和图10 所示。

第四轮计算

1.找最小
在集合 V-S = {4}中,依照贪心策略来寻找 dist最小的顶点,只有一个顶点,所以很容易找到,找到最小值为8,对应的结点T= 4,如图9所示。
2. 加入S集合
将顶点T=4加入集合S中,S = {1,2,3,5,4},同时更新 V-S = { },如图12所示。
在这里插入图片描述
3. 走捷径
V-S = { }为空,算法停止。

算法总结

map[i][j]:把图抽象为二维数组,下标代表各个顶点,值代表两个顶点ij之间的权值。
S集合:已经遍历且找到最短路径的顶点集合。
V-S集合:还未遍历的顶点集合,最短路径在寻找中。
T:当前正在遍历的顶点。
dist[ i ] : 最短距离数组,表示当前起点到其他顶点之间的最短距离,该数组经过每次计算会动态变化。
p[ i ]:前驱数组,表示当前到达该顶点的前一个顶点编号,也是动态变化的。

算法的关键是理解走捷径:
走捷径的意思是:已找到起点到T的最短路径,起点到T的最短路径 + T到T的邻接顶点P的权值 < 当前记录起点到P的最短路径,则更新P的最短路径和前一跳。

每轮计算都是重复的操作,直到遍历了图中所有顶点,得到dist[ ]和p[ ]。
可求得从源点图中其余各个顶点的最短路径及长度,也可通过前驱数组逆向找到最短路径上经过的顶点。
例如,p[5] = 3,即5的前驱是3,p[3] = 2,即3的前驱是2,p[2] = 1,即2的前驱是1,p[1] = -1,1没有前驱,那么从起点1到5的最短路径为1-2-3-5。

C++实现Dijkstra算法

头文件

#ifndef DIJKSTRA_H
#define DIJKSTRA_H

#include <string>
#include <fstream>
#include <iostream>
#include <vector>
#define MAX_VERNUM 20//最大顶点数
#define MAX_VALUE 99999//最大权值
using namespace std;

//顶点
struct Node
{
    string path;           //路径
    string NodeName;       //节点名字
    vector<string> next_Node;//从起点开始到终点所经过的节点,不包括起点
    int value;             //路径权值
    bool visit;            //是否访问过
    Node()
    {
        visit = false;
        value = 0;
        NodeName = "";
        next_Node.clear();
        path = "";
    }
};

class Dijkstra
{
public:
    Dijkstra();
    ~Dijkstra();

    void Create_graph();                    //创建图
    void Dijkstra_cpt(string from);         //迪斯拉算法求最短路径
    void Display_table(string from);        //输出路由表

    void Modify_edge(string from, string to, int value);                        //修改边
    void Add_node(string nodename);                      //添加顶点
    void Del_node(string r1);                      //删除顶点
private:
    int vernum;                             //图的顶点个数
    int **adjmatrix;                        //邻接矩阵
    Node *node;                             //记录各个顶点最短路径的信息
};

#endif // DIJKSTRA_H

源文件

#include "Dijkstra.h"

int g_nodeNum = 5;//顶点数量
int g_edgeNum = 8;//边的数量
int g_edge[8][3] = {//边的集合
    //起点,终点,权值
    {1, 2, 2},
    {1, 3, 5},
    {2, 3, 2},
    {2, 4, 6},
    {3, 4, 7},
    {3, 5, 1},
    {4, 3, 2},
    {4, 5, 4}
};

Dijkstra::Dijkstra()
{

}

//析构函数
Dijkstra::~Dijkstra()
{
    delete[] node;                      //释放一维数组node内存
    for (int i = 0; i < MAX_VERNUM; i++)//释放二维数组adjmatrix内存
    {
        delete this->adjmatrix[i];
    }
    delete adjmatrix;
}

//创建图
void Dijkstra::Create_graph()
{
    vernum = g_nodeNum;                    //初始化顶点数和边数
    node   = new Node[MAX_VERNUM];              //保留顶点信息,其中共有MAX_VERNUM条边
    adjmatrix = new int*[MAX_VERNUM];         //数组一维长度为MAX_VERNUM
    for (int i = 0; i < MAX_VERNUM; i++)
    {
        adjmatrix[i] = new int[MAX_VERNUM];   //数组二维长度也为MAX_VERNUM
        for (int k = 0; k < MAX_VERNUM; k++)
        {
            adjmatrix[i][k] = MAX_VALUE;      //邻接矩阵初始化为无穷大
        }
    }

    for(int index=0;index<g_edgeNum;index++)
    {
        //对邻接矩阵对应上的点赋值
        adjmatrix[g_edge[index][0] - 1][g_edge[index][1] - 1] = g_edge[index][2];
    }

    for (int i = 0; i < this->vernum; i++)    //初始化node数组的编号
    {
        node[i].NodeName = "r" + to_string(i + 1);
    }
}

//算法主体
void Dijkstra::Dijkstra_cpt(string from)
{
    int f, i, j, k;
    for (f = 0; f < this->vernum; f++)
    {
        if (from.compare(node[f].NodeName) == 0)
            break;
    }
    for (i = 0; i < this->vernum; i++)//初始化node数组
    {
        node[i].path = from + "-->" + node[i].NodeName;
        node[i].next_Node.clear();
        node[i].next_Node.push_back(node[i].NodeName);
        node[i].value = adjmatrix[f][i];
        node[i].visit = false;
    }
    node[f].value = 0;                //设置起点到起点的路径为0
    node[f].visit = true;

    for (i = 1; i < this->vernum; i++)//计算剩余的顶点的最短路径
    {
        int temp = 0;                 //temp用于保存当前node数组中最小的那个下标
        int min = MAX_VALUE;          //min记录的当前的最小值
        for (j = 0; j < this->vernum; j++)
        {
            if (!node[j].visit && node[j].value < min)
            {
                min = node[j].value;
                temp = j;
            }
        }
        node[temp].visit = true;      //把temp对应的顶点加入到已经找到的最短路径的集合中
        for (k = 0; k < this->vernum; k++)
        {
            //起点到T的最短路径 + T到T的邻接顶点P的权值  < 当前记录起点到P的最短路径
            if (!node[k].visit && adjmatrix[temp][k] != MAX_VALUE && (node[temp].value + adjmatrix[temp][k]) < node[k].value)
            {
                node[k].path = node[temp].path + "-->" + node[k].NodeName;
                node[k].next_Node = node[temp].next_Node;
                node[k].next_Node.push_back(node[k].NodeName);
                node[k].value = node[temp].value + adjmatrix[temp][k];
            }
        }
    }
    Display_table(from);
}

//输出路由表
void Dijkstra::Display_table(string from)
{
    int i;
    bool flag = false;
    for (i = 0; i < this->vernum; i++)
    {
        if (from.compare(node[i].NodeName) == 0)
           flag = true;
    }
    if (flag == false)
        printf("can not found node\n");
    else
    {
        printf("start:%s\n",from.c_str());
        for (i = 0; i < this->vernum; i++)
        {
            cout << "dest:" << node[i].NodeName << "  ";
            printf("next:%s,value:%d\n",node[i].next_Node.at(0).c_str(),node[i].value);
        }
    }
    cout << endl;
}

//添加边
void Dijkstra::Modify_edge(string from,string to,int value)
{
    int f, t;
    for (f = 0; f < this->vernum; f++)    //找到起点对应的数组坐标
    {
        if (from.compare(node[f].NodeName) == 0)
            break;
    }
    for (t = 0; t < this->vernum; t++)    //找到终点对应的数组坐标
    {
        if (to.compare(node[t].NodeName) == 0)
            break;
    }
    adjmatrix[f][t] = value;              //对邻接矩阵对应上的点赋值
}

//添加顶点
void Dijkstra::Add_node(string nodename)
{
    node[vernum].NodeName = nodename;
    this->vernum++;
}

//删除顶点
void Dijkstra::Del_node(string r1)
{
    int r2, i, j, count = 0;
    for (r2 = 0; r2 < this->vernum; r2++)
    {
        if (r1.compare(node[r2].NodeName) == 0)
            break;
    }
    for (i = 0; i < this->vernum; i++)  //统计与删除的顶点相关的边数
    {
        if(adjmatrix[r2][i] != MAX_VALUE)
            count++;
    }

    for(i = 0;i < this->vernum; i++)    //调整邻接矩阵
    {
        for(j = 0;j < this->vernum; j++)//将邻接矩阵向内紧缩
        {
            if(i > r2 && j > r2)        //调整右下角部分
                adjmatrix[i-1][j-1] = adjmatrix[i][j];
            else if(i > r2)             //调整右上角部分
                adjmatrix[i-1][j] = adjmatrix[i][j];
            else if(j > r2)             //调整左下角部分
                adjmatrix[i][j-1] = adjmatrix[i][j];
        }
    }
    for(i = 0;i < this->vernum; i++)
    {
        adjmatrix[this->vernum][i] = MAX_VALUE;
        adjmatrix[i][this->vernum] = MAX_VALUE;
    }

    for(i = r2; i < this->vernum-1; i++)//调整顶点数组值
        node[i] = node[i+1];
    this->vernum--;
}

调用

    mDijkstra.Create_graph();
    mDijkstra.Dijkstra_cpt("r1");

打印:

start:r1
dest:r1  next:r1,value:0
dest:r2  next:r2,value:2
dest:r3  next:r2,value:4
dest:r4  next:r2,value:8
dest:r5  next:r2,value:5
flody算法与迪杰斯特拉算法都是常见的最短路径算法,但它们有一些不同之处。 迪杰斯特拉算法是一种单源最短路径算法,用于计算一个节点到其他所有节点的最短路径。它的主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。迪杰斯特拉算法的时间复杂度为O(N^2)。在导航系统和网络路由等应用中,迪杰斯特拉算法被广泛使用。 flody算法(弗洛伊德算法)是一种多源最短路径算法,用于计算任意两个节点之间的最短路径。它通过对图中的所有节点进行迭代,不断更新节点之间的最短路径。flody算法的时间复杂度为O(N^3),相对于迪杰斯特拉算法来说,它的计算量较大。在需要确定任意两点之间的最短路径时,可以使用flody算法。 总结来说,迪杰斯特拉算法适用于单源最短路径问题,而flody算法适用于多源最短路径问题。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [数据结构与算法 —— 最短路径Dijkstra算法(迪杰斯特拉)详细图解以及python实现](https://blog.csdn.net/u013121610/article/details/130321289)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [最短路径的两种算法(迪杰斯特拉算法和弗洛伊德算法)](https://blog.csdn.net/qq_32172681/article/details/102532911)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值