概述

本篇博客主要内容:

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

广度优先搜索算法(Breadth-First-Search)

广度优先搜索算法(Breadth-First-Search)又被翻译为宽度优先搜索或横向优先搜索,简称BFS。

BFS是一种盲目搜索法。其系统地展开并检查图中的所有顶点。BFS也是最简便的图搜索算法之中的一个,其相似思想被Dijkstra算法和Prim算法採用。

通过一个样例来介绍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 <list>
class Edge {
// ...
}; class VertexNode {
std::list<Edge*> edgeAdj; /* 顶点的邻接表 */
// ...
};
class Graph {
// 顶点集合
std::list<VertexNode*> 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<Edge*> edgeAdj;/* 顶点的邻接表 */
VISIT_STATUS status; /* 标识此顶点的訪问状态 */
int distence; /* 此顶点与搜索顶点的距离 */
std::list<Edge*>::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<VertexNode*> vertexSet; public: Graph() {};
~Graph() {}; /* 功能找出key == _searchKey的顶点到图中所有其他顶点的距离。注:不可达的顶点距离为0xFFFF
* Input <long _searchKey> 查找关键字
* Output map<long, int>&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<VertexNode*> 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 <stdio.h>

#ifndef _GRAPH_BFS_H
#define _GRAPH_BFS_H #include <list>
#include <map> #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 = "<<GetOriNodeKey((_edge))<<"distence = "<<GetDistence((_edge))<<endl;\
(_dis).insert(pair<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<Edge*> edgeAdj; /* 顶点的邻接表 */
VISIT_STATUS status; /* 标识此顶点的訪问状态 */
int distence; /* 此顶点与搜索顶点的距离 */
std::list<Edge*>::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<VertexNode*> vertexSet; public: Graph() {};
~Graph() {}; /* 功能找出key == _searchKey的顶点到图中所有其他顶点的距离。注:不可达的顶点距离为0xFFFF
* Input <long _searchKey> 查找关键字
* Output map<long, int>&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 <iostream>
#include <queue>
#include <map>
#include <vector>
#include <list>
#include "graph_bfs.h" using namespace std; #define _DEBUG_ 1 void VertexNode::addEdge(Edge* _edge) {
if (IsNull(_edge)) {
cout<<"add an NULL edge."<<endl;
exit(-1);
} #ifdef _DEBUG_
cout<<"addEdge ori's key = "<<_edge->getOri()->getKey();
cout<<",des's key ="<<_edge->getDes()->getKey()<<endl;;
#endif edgeAdj.push_back(_edge); return ;
} bool Graph::findNode(long key,VertexNode **_node) {
list<VertexNode*> &VS = vertexSet;
list<VertexNode*>::iterator end = VS.end();
list<VertexNode*>::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 = "<<key<<"in graph."<<endl;
_node = NULL;
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:未找到该顶点"<<endl; exit(-1);
} // 创建此弧
Edge * edge = new Edge(weight,ori,des); // 在图中弧的起点的邻接表中。加入此弧
ori->addEdge(edge); return ;
} // 通过输入结点关键字_searchKey,找到该顶点
// 找到该顶点到图中其他可达顶点的最小距离
void Graph::bfs(long _searchKey,DistenceOfGraph& dis) {
queue<VertexNode*> 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<VertexNode*>::iterator it = vertexSet.begin();
cout<<"The nodes of Graph's keys = ";
for (; it != vertexSet.end(); it++) {
VertexNode * node = *it;
cout<<node->getKey()<<" ";
}
cout<<endl;
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 = "<<iter->first<<", dis = "<<iter->second<<endl;
} return 0;
} int main(int argc, const char * argv[]) {
testGraphBfs();
return 0;
}

Okay,今天就写到这里了。12点半了,困困哒。我要睡了,明天继续,这周把DFS、Dijkstra、Prim算法都实现一遍。

ps:这篇博文写的匆忙,有哪些不好,或者不正确的地方请朋友们指正。

图论-BFS解无权有向图最短路径距离的更多相关文章

  1. 图论 Make Unique:有向图和无向图的一些算法

    计算机科学入门资料之一的<算法与数据结构-C语言版>,覆盖了基础算法的几乎所有分支,其中的一个典型分支为图理论. 一个简介:图论基础-图数据结构基础 一个简洁的博客:图论基础,简列一本书 ...

  2. 《算法导论》读书笔记之图论算法—Dijkstra 算法求最短路径

    自从打ACM以来也算是用Dijkstra算法来求最短路径了好久,现在就写一篇博客来介绍一下这个算法吧 :) Dijkstra(迪杰斯特拉)算法是典型的最短路径路由算法,用于计算一个节点到其他所有节点的 ...

  3. 广度优先搜索(BFS)——迷宫的最短路径

    宽度优先搜索按照距开始状态由近到远的顺序进行搜索,因此可以很容易的用来求最短路径,最少操作之类问题的答案. 宽度优先搜索介绍(一篇不错的文章). 题目描述: 给定一个大小为N*M的迷宫.迷宫有通道和墙 ...

  4. BFS求解迷宫的最短路径问题

    题目:给定一个大小为N*M的迷宫,迷宫由通道('.')和墙壁('#')组成,其中通道S表示起点,通道G表示终点,每一步移动可以达到上下左右中不是墙壁的位置.试求出起点到终点的最小步数.(本题假定迷宫是 ...

  5. [算法] Dijkstra算法(带权有向图 最短路径算法)

    一.带权有向图 二.算法原理 1)由于我们的节点是从1-6,所以我们创建的列表或数组都是n+1的长度,index=0的部分不使用,循环范围为1-6(方便计算). 2)循环之前,我们先初始化dis数组和 ...

  6. 图论+回溯解QQ一笔画红包

    [春节整活] QQ的一笔画红包有几个特性: 1.最大为5×5的点阵,所以可以把每个点从左到右,从上到下标为1-25号点 2.每两个点只能存在一条线 3.线可以被盖住(例如连接2-1-3,2-1的线会被 ...

  7. bfs 记录和打印最短路径

    Poj3984 迷宫问题 #include <iostream> #include <algorithm> #include <cstdio> #include & ...

  8. python 解方程 和 python 距离公式实现

    解方程参考:https://zhuanlan.zhihu.com/p/24893371 缺点太慢,最后还是自己算了 距离公式参考:https://www.cnblogs.com/denny402/p/ ...

  9. POJ 3026 Borg Maze(Prim+bfs求各点间距离)

    题目链接:http://poj.org/problem?id=3026 题目大意:在一个y行 x列的迷宫中,有可行走的通路空格’  ‘,不可行走的墙’#’,还有两种英文字母A和S,现在从S出发,要求用 ...

随机推荐

  1. Java中jspf文件的作用

    转自:https://blog.csdn.net/xzmeasy/article/details/75103431 为什么要用jspf文件 写jsp页面时,是不是:css和js引用特别多,而且有些页面 ...

  2. PCB决策引擎:多维决策表转决策树

    准备设计一个PCB使用的决策引擎,需要用到决策表,而单维决策表不能满足业务要求, 这里主要是为了实现:用户编辑的是决策表,实际底层存储的是树结构,树的的各个节点挂上业务决策逻辑. 这里将多维决策表转决 ...

  3. E20170906-mk

    portrait   n. 肖像,肖像画; 模型,标本; 半身雕塑像; 人物描写; orientation  n. 方向,定位,取向,排列方向; 任职培训; (外交等的) 方针[态度]的确定; 环境判 ...

  4. JPA实体关联关系,一对一以及转换器

    现有两张表 room (rid,name,address,floor) room_detail (rid,roomid,type) 需要创建房间实体,但是也要包含type属性 @Data //lamb ...

  5. Java.HttpClient绕过Https证书解决方案一

    方案1 import javax.net.ssl.*; import java.io.*; import java.net.URL; import java.security.KeyManagemen ...

  6. angular的directive指令的link方法

    比如 指令标签 <mylink myLoad="try()"></mylink> link:function(scope,element,attr){ el ...

  7. 汇编程序18:利用and和or指令变换大小写

    assume cs:code,ds:data //and指令使某位变0,or指令使某位变1 data segment db 'BaSic','iNfOrMaTiOn' //db指令:定义字节数据,与d ...

  8. web 应用

    一.web应用 web应用程序是一种可以通过web访问的应用程序,程序 的最大好处是用户很容易访问应用程序,用户只需要有浏览器 即可,不需要安装其他团建,用用程序有两种模式C/S.B/S.C/S是客户 ...

  9. AI:恐怖谷理论的陷阱

    科学人的小品:恐怖谷:娃娃为什么很可怕? 一.恐怖的来源 恐怖的来源:美学概念.思想对安全的认识,映射到美学领域,转化为美和丑.恐怖,是一种精心掩饰的丑陋. 二.桑尼与C3PO 桑尼更接近于人,为什么 ...

  10. Content-Encoding与Content-Type的区别

    RFC 2616 for HTTP 1.1 specifies how web servers must indicate encoding transformations using the Con ...