图论-BFS解无权有向图最短路径距离

概述

本篇博客主要内容:

  1. 对广度优先搜索算法(Breadth-First-Search)进行介绍;
  2. 介绍用邻接表的存储结构实现一个图(附C++实现源码);
  3. 介绍用BFS算法求解无权有向图(附C++实现源码)。

广度优先搜索算法(Breadth-First-Search)又被翻译为宽度优先搜索或横向优先搜索,简称BFS。BFS是一种盲目搜索法,其系统地展开并检查图中的全部顶点。BFS也是最简便的图搜索算法之一,其类似思想被Dijkstra算法和Prim算法采用。
通过一个例子来介绍BFS的思想,如下图所示有一张由六个顶点组成的有向图,其中图中的每条边的权值都是1(也可以看做无权图),求解从一个顶点(源点)出发到图中全部顶点的距离。

图论-BFS解无权有向图最短路径距离_第1张图片

  • 选择V2作为源点,寻找距离V2距离为2的顶点,找到V2;
  • 寻找距离V2距离为1的顶点,找V2的出边,找到V0和V5;
  • 寻找距离V2距离为2的顶点,由于顶点V5的出度是0,所以找V0的出边,找到V1;
  • 寻找距离V2距离为3的顶点,找V1的出边,找到V3和V4
  • 寻找距离V2距离为4的顶点,找V3的出边,找到V5和V6
  • 于是图中所有的顶点都被访问过一次
    总结一下搜索的顺序就是:V2–>V0–>V5–>V1–>V3–V4–>V5–>V6

这种搜索图的方法被称为广度优先搜索(Breadth-First-Search),该方法按层处理顶点。距离源点最近的那些顶点首先被求值,而最远的那些顶点最后被求职。这很像树的层序遍历(level-order-traversal)。

图的邻接表实现

用邻接表实现一张图,这里采用的是两个两层链表的结构,其中一层链表存储顶点信息。每个顶点上有一个链表存储该顶点的出边。如下列伪代码所示:

#include 
class Edge {
    // ...
};

class VertexNode {
    std::list edgeAdj;    /* 顶点的邻接表 */
    // ...
};
class Graph {
    // 顶点集合
    std::list vertexSet;
    //...

下面的代码给出边的定义。
边有三个属性和三个方法:
- 属性:权重、弧(有向边)的起点和终点指针;
- 方法:获取弧的起点、终点和权值

typedef class VertexNode _VertexNode;
// 边的定义
class Edge {

    int weight;         /* 边的权值 */
    _VertexNode * ori;  /* 弧的起点*/
    _VertexNode * des;  /* 弧的终点*/

public:

    Edge(int _weight,_VertexNode *_ori,_VertexNode *_des): weight(_weight),ori(_ori),des(_des){};
    ~Edge() {};

    // 获取弧的起点
    _VertexNode* getOri() {return ori;};

    // 获取弧的终点
    _VertexNode* getDes() {return des;};

    // 获取弧的权值
    int getWeight() {return weight;};

};

下面代码给出了顶点的定义。顶点具有五个属性和十个方法。属性的含义和方法的功能在代码的注释中都有说明,这里有两点说明:

  • VISIT_STATUS状态表示该顶点是否被访问过,解决顶点重复访问的去重;
  • 顶点定义key属性,是顶点的标号,方便给大家演示。
typedef enum  _VISIT_STATUS{
    NO_VISIT,       /* 未访问过此点 */
    VISIT_NO_ADJ,   /* 访问过此点,但未访问过其邻接表 */
    VISIT_ADJ,      /* 访问过此点和其邻接表 */
} VISIT_STATUS;

class VertexNode {
    long key;               /* 顶点的关键字,用于标识该顶点*/
    int value;              /* 顶点附着的信息 */
    std::list edgeAdj;/* 顶点的邻接表 */
    VISIT_STATUS status;    /* 标识此顶点的访问状态 */
    int distence;           /* 此顶点与搜索顶点的距离 */
    std::list::iterator iter_edge;/* 用于遍历邻接表 */
public:

    VertexNode(long _key,int _value) : key(_key), \
        value(_value) { distence = 0xFFFF;status = NO_VISIT; };
    ~VertexNode() {};

    // 获取该顶点的关键字
    long getKey() { return key;};

    // 设置此顶点的关键字
    void setKey(long _key) { key = _key;};

    // 在顶点上添加一条弧
    void addEdge(Edge* _edge);

    // 在顶点上删除一条弧
    void removeEdge(Edge* _edge);

    // 获取邻接表第一条边
    Edge * getEdgeHeader() {
        iter_edge = edgeAdj.begin();
        return *iter_edge;
    };

    bool nextEdge(Edge **_edge) {
        iter_edge++;
        if (iter_edge != edgeAdj.end()) {
            *_edge = *iter_edge;
            return true;
        }
        return false;
    };

    // 获取顶点的访问状态
    VISIT_STATUS getStatus() { return status;};

    // 设置顶点的访问状态
    void setStatus(VISIT_STATUS _status) {status = _status;};

    // 获取出发点到此点的最小距离
    int getDistence() {return distence;};

    // 设置出发点到此点的最小距离
    void setDistence(int _distence) {distence = _distence;};

};

OKay,看过了边和顶点的定义,下面代码给出图的定义。主要有一个属性vertexSet和三个方法组成。其中vertexSet属性主要表示图中顶点的集合。

typedef std::map<long, int>  DistenceOfGraph;

class Graph {
    // 顶点集合
    std::list vertexSet;

public:

    Graph() {};
    ~Graph() {};

    /* 功能找出key == _searchKey的顶点到图中所有其它顶点的距离,注:不可达的顶点距离为0xFFFF
     * Input  查找关键字
     * Output map&dis 输出结果,long 为关键字,int 为输出结果
     */
    void bfs(long _searchKey,DistenceOfGraph &dis);

    // 打印图中的全部顶点
    void printNode();

    // 打印顶点键值为key的边
    void printEdge(long key);

    // 寻找顶点关键字为key的顶点,若找到由_node变量返回
    bool findNode(long key,VertexNode **_node);

    // 向图中增加一个顶点
    VertexNode* addNode(long key,int value);

    // 向图中增加一条边
    void addEdge(long keyOri,long keyDes,int weight);

};

okay,现在我们已经完成了图的邻接表存储方式的定义,以上图中的各方法将在后面具体实现。


BFS算法求解无权有向图

在本文第一部分广度优先搜索那部分给出了一个求解无权有向图的问题。下面我将结合此问题运用上面介绍的图的邻接表结构存储来介绍BFS算法的实现。

广度优先搜索过程中在访问V1顶点邻接表过程中,将V0和V5顶点后缓存,然后处理V0,再将V0顶点邻接表中的点缓存起来。这个过程用到的缓存结构其实就是一个队列。在遍历某一顶点的邻接表的同时,将邻接表中临接的顶点缓存到队列中,依次处理。
还有就是一个去除重复访问的问题。为每一个已经计算过距离的顶点,设置到达此点距离,并更新其状态由未访问过该顶点到访问过该顶点但未访问过其邻接表。
OKay,说明了这两点我们可以一起看bfs方法的代码了。

// 通过输入结点关键字_searchKey,找到该顶点
// 找到该顶点到图中其它可达顶点的最小距离
void Graph::bfs(long _searchKey,DistenceOfGraph& dis) {
    queue queueCache;
    int distence = 0;
    // 若不存在此关键字,则返回
    VertexNode *node = NULL;
    if (!findNode(_searchKey, &node)) {
        return ;
    }

    // 查找点距离查找点的距离为0
    node->setDistence(0);
    node->setStatus(VISIT_NO_ADJ);
    (dis).insert(pair<long, int>(_searchKey,0));

    // 广度优先遍历图
    while (IsNotNull(node)) {
        // 若顶点的邻接表已经被访问,则继续访问下一个顶点
        if ( JudgeNodeStatusVisitAdj(node) ) {
            // queue next
            QueueNext();
            continue;
        }

        // 遍历顶点得全部临界顶点
        Edge * edgeTmp = node->getEdgeHeader();
        while (IsNotNull(edgeTmp)) {
            // 获取顶点的临接点
            VertexNode * nodeTmp = edgeTmp->getDes();

            // 若该顶点未访问过,则将此点加入队列,并设置到此点的距离
            if (JudgeNodeStatusNoVisit(nodeTmp)) {
                queueCache.push(nodeTmp);
                distence = edgeTmp->getOri()->getDistence() + edgeTmp->getWeight();
                (dis).insert(pair<long, int>(GetDesNodeKey((edgeTmp)),distence));
                edgeTmp->getDes()->setDistence(distence);
                nodeTmp->setStatus(VISIT_NO_ADJ);

            }

            EdgeNext(edgeTmp);
        }

        QueueNext();
    }



    return;
}

相信到这里,bfs算法已经很清晰了,那么我在最后给出完整的实现代码和单元测试程序。

完整代码

头文件:

#include 


#ifndef _GRAPH_BFS_H
#define _GRAPH_BFS_H

#include 
#include 

#define IsNotNull(a) (a)
#define IsNull(a) !(a)
#define JudgeNodeStatusVisitAdj(a) (a)->getStatus() == VISIT_ADJ
#define JudgeNodeStatusNoVisit(a) (a)->getStatus() == NO_VISIT
#define GetDistence(_edge) (_edge)->getOri()->getDistence() + (_edge)->getWeight()
#define GetOriNodeKey(_edge) (_edge)->getOri()->getKey()
#define GetDesNodeKey(_edge) (_edge)->getDes()->getKey()
#define EdgeNext(_edge) if (!node->nextEdge(&(_edge))) { break;}
#define QueueNext() if (queueCache.empty()) {break;} node = queueCache.front();queueCache.pop()
#define DistenceInsert(_dis,_edge) \
        DistenceOfGraph::iterator it = (_dis).find(GetOriNodeKey((_edge))); \
        if (it == (_dis).end() || GetDistence((_edge)) < it->second) { \
            cout<<"insert key = "<"distence = "<long, int>(GetOriNodeKey((_edge)),GetDistence((_edge)))); \
        }


typedef class VertexNode _VertexNode;
typedef enum  _VISIT_STATUS{
    NO_VISIT,       /* 未访问过此点 */
    VISIT_NO_ADJ,   /* 访问过此点,但未访问过其邻接表 */
    VISIT_ADJ,      /* 访问过此点和其邻接表 */
} VISIT_STATUS;
typedef std::map<long, int>DistenceOfGraph;

// 边
class Edge {

    int weight;         /* 边的权值 */
    _VertexNode * ori;  /* 弧的起点*/
    _VertexNode * des;  /* 弧的终点*/

public:

    Edge(int _weight,_VertexNode *_ori,_VertexNode *_des): weight(_weight),ori(_ori),des(_des){};
    ~Edge() {};

    // 获取弧的起点
    _VertexNode* getOri() {return ori;};

    // 获取弧的终点
    _VertexNode* getDes() {return des;};

    // 获取弧的权值
    int getWeight() {return weight;};

};

//顶点
class VertexNode {
    long key;               /* 顶点的关键字,用于标识该顶点*/
    int value;              /* 顶点附着的信息 */
    std::list edgeAdj;    /* 顶点的邻接表 */
    VISIT_STATUS status;    /* 标识此顶点的访问状态 */
    int distence;           /* 此顶点与搜索顶点的距离 */
    std::list::iterator iter_edge;/* 用于遍历邻接表 */
public:

    VertexNode(long _key,int _value) : key(_key), \
        value(_value) { distence = 0xFFFF;status = NO_VISIT; };
    ~VertexNode() {};

    // 获取该顶点的关键字
    long getKey() { return key;};

    // 设置此顶点的关键字
    void setKey(long _key) { key = _key;};

    // 在顶点上添加一条弧
    void addEdge(Edge* _edge);

    // 在顶点上删除一条弧
    void removeEdge(Edge* _edge);

    // 获取邻接表第一条边
    Edge * getEdgeHeader() {
        iter_edge = edgeAdj.begin();
        return *iter_edge;
    };

    bool nextEdge(Edge **_edge) {
        iter_edge++;
        if (iter_edge != edgeAdj.end()) {
            *_edge = *iter_edge;
            return true;
        }
        return false;
    };

    // 获取顶点的访问状态
    VISIT_STATUS getStatus() { return status;};

    // 设置顶点的访问状态
    void setStatus(VISIT_STATUS _status) {status = _status;};

    // 获取出发点到此点的最小距离
    int getDistence() {return distence;};

    // 设置出发点到此点的最小距离
    void setDistence(int _distence) {distence = _distence;};

};

class Graph {
    // 顶点集合
    std::list vertexSet;

public:

    Graph() {};
    ~Graph() {};

    /* 功能找出key == _searchKey的顶点到图中所有其它顶点的距离,注:不可达的顶点距离为0xFFFF
     * Input  查找关键字
     * Output map&dis 输出结果,long 为关键字,int 为输出结果
     */
    void bfs(long _searchKey,DistenceOfGraph &dis);

    // 打印图中的全部顶点
    void printNode();

    // 打印顶点键值为key的边
    void printEdge(long key);

    // 寻找顶点关键字为key的顶点,若找到由_node变量返回
    bool findNode(long key,VertexNode **_node);

    // 向图中增加一个顶点
    VertexNode* addNode(long key,int value);

    // 向图中增加一条边
    void addEdge(long keyOri,long keyDes,int weight);

};

// 此测试程序测试上面Graph中的bfs方法
int testGraphBfs();
#endif

代码实现的源文件:

//
//  graph_bfs.cpp
//  100-alg-tests
//
//  Created by bobkentt on 15-8-8.
//  Copyright (c) 2015年 kedong. All rights reserved.
//
#include 
#include 
#include 
#include 
#include 
#include "graph_bfs.h"

using namespace std;

#define _DEBUG_ 1

void VertexNode::addEdge(Edge* _edge) {
    if (IsNull(_edge)) {
        cout<<"add an NULL edge."<exit(-1);
    }

#ifdef _DEBUG_
    cout<<"addEdge ori's key = "<<_edge->getOri()->getKey();
    cout<<",des's key ="<<_edge->getDes()->getKey()<#endif

    edgeAdj.push_back(_edge);

    return ;
}

bool Graph::findNode(long key,VertexNode **_node) {
    list &VS = vertexSet;
    list::iterator end = VS.end();
    list::iterator it;
    VertexNode *node = NULL;

    // 遍历顶点集,找到开始结点A
    for (it = VS.begin(); it != end; it++) {
        node = *it;
        if (node->getKey() == key)
        {
            break;
        }
    }
    if (it == end) {
        // 结点中
        cout<<"graph::isNodeExist cannot find key = "<"in graph."<return false;
    }
    *_node = node;
    return true;
}

VertexNode* Graph::addNode(long key,int value) {
    VertexNode * node = new VertexNode(key,value);

    vertexSet.push_back(node);

    return node;
}


void Graph::addEdge(long keyOri,long keyDes,int weight) {
    VertexNode *ori = NULL;
    VertexNode *des = NULL;

    // 在图中查找这两个顶点
    if (!findNode(keyOri, &ori) || !findNode(keyDes, &des)) {
        cout<<"Graph::addEdge failed:未找到该顶点"<exit(-1);
    }

    // 创建此弧
    Edge * edge = new Edge(weight,ori,des);

    // 在图中弧的起点的邻接表中,添加此弧
    ori->addEdge(edge);

    return ;
}

// 通过输入结点关键字_searchKey,找到该顶点
// 找到该顶点到图中其它可达顶点的最小距离
void Graph::bfs(long _searchKey,DistenceOfGraph& dis) {
    queue queueCache;
    int distence = 0;
    // 若不存在此关键字,则返回
    VertexNode *node = NULL;
    if (!findNode(_searchKey, &node)) {
        return ;
    }

    // 查找点距离查找点的距离为0
    node->setDistence(0);
    node->setStatus(VISIT_NO_ADJ);
    (dis).insert(pair<long, int>(_searchKey,0));

    // 广度优先遍历图
    while (IsNotNull(node)) {
        // 若顶点的邻接表已经被访问,则继续访问下一个顶点
        if ( JudgeNodeStatusVisitAdj(node) ) {
            // queue next
            QueueNext();
            continue;
        }

        // 遍历顶点得全部临界顶点
        Edge * edgeTmp = node->getEdgeHeader();
        while (IsNotNull(edgeTmp)) {
            // 获取顶点的临接点
            VertexNode * nodeTmp = edgeTmp->getDes();

            // 若该顶点未访问过,则将此点加入队列,并设置到此点的距离
            if (JudgeNodeStatusNoVisit(nodeTmp)) {
                queueCache.push(nodeTmp);
                distence = edgeTmp->getOri()->getDistence() + edgeTmp->getWeight();
                (dis).insert(pair<long, int>(GetDesNodeKey((edgeTmp)),distence));
                edgeTmp->getDes()->setDistence(distence);
                nodeTmp->setStatus(VISIT_NO_ADJ);

            }

            EdgeNext(edgeTmp);
        }

        QueueNext();
    }



    return;
}

void Graph::printNode() {
    list::iterator it = vertexSet.begin();
    cout<<"The nodes of Graph's keys = ";
    for (; it != vertexSet.end(); it++) {
        VertexNode * node = *it;
        cout<getKey()<<" ";
    }
    cout<return ;
}

int testGraphBfs() {
    Graph G;

    // 画出图中所有的点
    for (int i = 0; i <= 6; i++) {
        G.addNode(i, i);
    }

    G.printNode();

    // 画出图中所有的边
    G.addEdge(0, 1, 1);/* V0-->V1 */
    G.addEdge(0, 3, 1);/* V0-->V3 */
    G.addEdge(1, 3, 1);/* V1-->V3 */
    G.addEdge(1, 4, 1);/* V1-->V4 */
    G.addEdge(2, 0, 1);/* V2-->V0 */
    G.addEdge(2, 5, 1);/* V2-->V5 */
    G.addEdge(3, 2, 1);/* V3-->V2 */
    G.addEdge(3, 4, 1);/* V3-->V4 */
    G.addEdge(3, 5, 1);/* V3-->V5 */
    G.addEdge(3, 6, 1);/* V3-->V6 */
    G.addEdge(4, 6, 1);/* V4-->V6 */
    G.addEdge(6, 5, 1);/* V6-->V5 */

    // 选择V3作为源点,求V3到其它所有的点的距离
    DistenceOfGraph dis;
    G.bfs(2, dis);

    // debug "for each dis"
    map<long,int>::iterator iter = dis.begin();
    for (; iter != dis.end(); iter++) {
        cout<<"key = "<first<<", dis = "<second<return 0;
}

int main(int argc, const char * argv[]) {
    testGraphBfs();
    return 0;
}

Okay,今天就写到这里了,12点半了,困困哒,我要睡了,明天继续,这周把DFS、Dijkstra、Prim算法都实现一遍。
ps:这篇博文写的匆忙,有哪些不好,或者不对的地方请朋友们指正。

你可能感兴趣的:(ALG)