C++ 图的广度优先遍历,深度优先遍历和Dijkstra

十多年前学的数据结构问题和算法问题,闲来拿出来复习复习,权当练练手。本代码所用的方法并非最高效的实现方法,纯以练手为目的,实现过程中尽量把能用上的STL容器挨个用了一遍。解题过程中一些思考都写在代码中的注释里了,顺便重新整理一下思路。

此代码兼容联通图和非联通图。不完全测试了一些边界条件,如有没考虑周全的地方欢迎拍砖或提供test case。

废话不多说,看东西。

EWDigraph.h头文件定义

#pragma once
#include 
#include  
#include  
#include  
#include  
#include  
#include  
#include  

using namespace std;

class EWDigraph
//图用邻接表方式表示
{
private:
    //图中顶点数
    int vex_;
    //图的边数
    int edge_;
    //当前顶点是否在队列中,可以变成局部变量,但是可能会增加内存分配和释放的次数
    map mapIsInQue_;
    //顶点V到出发点S的最短距离
    map mapDistance_;
    //顶点V到出发点S的最短路径
    map mapPath_;
    //图的邻接表Map
    map>> ewd_;
public:

    //构造函数,传入顶点数,边数和<顶点1,顶点2,边权值>元组的数组
    EWDigraph(const int vex, const int edge, const vector>& );
    //打印邻接表
    void Print();
    //计算出发点start到图中所有点的最短距离
    void Dijkstra(const char start);
    //计算出发点start到终点end的最短距离并且打印最短路径
    void Dijkstra(const char start, const char end);
    //获取当前图中的所有顶点
    //函数最后的const是向编译器承诺函数返回值不会被修改
    vector GetNodes() const;
    //检查输入的顶点是否在顶点列表中
    bool IsValidVex(const char vex) const;
    //从出发点start开始对图进行广度优先遍历 Breadth First Search Traversal
    void BFST(const char start);
    //从出发点start开始对图进行深度优先遍历 Depth First Search Traversal
    void DFST(const char start);

private:
    //初始化 mapIsInQue_, mapDistance_, mapPath_
    void InitVecs();
    //打印出发点start到图中所有点的最短距离和最短路径
    void PrintDij(const char start);
    //打印出发点start到终点end的最短距离和最短路径
    void PrintDij(const char start, const char end);
};

EWD 同时是Edge Weighted Digraph和Edsger Wybe Dijkstra 的缩写,不知是不是巧合...
const int vex, const int edge 参数中的const是向编译器承诺在函数中不对该参数进行修改。如果该参数在函数体内被改变,则会收到编译器报错。
const vector>& 指该参数以引用方式传入函数,减少一次参数拷贝的过程,尤其是当数组较大时,会是很明显的运行时间和空间的优化。如果不是明确地需要在函数中修改传入的参数,最好将所有传入的参数都标记成const。顺便说一句,Google在其开源项目风格指南中明确规定,所有按引用传递的参数必须加上 const.

图的创建

    vector> g = {
        make_tuple('E', 'F', 0.35),
        make_tuple('F', 'E', 0.35),
        make_tuple('E', 'H', 0.37),
        make_tuple('F', 'H', 0.28),
        make_tuple('H', 'F', 0.28),
        make_tuple('F', 'B', 0.32),
        make_tuple('A', 'E', 0.38),
        make_tuple('A', 'C', 0.26),
        make_tuple('H', 'D', 0.39),
        make_tuple('B', 'D', 0.29),
        make_tuple('C', 'H', 0.34),
        make_tuple('G', 'C', 0.40),
        make_tuple('D', 'G', 0.52),
        make_tuple('G', 'A', 0.58),
        make_tuple('G', 'E', 0.93),
        make_tuple('I', 'J', 0.5),
        make_tuple('J', 'J', 0)
    };
    
    auto ewd = make_unique(10,17,g);
    ewd->Print();

边带权的单向图。注意:I和J与其他顶点非联通。

打印结果

    A | A->E:0.38  A->C : 0.26
    B | B->D : 0.29
    C | C->H : 0.34
    D | D->G : 0.52
    E | E->F : 0.35  E->H : 0.37
    F | F->E : 0.35  F->H : 0.28  F->B : 0.32
    G | G->C : 0.4  G->A : 0.58  G->E : 0.93
    H | H->F : 0.28  H->D : 0.39
    I | I->J : 0.5
    J | J->J : 0

广度优先遍历实现思路:

  1. 把出发点压入队列
  2. 读取队列头,将该顶点标记为“已访问”
  3. 把队列头指向的所有未访问的顶点压入队尾
  4. 重复第2,3步,直到队列为空
  5. 检查图中是否还有顶点未访问 (非连通顶点),如果是,则将该顶点入队列,重复2,3,4,5
void EWDigraph::BFST(const char start){
    //也可以使用queue, 区别是不能push_front
    std::deque q;
    map vVisited;
    for (auto i : GetNodes()) {
        //所有顶点标记为未访问
        vVisited[i] = false;
    }
    //出发点入队列、
    q.push_front(start);
    cout << "BFST(" << start << "):"<

调用方法

    for (auto it : ewd->GetNodes()){
        ewd->BFST(it);
        cout << endl;
    }

打印结果

    BFST(A) :
    A - E - C - F - H - B - D - G - I - J - end
    BFST(B) :
    B - D - G - C - A - E - H - F - I - J - end
    BFST(C) :
    C - H - F - D - E - B - G - A - I - J - end
    BFST(D) :
    D - G - C - A - E - H - F - B - I - J - end
    BFST(E) :
    E - F - H - B - D - G - C - A - I - J - end
    BFST(F) :
    F - E - H - B - D - G - C - A - I - J - end
    BFST(G) :
    G - C - A - E - H - F - D - B - I - J - end
    BFST(H) :
    H - F - D - E - B - G - C - A - I - J - end
    BFST(I) :
    I - J - A - E - C - F - H - B - D - G - end
    BFST(J) :
    J - A - E - C - F - H - B - D - G - I - end

深度优先遍历实现思路:

  1. 把当前图中所有顶点压入栈 (防止有非联通顶点未被遍历)
  2. 出发点入栈
  3. 读取栈顶,将该顶点标记为“已访问”
  4. 把栈顶指向的所有未访问的顶点入栈
  5. 重复第3,4步,直到队列为空
void EWDigraph::DFST(const char start){

    std::stack s;
    map vVisited;
    for (auto i : GetNodes()) {
        vVisited[i] = false;
        s.push(i);
    }
    //出发点处于栈顶
    s.push(start);
    cout << "DFST(" << start << "):"<

调用方法

    for (auto it : ewd->GetNodes()){
        ewd->DFST(it);
        cout << endl;
    }

打印结果D

    DFST(A) :
    A - C - H - D - G - E - F - B - J - I - end
    DFST(B) :
    B - D - G - E - H - F - A - C - J - I - end
    DFST(C) :
    C - H - D - G - E - F - B - A - J - I - end
    DFST(D) :
    D - G - E - H - F - B - A - C - J - I - end
    DFST(E) :
    E - H - D - G - A - C - F - B - J - I - end
    DFST(F) :
    F - B - D - G - E - H - A - C - J - I - end
    DFST(G) :
    G - E - H - D - F - B - A - C - J - I - end
    DFST(H) :
    H - D - G - E - F - B - A - C - J - I - end
    DFST(I) :
    I - J - H - D - G - E - F - B - A - C - end
    DFST(J) :
    J - I - H - D - G - E - F - B - A - C - end

Dijkstra最短路径实现思路(此方法不支持路径为负)

  1. 将出发点(标记为S)到自己的距离设为0,到所有其他顶点的距离设为无穷大,所有其他顶点路径标记为“不可达到”
  2. 出发点压入队列,将出发点标记为“已在队列中”
  3. 读取队列头(标记为H),将其标记为“未在队列中”
  4. 遍历所有与队列头相邻的顶点(标记为N1,N2,...)。如果S到N1(N2,...)的距离(初始值是无穷大) > S到H的距离+H到N1(N2,...)的距离时,记录:S到N1的距离=S到H距离+H到N1(N2,...)的距离
  5. 使用mapPath_记录当前状态下S到N1(N2,...)的最短路径必须经过顶点H,该状态在后续的遍历中可能会改变
  6. 检查N1,N2,...是否在队列中,如果不在,则将其压入队列
  7. 重复3,4,5,6,直到队列为空
void EWDigraph::Dijkstra(const char start){
    InitVecs();
    cout << "Start Point:" << start << endl;
    //出发点到自身的距离为0
    mapDistance_[start] = 0;
    std::queue q;
    //出发点入队列
    q.push(start);
    //标记已在队列中
    mapIsInQue_[start] = true;
    while (!q.empty()){
        //队列头出队列
        int node = q.front();
        q.pop();
        mapIsInQue_[node] = false;
        for (auto it : ewd_[node]){
            //遍历所有与当前顶点(队列头顶点)相邻的顶点
            if (mapDistance_[it.first] > mapDistance_[node] + it.second){
                mapDistance_[it.first] = mapDistance_[node] + it.second;
                //记录当前到达该相邻顶点的最短路径需要经过当前顶点(队列头顶点)
                mapPath_[it.first] = node;
                if (!mapIsInQue_[it.first]){
                    //如果这些与当前顶点相邻的顶点不在队列中,则将其压入队列
                    q.push(it.first);
                    mapIsInQue_[it.first] = true;
                }
            }
        }
    }
    PrintDij(start);
}

Dijkstra最短路径打印实现思路

  1. 如果目标顶点T被标记为"不可达到"则打印"unreachable",否则将T入栈
  2. 遍历mapPath_,找到到达目标顶点T必须经过的前一个顶点T-1
  3. 将T-1入栈,重复2找到到达目标顶点必须经过的前一个顶点T-2
  4. 以此类推,直到找到出发点S,将S入栈
  5. 根据出栈顺序依次打印站内顶点
void EWDigraph::PrintDij(const char start){
    for (auto im:ewd_){
        cout << im.first << ":" << mapDistance_[im.first] << "  |  Path = " ;
        std::stack spath;
        for (int j = im.first; j != start; j = mapPath_[j]){
            if (j == '0'){
                cout << "unreachable " ;
                break;
            }
            else{
                spath.push(j);
            }
        }
        spath.push(start);
        while (spath.size()>0){
            cout << spath.top();
            if (spath.top() != im.first)
                cout << "->";
            spath.pop();
        }
        cout << endl;
    }
    cout << endl;
}

运行结果

    Start Point : A
    A : 0 | Path = A
    B : 1.05 | Path = A->E->F->B
    C : 0.26 | Path = A->C
    D : 0.99 | Path = A->C->H->D
    E : 0.38 | Path = A->E
    F : 0.73 | Path = A->E->F
    G : 1.51 | Path = A->C->H->D->G
    H : 0.6 | Path = A->C->H
    I : 1.79769e+308 | Path = unreachable A->I
    J : 1.79769e+308 | Path = unreachable A->J

    Start Point : B
    A : 1.39 | Path = B->D->G->A
    B : 0 | Path = B
    C : 1.21 | Path = B->D->G->C
    D : 0.29 | Path = B->D
    E : 1.74 | Path = B->D->G->E
    F : 1.83 | Path = B->D->G->C->H->F
    G : 0.81 | Path = B->D->G
    H : 1.55 | Path = B->D->G->C->H
    I : 1.79769e+308 | Path = unreachable B->I
    J : 1.79769e+308 | Path = unreachable B->J

    Start Point : C
    A : 1.83 | Path = C->H->D->G->A
    B : 0.94 | Path = C->H->F->B
    C : 0 | Path = C
    D : 0.73 | Path = C->H->D
    E : 0.97 | Path = C->H->F->E
    F : 0.62 | Path = C->H->F
    G : 1.25 | Path = C->H->D->G
    H : 0.34 | Path = C->H
    I : 1.79769e+308 | Path = unreachable C->I
    J : 1.79769e+308 | Path = unreachable C->J

    Start Point : D
    A : 1.1 | Path = D->G->A
    B : 1.86 | Path = D->G->C->H->F->B
    C : 0.92 | Path = D->G->C
    D : 0 | Path = D
    E : 1.45 | Path = D->G->E
    F : 1.54 | Path = D->G->C->H->F
    G : 0.52 | Path = D->G
    H : 1.26 | Path = D->G->C->H
    I : 1.79769e+308 | Path = unreachable D->I
    J : 1.79769e+308 | Path = unreachable D->J

    Start Point : E
    A : 1.86 | Path = E->H->D->G->A
    B : 0.67 | Path = E->F->B
    C : 1.68 | Path = E->H->D->G->C
    D : 0.76 | Path = E->H->D
    E : 0 | Path = E
    F : 0.35 | Path = E->F
    G : 1.28 | Path = E->H->D->G
    H : 0.37 | Path = E->H
    I : 1.79769e+308 | Path = unreachable E->I
    J : 1.79769e+308 | Path = unreachable E->J

    Start Point : F
    A : 1.71 | Path = F->B->D->G->A
    B : 0.32 | Path = F->B
    C : 1.53 | Path = F->B->D->G->C
    D : 0.61 | Path = F->B->D
    E : 0.35 | Path = F->E
    F : 0 | Path = F
    G : 1.13 | Path = F->B->D->G
    H : 0.28 | Path = F->H
    I : 1.79769e+308 | Path = unreachable F->I
    J : 1.79769e+308 | Path = unreachable F->J

    Start Point : G
    A : 0.58 | Path = G->A
    B : 1.34 | Path = G->C->H->F->B
    C : 0.4 | Path = G->C
    D : 1.13 | Path = G->C->H->D
    E : 0.93 | Path = G->E
    F : 1.02 | Path = G->C->H->F
    G : 0 | Path = G
    H : 0.74 | Path = G->C->H
    I : 1.79769e+308 | Path = unreachable G->I
    J : 1.79769e+308 | Path = unreachable G->J

    Start Point : H
    A : 1.49 | Path = H->D->G->A
    B : 0.6 | Path = H->F->B
    C : 1.31 | Path = H->D->G->C
    D : 0.39 | Path = H->D
    E : 0.63 | Path = H->F->E
    F : 0.28 | Path = H->F
    G : 0.91 | Path = H->D->G
    H : 0 | Path = H
    I : 1.79769e+308 | Path = unreachable H->I
    J : 1.79769e+308 | Path = unreachable H->J

    Start Point : I
    A : 1.79769e+308 | Path = unreachable I->A
    B : 1.79769e+308 | Path = unreachable I->B
    C : 1.79769e+308 | Path = unreachable I->C
    D : 1.79769e+308 | Path = unreachable I->D
    E : 1.79769e+308 | Path = unreachable I->E
    F : 1.79769e+308 | Path = unreachable I->F
    G : 1.79769e+308 | Path = unreachable I->G
    H : 1.79769e+308 | Path = unreachable I->H
    I : 0 | Path = I
    J : 0.5 | Path = I->J

    Start Point : J
    A : 1.79769e+308 | Path = unreachable J->A
    B : 1.79769e+308 | Path = unreachable J->B
    C : 1.79769e+308 | Path = unreachable J->C
    D : 1.79769e+308 | Path = unreachable J->D
    E : 1.79769e+308 | Path = unreachable J->E
    F : 1.79769e+308 | Path = unreachable J->F
    G : 1.79769e+308 | Path = unreachable J->G
    H : 1.79769e+308 | Path = unreachable J->H
    I : 1.79769e+308 | Path = unreachable J->I
    J : 0 | Path = J

EWDigraph.cpp完整代码

#include "EWDigraph.h"

EWDigraph::EWDigraph(const int vex, const int edge, const vector>   &v)
    :vex_(vex),
    edge_(edge)
{
    //把vector> 转换成map>
    //get<0>(v[i])是元组tuple的特殊用法
    for (int i = 0; i < edge_; i++) 
        ewd_[get<0>(v[i])].push_back(make_pair(get<1>(v[i]), get<2>(v[i])));
}

void EWDigraph::Print(){
    for (auto a: ewd_){
        cout << a.first<< "|";
        for (auto it:ewd_[a.first])
            cout << a.first << "->" << it.first << ":" << it.second << "  ";
        cout << endl;
    }
    cout << endl;
}

vector EWDigraph::GetNodes() const{
    vector vRet;
    for (auto it : ewd_)
        vRet.push_back(it.first);
    return vRet;
}

void EWDigraph::BFST(const char start){
    //也可以使用queue, 区别是不能push_front
    std::deque q;
    map vVisited;
    for (auto i : GetNodes()) {
        //所有顶点标记为未访问
        vVisited[i] = false;
    }
    //出发点入队列、
    q.push_front(start);
    cout << "BFST(" << start << "):"< s;
    map vVisited;
    for (auto i : GetNodes()) {
        vVisited[i] = false;
        s.push(i);
    }
    //出发点处于栈顶
    s.push(start);
    cout << "DFST(" << start << "):"< mapPath_
        mapPath_[i] = '0';
        //map mapIsInQue_
        mapIsInQue_[i] = false;
        //DBL_MAX = 2^1024
        mapDistance_[i] = DBL_MAX;
    }
}

void EWDigraph::Dijkstra(const char start){
    InitVecs();
    cout << "Start Point:" << start << endl;
    //出发点到自身的距离为0
    mapDistance_[start] = 0;
    std::queue q;
    //出发点入队列
    q.push(start);
    //标记已在队列中
    mapIsInQue_[start] = true;
    while (!q.empty()){
        //队列头出队列
        int node = q.front();
        q.pop();
        mapIsInQue_[node] = false;
        for (auto it : ewd_[node]){
            //遍历所有与当前顶点(队列头顶点)相邻的顶点
            if (mapDistance_[it.first] > mapDistance_[node] + it.second){
                mapDistance_[it.first] = mapDistance_[node] + it.second;
                //记录当前到达该相邻顶点的最短路径需要经过当前顶点(队列头顶点)
                mapPath_[it.first] = node;
                if (!mapIsInQue_[it.first]){
                    //如果这些与当前顶点相邻的顶点不在队列中,则将其压入队列
                    q.push(it.first);
                    mapIsInQue_[it.first] = true;
                }
            }
        }
    }
    PrintDij(start);
}

void EWDigraph::PrintDij(const char start){
    for (auto im:ewd_){
        cout << im.first << ":" << mapDistance_[im.first] << "  |  Path = " ;
        std::stack spath;
        for (int j = im.first; j != start; j = mapPath_[j]){
            if (j == '0'){
                cout << "unreachable " ;
                break;
            }
            else{
                spath.push(j);
            }
        }
        spath.push(start);
        while (spath.size()>0){
            cout << spath.top();
            if (spath.top() != im.first)
                cout << "->";
            spath.pop();
        }
        cout << endl;
    }
    cout << endl;
}

void EWDigraph::Dijkstra(const char start, const char end){
    //指定起点和终点的最短路径查找,思路同前
    InitVecs();
    cout << start << "->" << end << ":";
    mapDistance_[start] = 0;
    std::queue q;
    q.push(start);
    mapIsInQue_[start] = true;
    while (!q.empty()){
        int node = q.front();
        q.pop();
        mapIsInQue_[node] = false;
        for (auto it : ewd_[node]){
            if (mapDistance_[it.first] > mapDistance_[node] + it.second){
                mapDistance_[it.first] = mapDistance_[node] + it.second;
                mapPath_[it.first] = node;
                if (!mapIsInQue_[it.first]){
                    q.push(it.first);
                    mapIsInQue_[it.first] = true;
                }
            }
        }
    }
    PrintDij(start, end);
}

void EWDigraph::PrintDij(const char start, const char end){
    cout << mapDistance_[end] << "  |  Path = ";
    std::stack spath;
    for (int j = end; j != start; j = mapPath_[j]){
        if (j == '0'){
            cout << "unreachable";
            break;
        }
        else{
            spath.push(j);
        }
    }
    spath.push(start);

    while (spath.size()>0){
        cout << spath.top();
        if (spath.top() != end)
            cout << "->";
        spath.pop();
    }
    cout << endl;
    cout << endl;
}

bool EWDigraph::IsValidVex(const char vex) const{
    //检查输入的顶点是否合法
    return (ewd_.find(vex) != ewd_.end());
}

你可能感兴趣的:(C++ 图的广度优先遍历,深度优先遍历和Dijkstra)