狄克斯特拉算法 - 学习整理

个人整理,原创发布,转载请标注博客来源。https://editor.csdn.net/md/?articleId=102799813
很多时候,总会忽略了一些你认为不需要的知识体系,但最终你发现,你又要花大量的时间去弥补这个空缺。


算法简介
狄克斯特拉算法,用于计算出在非负权重的情况下,图中起点到终点的最短路径......
解决问题
  1. 从A出发是否存在到达B的路径;
  2. 从A出发到达B点的最短路径(时间最少或者路径最少);
算法思路
  1. 找出“最便宜”的节点,即可在最短时间内到达的节点;
  2. 更新此节点到“邻居”节点的开销,其含义下文案例说明。
  3. 重复上述过程,直到对图中的所有节点都这样做了;
  4. 计算最短路径;
案例分析
  • 案例一

    图示(MarkDown画的图)

6
2
1
5
3
起点
A
B
终点
  1. 首先列出起点到各相连节点的耗费时间

    父节点 节点 耗时
    起点 A 6分钟
    起点 B 2分钟
    终点 ∞ 无穷大
  2. 获取耗时最短的节点,由上表可以看出是起点->B节点花费时间最少,计算节点B前往各个邻居节点的所需时间,并更新原本需要花费更多的时间的节点:

    父节点 节点 耗时
    B A 5分钟 (更新耗时)
    起点 B 2分钟
    B 终点 7 (更新耗时)
  3. 此时,B节点添加进已处理的列表中,不再进行处理,现在只剩余A节点到各邻居节点的所需要的耗时,并更新原本需要花费更多的时间的节点。

    父节点 节点 耗时
    B A 5分钟
    起点 B 2分钟
    A 终点 6 (更新耗时)
  4. 有表格可以看出,最终路径是起点-> B -> A -> 终点,耗时6分钟。

算法编码
/*
 * Dijkstra.cpp
 *
 *  Created on: Oct 21, 2019
 *      Author: kjh
 */

#include 
#include 
#include 
#include 
#include 
#include 

using std::map;
using std::string;
using std::set;

class Dijkstra {
     
public:
    typedef std::map<std::string, int> KeyValueNode;

    typedef std::map<std::string, KeyValueNode> GraphMap;

    void init();

    void process(const std::string& startKey, const std::string& target);

private:
    std::string getMinCost(KeyValueNode& keyVals);
    void process_node(const std::string& key, KeyValueNode& keyVals);
    void print(const std::string& target);

private:
    // for graph.
    GraphMap _map;
    // for visited node
    std::set<string> _visited_set;
    // for parents relation
    std::map<string, string> _node_parents;
    // 存放到各节点需要的最小时间
    KeyValueNode _cost_path;
};

/*******************************
 * start -->  A (6)
 *       -->  B (2)
 *       B -->  A  (3)
 *         -->  fin(5)
 *            A --> fin (1)
 ******************************/
void Dijkstra::init() {
     
    _map.clear();
    _visited_set.clear();
    _cost_path.clear();
    _node_parents.clear();
    // init graph
    KeyValueNode start = {
     {
     "A", 6}, {
     "B", 2}};
    KeyValueNode b = {
     {
     "A", 3}, {
     "FIN", 5}};
    KeyValueNode a = {
     {
     "FIN", 1}};
    _map = {
     {
     "START", start}, {
     "B", b}, {
     "A", a}};
}

std::string Dijkstra::getMinCost(KeyValueNode& keyVals) {
     
    int min_cost = std::numeric_limits<int>::max();
    string min_key;
    for (auto& it : keyVals) {
     
        if (it.second < min_cost && _visited_set.count(it.first) != 1) {
     
            min_cost = it.second;
            min_key = it.first;
        }
    }
    return min_key;
}

void Dijkstra::process(const std::string& startKey, const std::string& target) {
     
    // find start key.
    auto iter = _map.find(startKey);
    if (iter == _map.end()) {
     
        return ;
    }

    // 处理起点的邻居节点,讲邻居节点的耗时加入cost_path.
    for (auto & node :iter->second) {
     
        int cost = node.second;
        _cost_path.insert(node);
        _node_parents.insert(std::make_pair(node.first, startKey));
    }
    
    // 查找cost最小的节点
    std::string mini_key = getMinCost(_cost_path);
    while(!mini_key.empty()) {
     
        std::cout << "process node: " << mini_key << std::endl;
        auto it = _map.find(mini_key);
        if (it == _map.end()) {
     
            break;
        }
        // 处理cost最小节点邻居节点,并更新cost_path
        process_node(mini_key, it->second);
        _visited_set.insert(mini_key);
        mini_key = getMinCost(_cost_path);
    }
    // 输出结果
    print(target);
}

void Dijkstra::process_node(const std::string& key, KeyValueNode& keyVals) {
     
    auto it = _cost_path.find(key);
    if (it == _cost_path.end()) {
     
        return;
    }
    int has_cost = it->second;
    for (auto& node : keyVals) {
     
        int cost = node.second;
        cost = has_cost + cost;

        auto cit = _cost_path.find(node.first);
        if (cit == _cost_path.end()) {
     
            _cost_path.insert(std::make_pair(node.first, cost));
            _node_parents.insert(std::make_pair(node.first, key));
        } else {
     
            int old_cost = cit->second;
            if (cost < old_cost) {
     
                _cost_path.erase(cit);
                _cost_path.insert(std::make_pair(node.first, cost));
                _node_parents.erase(node.first);
                _node_parents.insert(std::make_pair(node.first, key));

            }
        }
    }
}

void Dijkstra::print(const std::string& target) {
     
    auto citer = _node_parents.find(target);
    if (citer != _node_parents.end()) {
     
        std::vector<std::string> path;
        std::string parents = citer->second;
        path.push_back(target);
        while(!parents.empty()) {
     
            path.push_back(parents);
            auto piter = _node_parents.find(parents);
            if (piter != _node_parents.end()) {
     
                parents = piter->second;
            } else {
     
                parents = "";
            }
        }

        int size = path.size();
        std::cout << "Path : ";
        for (int i = size -1 ; i >= 0; i--) {
     
            std::cout << path[i];
            if (i > 0) {
     
                std::cout << " --> ";
            }
        }

        auto cost_iter = _cost_path.find(target);
        if (cost_iter != _cost_path.end()) {
     
            std::cout << " cost: " << cost_iter->second;
        }

        std::cout << std::endl;
    } else {
     
        std::cout << "Does not exists to " << target << " path" << std::endl;
    }
}

int main() {
     
    Dijkstra test;
    test.init();
    test.process("A", "H");
    return 0;
}

// g++ Dijkstra.cpp -std=c++11
  • 案例二

    案例一中选择了最简单的图,案例二将图复杂一步,来说明算法的思想

5
1
10
5
3
3
6
2
10
A
B
C
E
D
H
F
G
  • 如果使用广度优先算法,可以得出A到H的最短路径为:A–>B–>E–>H
  • 图中给每个节点赋予权重值,就可以使用狄克斯特拉算法来求解A到H的权重和值最小的路径。

按照案例一中的步骤,进行分析:

  1. 取A(起点)到各节点的耗时
父节点 节点 耗时
A       |    B      |   5
A       |    C      |   1
A       |    H      |   +∝
  1. 找出A节点到达各邻居节点中权重最小的节点,此时C节点的权重最小,将C节点邻居节点D、F加入节点图表中
父节点 节点 耗时 标志
A       |    B      |   5       |
A       |    C      |   1       |   C已处理
A       |    H      |   +∝     |    
C       |    D      |   6       |
C       |    F      |   7       |   
  1. 在加入C节点的邻居节点后,从最新的列表中找出A到各节点最小权重点,此时B最小;处理B节点的各邻居节点
父节点 节点 耗时 标志
A       |    B      |   5       |   B已处理
A       |    C      |   1       |   C已处理
A       |    H      |   +∝     |    
C       |    D      |   6       |
C       |    F      |   7       |  
B       |    E      |   15      |   
  1. A的相邻节点已经处理完成,此时找最小权重路径,C-D,计算处理相邻节点的耗时,并标记D已处理
父节点 节点 耗时 标志
A       |    B      |   5       |   B已处理
A       |    C      |   1       |   C已处理
A       |    H      |   +∝     |    
C       |    D      |   6       |   D已处理
C       |    F      |   7       |  
D       |    E      |   9       |   
  1. 同理处理F
父节点 节点 耗时 标志
A       |    B      |   5       |   B已处理
A       |    C      |   1       |   C已处理
E       |    H      |   +∝     |    
C       |    D      |   6       |   D已处理
C       |    F      |   7       |   F已处理
D       |    E      |   9       |   
F       |    G      |   9       |
  1. 同理处理E
父节点 节点 耗时 标志
A       |    B      |   5       |   B已处理
A       |    C      |   1       |   C已处理
E       |    H      |   12      |    
C       |    D      |   6       |   D已处理
C       |    F      |   7       |   F已处理
D       |    E      |   9       |   E已处理   
F       |    G      |   9       |
  1. 同理更新G
父节点 节点 耗时 标志
A       |    B      |   5       |   B已处理
A       |    C      |   1       |   C已处理
E       |    H      |   12      |   
C       |    D      |   6       |   D已处理
C       |    F      |   7       |   F已处理
D       |    E      |   9       |   E已处理   
F       |    G      |   9       |   G已处理

经过上述的过程推敲,可以看出,最后一步处理G,其用时没有比之前更小了,固不更新列表值。
有次的出最小权重:12, 路径:A–>C–>D–>E–>H

程序验证:
// 替换案例一中初始化函数,生成新的图

void Dijkstra::init() {
     
    _map.clear();
    _visited_set.clear();
    _cost_path.clear();
    _node_parents.clear();

    // init graph
    KeyValueNode a = {
     {
     "B", 5}, {
     "C", 1}};
    KeyValueNode b = {
     {
     "E", 10}};
    KeyValueNode c = {
     {
     "D", 5}, {
     "F", 6}};
    KeyValueNode d = {
     {
     "E", 3}};
    KeyValueNode e = {
     {
     "H", 3}};
    KeyValueNode f = {
     {
     "G", 2}};
    KeyValueNode g = {
     {
     "H", 10}};
    _map = {
     {
     "A", a}, {
     "B", b}, {
     "C", c}, {
     "D", d}, {
     "E", e}, {
     "F", f}, {
     "G", g}};
}
  • 从案例的图示做为输入编写了算法,即在固定的图规格中寻找消耗最小的路径。
  • 转化到工程项目中,可能给出的图会很复杂,需要根据实际的工程问题来解答(狄克斯特拉算法的核心不会变化,我们就需要从具体的问题抽象划分图解)。
  • 比如将复杂的问题分解成我们需要的两点间的图。在应用狄克斯特拉算法来解决。
  • 上述只是一个解题的思路,具体问题还需要具体对待。

你可能感兴趣的:(C++基础知识,Linux,笔记,狄克斯特拉,算法,图)