数据结构与算法——最短路径Dijkstra算法的C++实现

之前的讨论了无权图的最短路径算法。数据结构与算法——无权最短路径算法的C++实现

如果是加权图,那么问题就变得困难了,不过仍然可以采用无权情况的想法。

我们仍然保留之前的信息。因此,每个顶点会被标记为known或unknown,每个顶点保留一个尝试性的距离dv(这个距离是只使用一些known顶点作为中间顶点从s到v的最短路径的长),每个顶点还保留字段pv,该字段是记录引起dv变化的最后的顶点。

图顶点信息的数据结构:

//保存每个顶点信息的数据结构
struct GraphNode{
    bool known;//当前顶点距离起点的距离是否确定
    int dist;//当前顶点到起点的最短距离
    int path;//当前顶点距离起点的最短路径的前一个顶点
};

Dijkstra算法简介:

解决单源最短路径问题的一般方法叫做Dijkstra算法。这个算法是贪心算法最好的例子。
贪心算法一般分阶段求解问题,在每个阶段它都把出现的东西当作是最好的去处理。


与无权最短路径算法一样,Dijkstra算法按阶段进行。在每个阶段,Dijkstra算法选择一个顶点v,它在所有的unknown顶点中具有最小的dv,同时算法声明从s到v的最短路径是known的。其它阶段由顶点dv的更新工作成。


主要思想就是根据已经确定了的点的距离,来确定该点相邻顶点的距离,不断的向外散射,直到所以的点的到起点的最短距离确定为止。


下面是Dijkstra算法具体步骤图示:

《数据结构与算法——最短路径Dijkstra算法的C++实现》



假设源点s=v1,下面是求s到其它点的最短距离。


第一步:初始化顶点信息

《数据结构与算法——最短路径Dijkstra算法的C++实现》

第二步:

顶点v1就是顶点s,那么距离顶点v1路径长为0,将顶点v1字段的known设为true。
与s相邻的点为v2,v4。v1到v2的距离为4,v1到v4的距离为1。并更新v2和v4的距离。
p2 = v1; p4 = v1;

《数据结构与算法——最短路径Dijkstra算法的C++实现》

第三步:

寻找unknown的顶点中距离最短的顶点,并将该顶点标记为known。所以将v4标记为known。
然后,再根据顶点v4的dv值更新与v4相邻的所有点(v3,v6,v7,v5)的距离。
d4+d43 = 1+2 < d3,所以d3 = 3;p3 = v4;
d4+d46 = 1+8 < d6,所以d6 = 9;p6 = v4;
d4+d47 = 1+4 < d7,所以d7 = 5;p7 = v4;
d4+d45 = 1+2 < d5,所以d5 = 3;p5 = v4;
《数据结构与算法——最短路径Dijkstra算法的C++实现》



第四步:

执行第三步,寻找
unknown的顶点中距离最短的顶点,并将该顶点标记为known。
所以将v2标记为known。
然后,再根据顶点v2的dv值更新与v2相邻的所有点(v4,v5)的距离。因为v4已经确定了,就不用更新了。因为d2+d25 = 2 + 10 > d5,d5的值为3。所以不需要更新d5。

《数据结构与算法——最短路径Dijkstra算法的C++实现》



第五步:

执行第三步,寻找
unknown的顶点中距离最短的顶点,并将该顶点标记为known。
所以将v5标记为known。
然后,再根据顶点v5的dv值更新与v5相邻的所有点(v7)的距离。因为d5+d57 = 3+6>d7,所以d7不用更新。

第六步:

执行第三步,寻找
unknown的顶点中距离最短的顶点,并将该顶点标记为known。
所以将v3标记为known。
然后,再根据顶点v3的dv值更新与v3相邻的所有点(v1,v6)的距离。因为v1是known的,所以d1不用更新。d3+d36 = 3+5 < d6,所以d6 = d3+d36 = 8; p6 = v3;

《数据结构与算法——最短路径Dijkstra算法的C++实现》

第七步:

执行第三步,寻找
unknown的顶点中距离最短的顶点,并将该顶点标记为known。
所以将v7标记为known。
然后,再根据顶点v7的dv值更新与v7相邻的所有点(v6)的距离。因为d7+d76 = 5+1<d6,所以d6=d7+d76 = 6; p6 = v7;

《数据结构与算法——最短路径Dijkstra算法的C++实现》



第八步:

执行第三步,寻找
unknown的顶点中距离最短的顶点,并将该顶点标记为known。
所以将v6标记为known。
然后,再根据顶点v6的dv值更新与v6相邻的所有点的距离。结果没有v6指向的其它顶点。
《数据结构与算法——最短路径Dijkstra算法的C++实现》



第九步:

执行第三步,寻找
unknown的顶点中距离最短的顶点,并将该顶点标记为known。结果没有找到满足条件的顶点,表明此时已经确定了所有顶点的距离,则退出算法。


Dijkstra算法的基本步骤:

1、初始化顶点信息;v.known = flase;
v.dist = INFINITY;
v.path = 0;
2、对起点s的dist字段设为0;s.dist = 0;
3、从所有顶点中找到dist最小的并且known为false的顶点v。然后将该顶点v的known置为true;
然后更新与顶点v相邻的所有其它known为false的顶点w的dist和path的值。
如果v.dist+distance(v,w) < w.dist;则更新w.dist = v.dist + distance(v, w);w.path=v;
4、循环执行第3步,直到从所有顶点中找不到known为false的顶点v为止,找不到合适的顶点的时候则退出算法。

Dijkstra算法的伪代码:

void Graph::dijkstra(Vertex s)
{
          //初始化顶点信息
          for each Vertex v
          {
                    v.known = false;
                    v.dist = INFINITY;
                    v.path = 0;
          }
         
          //起点s的dist设为0
          s.dist = 0;
         
          //循环执行第3步
          for(; ;)
          {
                    //从所有顶点中找到dist最小的并且known为false的顶点v
                    Vertex v = unknown smallest distance vertex;
                   
                    //如果没有找到满足条件的v,则退出算法(此时所有顶点已经全部确定了)
                    if(v == NOT_A_VERTEX)
                              break;
                             
                    //将该顶点v的known置为true
                    v.known = true;
                   
                    //更新与顶点v相邻的所有其它known为false的顶点w的dist和path的值
                    for each Vertex w adjacent to v
                    {
                              if(!w.known)
                              {
                                        //更新w.dist
                                        if(v.dist+distance(v,w) < w.dist)
                                        {
                                                  w.dist = v.dist + distance(v,w);
                                                  w.path = v;
                                        }                                            
                              }                        
                    }         
          }
}

Dijkstra算法的代码:

/*************************************************
*  函数名称:dijkstra(int src)
*  功能描述:求无权图的任意点到其它顶点的距离
*  参数列表:src是起点
*  返回结果:void 
*************************************************/
void Graph::dijkstra(int src)
{
    //初始化顶点信息
    for(int i = 0; i < vertex_num; ++i){
        nodeArr[i].known = false;
        nodeArr[i].dist = INFINITY;
        nodeArr[i].path = 0;
    }
    //重要的一步,开启算法的关键一步
    nodeArr[src].dist = 0;

    for(; ;){
        //找到unknown的dist最小的顶点 
        int v = 0;
        int max = INFINITY;
        for(int i = 0; i < vertex_num; ++i){
            if(!nodeArr[i].known && (max > nodeArr[i].dist)){
                max = nodeArr[i].dist;
                v = i;
            }
        }

        //没有找到满足条件的顶点,退出算法
        if(max == INFINITY)
            break;

        nodeArr[v].known = true;
        //更新与v相邻所有顶点w的dist,path
        for(list<Node>::iterator it = graph_list[v].begin(); it != graph_list[v].end(); ++it){
            if(!nodeArr[(*it).vertex].known){
                if(nodeArr[v].dist + (*it).weight < nodeArr[(*it).vertex].dist){
                    nodeArr[(*it).vertex].dist = nodeArr[v].dist + (*it).weight;
                    nodeArr[(*it).vertex].path = v;
                }
            }
        }

    }
}

图类的接口:

/*******************************************************
*  类名称: 邻接表图
********************************************************/ 
class Graph{
    private:
        int edge_num;//图边的个数
        int vertex_num;//图的顶点数目
        list<Node> * graph_list;//邻接表
        vector<GraphNode> nodeArr;//保存每个顶点信息的数组
        
    public:
        Graph(){}
        Graph(char* graph[], int edgenum); 
        ~Graph();
        void print();
        void dijkstra(int src);
        void printShorestPath(); 
    private:
        vector<int> get_graph_value(char* graph[], int columns);
        void addEdge(char* graph[], int columns);
};

测试主函数:

int main(int argc, char *argv[])
{
    char *topo[5000];
    int edge_num;
    char *demand;
    int demand_num;

    char *topo_file = argv[1];
    edge_num = read_file(topo, 5000, topo_file);
    if (edge_num == 0)
    {
        printf("Please input valid topo file.\n");
        return -1;
    }

    int src;
    cout << "输入求最短路径的起点:";
    cin >> src;

    Graph G(topo, edge_num);
    G.print();
    
    cout << "Dijkstra: " << endl;
    G.dijkstra(src);
    G.printShorestPath();


    release_buff(topo, edge_num);

	return 0;
}

测试的图的数据:

1,1,2,2
2,1,4,1
3,2,4,3
4,2,5,10
5,3,1,4
6,3,6,5
7,4,3,2
8,4,6,8
9,4,5,2
10,4,7,4
11,5,7,6
12,7,6,1

图类的源代码:

#ifndef GRAPH_H
#define GRAPH_H

#include <list>
#include <iostream>
#include <vector>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <iterator>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <queue>

using namespace std;

#define MAX_VERTEX_NUM 600
#define INFINITY 1000000//将INFINITY定义为无穷大的值

//保存每个顶点信息的数据结构
struct GraphNode{
    bool known;//当前顶点距离起点的距离是否确定
    int dist;//当前顶点到起点的最短距离
    int path;//当前顶点距离起点的最短路径的前一个顶点
};

//图节点信息
typedef struct Node{ 
    int edge_num;//边号 
    int src;//源点 
    int vertex;//自身 
    int weight;//边的权重 
}Node; 

/*******************************************************
*  类名称: 邻接表图
********************************************************/ 
class Graph{
    private:
        int edge_num;//图边的个数
        int vertex_num;//图的顶点数目
        list<Node> * graph_list;//邻接表
        vector<GraphNode> nodeArr;//保存每个顶点信息的数组
        
    public:
        Graph(){}
        Graph(char* graph[], int edgenum); 
        ~Graph();
        void print();
        void dijkstra(int src);
        void printShorestPath(); 
    private:
        vector<int> get_graph_value(char* graph[], int columns);
        void addEdge(char* graph[], int columns);
};


/*************************************************
*  函数名称:dijkstra(int src)
*  功能描述:求无权图的任意点到其它顶点的距离
*  参数列表:src是起点
*  返回结果:void 
*************************************************/
void Graph::dijkstra(int src)
{
    //初始化顶点信息
    for(int i = 0; i < vertex_num; ++i){
        nodeArr[i].known = false;
        nodeArr[i].dist = INFINITY;
        nodeArr[i].path = 0;
    }
    //重要的一步,开启算法的关键一步
    nodeArr[src].dist = 0;

    for(; ;){
        //找到unknown的dist最小的顶点 
        int v = 0;
        int max = INFINITY;
        for(int i = 0; i < vertex_num; ++i){
            if(!nodeArr[i].known && (max > nodeArr[i].dist)){
                max = nodeArr[i].dist;
                v = i;
            }
        }

        //没有找到满足条件的顶点,退出算法
        if(max == INFINITY)
            break;

        nodeArr[v].known = true;
        //更新与v相邻所有顶点w的dist,path
        for(list<Node>::iterator it = graph_list[v].begin(); it != graph_list[v].end(); ++it){
            if(!nodeArr[(*it).vertex].known){
                if(nodeArr[v].dist + (*it).weight < nodeArr[(*it).vertex].dist){
                    nodeArr[(*it).vertex].dist = nodeArr[v].dist + (*it).weight;
                    nodeArr[(*it).vertex].path = v;
                }
            }
        }

    }
}

/*************************************************
*  函数名称:printShorestPath()
*  功能描述:将获得的src顶点到其它顶点的最短路径输出
*  参数列表:无
*  返回结果:无
*************************************************/
void Graph::printShorestPath()
{
    cout << "顶点\t" << "known\t" << "dist\t" << "path" << endl;
    for(int i = 0; i < vertex_num; ++i){
        if(nodeArr[i].known)
            cout << i << "\t" << nodeArr[i].known << "\t" << nodeArr[i].dist << "\t" << nodeArr[i].path << endl;
    } 
}

/*************************************************
*  函数名称:print
*  功能描述:将图的信息以邻接表的形式输出到标准输出
*  参数列表:无
*  返回结果:无
*************************************************/
void Graph::print()
{
    cout << "******************************************************************" << endl; 
    //for(int i = 0 ; i < MAX_VERTEX_NUM; ++i){
    for(int i = 0 ; i < vertex_num; ++i){
        if(graph_list[i].begin() != graph_list[i].end()){
            cout << i << "-->";
            for(list<Node>::iterator it = graph_list[i].begin(); it != graph_list[i].end(); ++it){
                cout << (*it).vertex << "(边号:" << (*it).edge_num << ",权重:" << (*it).weight << ")-->";
            }
            cout << "NULL" << endl;
        }
    }

    cout << "******************************************************************" << endl; 
}

/*************************************************
*  函数名称:get_graph_value
*  功能描述:将图的每一条边的信息保存到一个数组中
*  参数列表: graph:指向图信息的二维数组
             columns:图的第几条边
*  返回结果:无
*************************************************/
vector<int> Graph::get_graph_value(char* graph[], int columns)
{
    vector<int> v;
    char buff[20];
    int i = 0, j = 0, val;
    memset(buff, 0, 20);

    while((graph[columns][i] != '\n') && (graph[columns][i] != '\0')){
        if(graph[columns][i] != ','){
            buff[j] = graph[columns][i];
            j++;
        }
        else{
            j = 0;
            val = atoi(buff); 
            v.push_back(val);
            memset(buff, 0, 20);
        }
        i++;
    }
    val = atoi(buff); 
    v.push_back(val);

    return v;
}



/*************************************************
*  函数名称:addEdge
*  功能描述:将图的每一条边的信息加入图的邻接表中
*  参数列表:graph:指向图信息的二维数组
             columns:图的第几条边
*  返回结果:无
*************************************************/
void Graph::addEdge(char* graph[], int columns)
{
    Node node;
    vector<int> v = get_graph_value(graph, columns);

    node.edge_num = v[0];
    node.src = v[1];
    node.vertex = v[2];
    node.weight = v[3];


    //根据顶点的标号,求的总的顶点数目
    if(node.vertex > vertex_num)
        vertex_num = node.vertex;

    //要考虑重复的边,但是边的权重不一样
    for(list<Node>::iterator it = graph_list[node.src].begin(); it != graph_list[node.src].end(); ++it){
        if((*it).vertex == node.vertex){
            if((*it).weight > node.weight){
                (*it).weight = node.weight;   
            }
            return;
        }
    }

    graph_list[node.src].push_back(node);
}


/*************************************************
*  函数名称:构造函数
*  功能描述:以邻接表的形式保存图的信息,并保存必须经过的顶点
*  参数列表:graph:指向图信息的二维数组
             edgenum:图的边的个数
*  返回结果:无
*************************************************/
Graph::Graph(char* graph[], int edgenum):nodeArr(MAX_VERTEX_NUM)
{
    edge_num =  edgenum; 
    vertex_num = 0;
    graph_list = new list<Node>[MAX_VERTEX_NUM+1];


    for(int i = 0; i < edgenum; ++i){
        addEdge(graph, i);   
    }

    //对顶点信息进行初始化
    for(int i = 0; i < MAX_VERTEX_NUM; ++i){
        nodeArr[i].known = false;
        nodeArr[i].dist = INFINITY;
        nodeArr[i].path = -1;//如果为-1,表示没有该点,配合dijkstra算法使用
    }

    vertex_num++;
}


/*************************************************
*  函数名称:析构函数
*  功能描述:释放动态分配的内存
*  参数列表:无
*  返回结果:无
*************************************************/
Graph::~Graph()
{
    delete[] graph_list;
}

#endif

测试函数的源代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <time.h>
#include <sys/timeb.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include "graphDijkstra.h"

#define MAX_LINE_LEN 4000

int read_file(char ** const buff, const unsigned int spec, const char * const filename);
void release_buff(char ** const buff, const int valid_item_num);

int main(int argc, char *argv[])
{
    char *topo[5000];
    int edge_num;
    char *demand;
    int demand_num;

    char *topo_file = argv[1];
    edge_num = read_file(topo, 5000, topo_file);
    if (edge_num == 0)
    {
        printf("Please input valid topo file.\n");
        return -1;
    }

    int src;
    cout << "输入求最短路径的起点:";
    cin >> src;

    Graph G(topo, edge_num);
    G.print();
    
    cout << "Dijkstra: " << endl;
    G.dijkstra(src);
    G.printShorestPath();


    release_buff(topo, edge_num);

	return 0;
}

/****************************************************************
*   函数名称:read_file
*   功能描述: 读取文件中的图的数据信息
*   参数列表: buff是将文件读取的图信息保存到buff指向的二维数组中 
*             spec是文件中图最大允许的边的个数
*             filename是要打开的图文件
*   返回结果:无
*****************************************************************/
int read_file(char ** const buff, const unsigned int spec, const char * const filename)
{
    FILE *fp = fopen(filename, "r");
    if (fp == NULL)
    {
        printf("Fail to open file %s, %s.\n", filename, strerror(errno));
        return 0;
    }
    printf("Open file %s OK.\n", filename);

    char line[MAX_LINE_LEN + 2];
    unsigned int cnt = 0;
    while ((cnt < spec) && !feof(fp))
    {
        line[0] = 0;
        fgets(line, MAX_LINE_LEN + 2, fp);
        if (line[0] == 0)   continue;
        buff[cnt] = (char *)malloc(MAX_LINE_LEN + 2);
        strncpy(buff[cnt], line, MAX_LINE_LEN + 2 - 1);
        buff[cnt][4001] = 0;
        cnt++;
    }
    fclose(fp);
    printf("There are %d lines in file %s.\n", cnt, filename);

    return cnt;
}

/****************************************************************
*   函数名称:release_buff
*   功能描述: 释放刚才读取的文件中的图的数据信息
*   参数列表: buff是指向文件读取的图信息
*             valid_item_num是指图中边的个数
*   返回结果:void
*****************************************************************/
void release_buff(char ** const buff, const int valid_item_num)
{
    for (int i = 0; i < valid_item_num; i++)
        free(buff[i]);
}

运行结果:

《数据结构与算法——最短路径Dijkstra算法的C++实现》

    原文作者:Dijkstra算法
    原文地址: https://blog.csdn.net/Linux_ever/article/details/51314082
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞