广度优先遍历

来源:互联网 发布:linux远程拷贝文件命令 编辑:程序博客网 时间:2024/05/15 21:54

--------------------siwuxie095

  

  

  

  

  

  

  

  

广度优先遍历

  

  

广度优先遍历,也被称为层序优先遍历,从遍历的起始点

开始,一层一层地向下推

  

  

看如下实例:

  

  

  

  

这张图的邻接表如下:

  

  

  

对于广度优先遍历,需要使用队列作为辅助数据结构

  

  

  

  

  

0开始进行广度优先遍历为例(注意对照邻接表):

  

首先将 0入队

  

接着将队列头,即 0出队,作为遍历对象,和 0 相邻的顶点是 12

56,将 1256入队

  

接着将队列头,即 1出队,作为遍历对象,而和 1 相邻的顶点只有 0

0曾入队,不用管

  

接着将队列头,即 2出队,作为遍历对象,而和 2 相邻的顶点只有 0

0曾入队,不用管

  

接着将队列头,即 5出队,作为遍历对象,和 5 相邻的顶点是 03

4,其中 0曾入队,不用管,将 34入队

  

接着将队列头,即 6出队,作为遍历对象,和 6 相邻的顶点是 04

04 都曾入队,不用管

  

接着将队列头,即 3出队,作为遍历对象,和 3 相邻的顶点是 45

45 都曾入队,不用管

  

接着将队列头,即 4出队,作为遍历对象,和 4 相邻的顶点是 35

6,且 3、5、6都曾入队,不用管

  

  

  

至此,队列为空,全部顶点遍历完毕

  

  

注意:只要加入到队列的顶点,就进行visited 标记,因为一旦入队,

迟早都会被遍历

  

  

  

  

  

  

  

最短路径

  

  

在广度优先遍历中,先遍历到的顶点距离起始点的距离一定是小于等于

后遍历到的顶点的

  

  

如果把这个距离存储下来,就相当于求出了每一个顶点到起始点的最短

距离

  

  

更进一步,遍历到某顶点的同时,如果把从哪个顶点遍历到了当前顶点,

当前顶点的上一个顶点,也存储下来,就能把最短距离背后的最短路

给求出来

  

「广度优先遍历求出了无权图的最短路径」

  

  

  

  

  

程序:

  

SparseGraph.h:

  

#ifndef SPARSEGRAPH_H

#define SPARSEGRAPH_H

  

#include <iostream>

#include <vector>

#include <cassert>

using namespace std;

  

  

  

//稀疏图 -邻接表

class SparseGraph

{

  

private:

  

int n, m;//n m分别表示顶点数和边数

bool directed;//directed表示是有向图还是无向图

vector<vector<int>> g;//g[i]里存储的就是和顶点i相邻的所有顶点

  

public:

  

SparseGraph(int n,bool directed)

{

//初始化时,有n个顶点,0条边

this->n = n;

this->m =0;

this->directed = directed;

//g[i]初始化为空的vector

for (int i =0; i < n; i++)

{

g.push_back(vector<int>());

}

}

  

  

~SparseGraph()

{

  

}

  

  

int V(){return n; }

int E(){return m; }

  

  

//在顶点v和顶点w之间建立一条边

void addEdge(int v,int w)

{

  

assert(v >=0 && v < n);

assert(w >=0 && w < n);

  

g[v].push_back(w);

//1)顶点v不等于顶点w,即不是自环边

//2)且不是有向图,即是无向图

if (v != w && !directed)

{

g[w].push_back(v);

}

  

m++;

}

  

  

//hasEdge()判断顶点v和顶点w之间是否有边

//hasEdge()的时间复杂度:O(n)

bool hasEdge(int v,int w)

{

  

assert(v >=0 && v < n);

assert(w >=0 && w < n);

  

for (int i =0; i < g[v].size(); i++)

{

if (g[v][i] == w)

{

return true;

}

}

  

return false;

}

  

  

void show()

{

  

for (int i =0; i < n; i++)

{

cout <<"vertex " << i << ":\t";

for (int j =0; j < g[i].size(); j++)

{

cout << g[i][j] <<"\t";

}

cout << endl;

}

}

  

  

  

//相邻点迭代器(相邻,即 adjacent

//

//使用迭代器可以隐藏迭代的过程,按照一定的

//顺序访问一个容器中的所有元素

class adjIterator

{

private:

  

SparseGraph &G;//图的引用,即要迭代的图

int v;//顶点v

int index;//相邻顶点的索引

  

public:

  

adjIterator(SparseGraph &graph,int v) : G(graph)

{

this->v = v;

this->index =0;

}

  

  

//要迭代的第一个元素

int begin()

{

//因为有可能多次调用begin()

//所以显式的将index设置为0

index =0;

//如果g[v]size()不为0

if (G.g[v].size())

{

return G.g[v][index];

}

  

return -1;

}

  

  

//要迭代的下一个元素

int next()

{

index++;

if (index < G.g[v].size())

{

return G.g[v][index];

}

  

return -1;

}

  

  

//判断迭代是否终止

bool end()

{

return index >= G.g[v].size();

}

};

};

  

  

//事实上,平行边的问题,就是邻接表的一个缺点

//

//如果要在addEdge()中判断hasEdge(),因为hasEdge()O(n)的复

//杂度,那么addEdge()也就变成O(n)的复杂度了

//

//由于在使用邻接表表示稀疏图时,取消平行边(即addEdge()

//中加上hasEdge()),相应的成本比较高

//

//所以,通常情况下,在addEdge()函数中就先不管平行边的问题,

//也就是允许有平行边。如果真的要让图中没有平行边,就在所有

//边都添加进来之后,再进行一次综合的处理,将平行边删除掉

  

#endif

  

  

  

DenseGraph.h:

  

#ifndef DENSEGRAPH_H

#define DENSEGRAPH_H

  

#include <iostream>

#include <vector>

#include <cassert>

using namespace std;

  

  

  

//稠密图 -邻接矩阵

class DenseGraph

{

  

private:

  

int n, m;//n m分别表示顶点数和边数

bool directed;//directed表示是有向图还是无向图

vector<vector<bool>> g;//二维矩阵,存放布尔值,表示是否有边

  

public:

  

DenseGraph(int n,bool directed)

{

//初始化时,有n个顶点,0条边

this->n = n;

this->m =0;

this->directed = directed;

//二维矩阵:nn列,全部初始化为false

for (int i =0; i < n; i++)

{

g.push_back(vector<bool>(n,false));

}

}

  

  

~DenseGraph()

{

  

}

  

  

int V(){return n; }

int E(){return m; }

  

  

//在顶点v和顶点w之间建立一条边

void addEdge(int v,int w)

{

  

assert(v >=0 && v < n);

assert(w >=0 && w < n);

  

//如果顶点v和顶点w之间已经存在一条边,

//则直接返回,即排除了平行边

if (hasEdge(v, w))

{

return;

}

  

g[v][w] =true;

//如果是无向图,则g[w][v]处也设为true(无向图沿主对角线对称)

if (!directed)

{

g[w][v] =true;

}

  

m++;

}

  

  

//hasEdge()判断顶点v和顶点w之间是否有边

//hasEdge()的时间复杂度:O(1)

bool hasEdge(int v,int w)

{

assert(v >=0 && v < n);

assert(w >=0 && w < n);

return g[v][w];

}

  

  

void show()

{

  

for (int i =0; i < n; i++)

{

for (int j =0; j < n; j++)

{

cout << g[i][j] <<"\t";

}

cout << endl;

}

}

  

  

  

//相邻点迭代器(相邻,即 adjacent

class adjIterator

{

private:

  

DenseGraph &G;//图的引用,即要迭代的图

int v;//顶点v

int index;//相邻顶点的索引

  

public:

  

adjIterator(DenseGraph &graph,int v) : G(graph)

{

this->v = v;

this->index = -1;

}

  

  

//要迭代的第一个元素

int begin()

{

//找第一个为true的元素,即为要迭代的第一个元素

index = -1;

return next();

}

  

  

//要迭代的下一个元素

int next()

{

for (index +=1; index < G.V(); index++)

{

if (G.g[v][index])

{

return index;

}

}

  

return -1;

}

  

  

//判断迭代是否终止

bool end()

{

return index >= G.V();

}

};

};

  

  

//addEdge()函数隐含着:当使用邻接矩阵表示稠密图时,已经

//不自觉的将平行边给去掉了,即在添加边时,如果发现已经

//存在该边,就不做任何操作,直接返回即可

//

//事实上,这也是使用邻接矩阵的一个优势可以非常方便的处理

//平行边的问题

//

//另外,由于使用的是邻接矩阵,可以非常快速的用O(1)的方式,

//来判断顶点v和顶点w之间是否有边

  

#endif

  

  

  

ReadGraph.h:

  

#ifndef READGRAPH_H

#define READGRAPH_H

  

#include <iostream>

#include <string>

#include <fstream>

#include <sstream>

#include <cassert>

using namespace std;

  

  

  

//从文件中读取图的测试用例

template <typename Graph>

class ReadGraph

{

  

public:

  

ReadGraph(Graph &graph,const string &filename)

{

  

ifstream file(filename);

string line;//一行一行的读取

int V, E;

  

assert(file.is_open());

  

//读取file中的第一行到line

assert(getline(file, line));

//将字符串line放在stringstream

stringstream ss(line);

//通过stringstream解析出整型变量:顶点数和边数

ss >> V >> E;

  

//确保文件里的顶点数和图的构造函数中传入的顶点数一致

assert(V == graph.V());

  

//读取file中的其它行

for (int i =0; i < E; i++)

{

  

assert(getline(file, line));

stringstream ss(line);

  

int a, b;

ss >> a >> b;

assert(a >=0 && a < V);

assert(b >=0 && b < V);

graph.addEdge(a, b);

}

}

  

};

  

  

#endif

  

  

  

ShortestPath.h:

  

#ifndef SHORTESTPATH_H

#define SHORTESTPATH_H

  

#include <vector>

#include <queue>

#include <stack>

#include <iostream>

#include <cassert>

using namespace std;

  

  

  

//通过广度优先遍历求最短路径(其中含有广度优先遍历的实现)

template <typename Graph>

class ShortestPath

{

  

private:

  

Graph &G;//图的引用,即要进行广度优先遍历的图

int s;//从顶点 s到任意其它顶点的最短路径,s source

bool *visited;//每个顶点是否被访问过(是否被遍历过)

int *from;//每访问一个顶点,就存储一下是从哪个顶点遍历到了当前顶点

int *ord;//从顶点 s到每一个顶点具体的最短距离是多少,ord order

  

public:

  

ShortestPath(Graph &graph,int s) :G(graph)

{

  

//算法初始化

assert(s >=0 && s < graph.V());

  

visited =newbool[graph.V()];

from =newint[graph.V()];

ord =newint[graph.V()];

for (int i =0; i < graph.V(); i++)

{

visited[i] =false;

from[i] = -1;

ord[i] = -1;

}

this->s = s;

  

//声明一个队列作为辅助数据结构

queue<int> q;

  

//无向图最短路径算法

q.push(s);

visited[s] =true;

ord[s] =0;

while (!q.empty())

{

//获取队列头代表的顶点,并出队

int v = q.front();

q.pop();

  

//注意:声明迭代器时,前面还要加 typename,表明 adjIterator

// Graph 中的类型,而不是成员变量

typename Graph::adjIterator adj(G, v);

//遍历队列头所代表顶点的所有相邻顶点

for (int i = adj.begin(); !adj.end(); i = adj.next())

{

//如果当前顶点不曾入队,就入队,同时维护相关信息

if (!visited[i])

{

q.push(i);

visited[i] =true;

from[i] = v;

ord[i] = ord[v] +1;

}

}

}

  

}

  

  

~ShortestPath()

{

  

delete []visited;

delete []from;

delete []ord;

}

  

  

//从顶点s到顶点w是否有路:如果visited[w]true

//表明从顶点s通过BFS访问到了顶点w,即有路

bool hasPath(int w)

{

assert(w >=0 && w < G.V());

return visited[w];

}

  

  

//找到从顶点s到顶点w的路径:通过from数组从顶点w倒推回去,

//并存储在栈中,最后再从栈中转存到向量中

void path(int w, vector<int> &vec)

{

  

assert(w >=0 && w < G.V());

  

stack<int> s;

  

int p = w;

//直到倒推到源顶点,它的from值为-1,即 from[s] = -1

while (p != -1)

{

s.push(p);

p = from[p];

}

  

//为了安全起见,先将向量vector清空

vec.clear();

//只要栈不为空,就将栈顶元素放入向量中,并出栈

while (!s.empty())

{

vec.push_back(s.top());

s.pop();

}

}

  

  

//打印从顶点s到顶点w的最短路径

void showPath(int w)

{

  

assert(w >=0 && w < G.V());

  

vector<int> vec;

path(w, vec);

for (int i =0; i < vec.size(); i++)

{

cout << vec[i];

if (i == vec.size() -1)

{

cout << endl;

}

else

{

cout <<" -> ";

}

}

}

  

  

//从顶点s到顶点w的最短路径的长度,即最短距离

int length(int w)

{

assert(w >=0 && w < G.V());

return ord[w];

}

};

  

  

#endif

  

  

  

main.cpp:

  

#include"SparseGraph.h"

#include"DenseGraph.h"

#include"ReadGraph.h"

#include"ShortestPath.h"

#include <iostream>

using namespace std;

  

  

  

int main()

{

  

string filename ="testG2.txt";

//稀疏图

SparseGraph g = SparseGraph(7,false);

ReadGraph<SparseGraph> readGraph(g, filename);

g.show();

cout << endl;

  

ShortestPath<SparseGraph> bfs(g,0);

cout <<"BFS : ";

bfs.showPath(6);

  

system("pause");

return0;

}

  

  

//1)从一个点到另外一点,最短路径可能有多条,最后得到的那条最短路

//径,取决于图中的遍历顺序

//

//

//2)图的广度优先遍历的复杂度(和深度优先遍历的复杂度一致):

//

//稀疏图 - 邻接表:O(V+E),通常情况下,E会比V大,所以也可以说是 O(E)

//

//稠密图 - 邻接矩阵:O(V^2)

  

  

运行一览:

  

  

  

  

其中,testG2.txt 的内容如下:

  

  

  

该文件可以分成两个部分:

  

1)第一行:两个数字分别代表顶点数和边数

  

2)其它行:每一行的两个数字表示一条边

  

  

  

  

  

  

  

  

  

  

  

【made by siwuxie095】