算法概述
图的遍历是指访问图中每个节点一次。图的遍历方式主要有两种,一种是深度优先,即能走多远就先走多远的遍历方式,这就意味着,对于每个节点的遍历完后,下一个访问的节点应该是他的邻接点,而不是兄弟节点。另一种方式是深度优先的方式,这是一种分层遍历,对于没一个节点访问完后,就访问它的兄弟节点,而不是优先考虑邻接顶点。深度优先算法使用递归实现比较直观,而广度优先遍历则需要一个栈辅助,和分层遍历一棵二叉树的算法是一样一样的。
算法实现
#ifndef _GRAPHTRAVERSE_H_
#define _GRAPHTRAVERSE_H_
#include "../include/DirectedWeightGraph.h"
#include <queue>
using namespace MyDataStructure;
using namespace std;
namespace MyTools
{
//遍历的流程是一样的,所以将遍历方式写成一个仿函数,函数指针
//太难看了
template<typename Value, typename Weight, typename Visitor>
struct bfs
{
void operator()(DirectedWeightGraph<Value, Weight>& Graph, int v, Vector<bool>& is_visited,Visitor vf)
{
typedef DirectedWeightGraph<Value, Weight>::VerticePtr VerticePtr;
typedef DirectedWeightGraph<Value, Weight>::EdgePtr EdgePtr;
if (is_visited[v] == true) return;
queue<int> q;
q.push(v);
while (q.empty() != true)
{
int v1 = q.front();
if (is_visited[v1] != true && Graph.IsVerticeContianed(v1))
{
VerticePtr v_ptr = Graph.GetVertice(v1);
vf(v_ptr->value);
is_visited[v1] = true;
EdgePtr e = v_ptr->adj;
while (e != nullptr)
{
if (is_visited[e->dst] != true)
{
q.push(e->dst);
}
e = e->next;
}
}
q.pop();
}
}
};
template<typename Value, typename Weight, typename Visitor>
struct dfs
{
void operator()(DirectedWeightGraph<Value, Weight>& Graph, int v, Vector<bool>& is_visited,Visitor vf)
{
typedef DirectedWeightGraph<Value, Weight>::VerticePtr VerticePtr;
typedef DirectedWeightGraph<Value, Weight>::EdgePtr EdgePtr;
if (Graph.IsVerticeContianed(v) && is_visited[v] != true)
{
VerticePtr v_ptr = Graph.GetVertice(v);
vf(v_ptr->value);
is_visited[v] = true;
EdgePtr e = v_ptr->adj;
while (e != nullptr)
{
if (is_visited[e->dst] != true)
{
operator()(Graph,e->dst, is_visited, vf);
}
e = e->next;
}
}
}
};
//遍历流程,可配置遍历方式、遍历的起点和对每个节点的访问操作
template<typename Value, typename Weight,typename TraverseMethod, typename Visitor>
bool Traverse(DirectedWeightGraph<Value, Weight>& Graph,int start,TraverseMethod tm, Visitor vf)
{
if (Graph.IsVerticeContianed(start) == true)
{
int size = Graph.GetVerticeSize();
Vector<bool> visited(size);
for (int i = 0; i < size; ++i)
{
visited[i] = false;
}
//如果图是由多个连通分量构成的,这个循环确保每个分量
//的入口不会被错过
for (int i = 0; i < size; ++i)
{
//起点可能在存储结构的中间,所以用模运算
//让遍历从中间能走到开头
int v = start++ % size;
if (visited[v] == false)
{
tm(Graph,v, visited, vf);
cout << endl;
}
}
return true;
}
return false;
}
//提供给外部使用的比较方便的接口
template<typename Value, typename Weight,typename Visitor>
bool BFS(DirectedWeightGraph<Value,Weight>& Graph, int start,Visitor vf)
{
bfs<Value, Weight, Visitor> b;
return Traverse(Graph, start, b, vf);
}
template<typename Value, typename Weight,typename Visitor>
bool DFS(DirectedWeightGraph<Value, Weight>& Graph, int start, Visitor vf)
{
dfs<Value, Weight, Visitor> d;
return Traverse(Graph, start, d, vf);
}
}
#endif
测试代码:
// GraphTest.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "../include/DirectedWeightGraph.h"
#include "../Tool/MinGenTree.h"
#include "../Tool/ShortestPath.h"
#include "../Tool/TopologicalSort.h"
#include "../Tool/GraphTraverse.h"
#include <string>
#include <iostream>
#include <fstream>
using namespace MyDataStructure;
using namespace MyTools;
using namespace std;
bool LoadVertice(string filename, Vector<string>& citynames)
{
ifstream input;
citynames.Clear();
input.open(filename);
string city;
int no;
while (input>>no>>city)
{
citynames.PushBack(city);
}
input.close();
return true;
}
bool LoadEdge(string filename, Vector<int>& srcs,Vector<int>& dsts,Vector<float>& weights)
{
ifstream input;
srcs.Clear();
dsts.Clear();
weights.Clear();
input.open(filename);
int src, dst;
float weight;
while (input>>src>>dst>>weight)
{
srcs.PushBack(src);
dsts.PushBack(dst);
weights.PushBack(weight);
}
input.close();
return true;
}
//节点访问函数
void print1(const string& s)
{
cout << "-->" << s;
}
//以仿函数的形式访问节点
struct Vis
{
Vis(string s)
{
this->s = s;
}
void operator()(const string& value)
{
cout <<s + "-->" << value;
}
private:
string s;
};
int _tmain(int argc, _TCHAR* argv[])
{
Vector<int> srcs,dsts;
Vector<string> citynames;
Vector<float> weights;
LoadVertice("vertice - 副本.txt", citynames);
LoadEdge("edge - 副本.txt", srcs, dsts, weights);
DirectedWeightGraph<string, float> WG(citynames, srcs, dsts, weights);
int verticecount = citynames.Size(), edgecount = weights.Size();
cout<<"广度优先:"<<endl;
BFS(WG, 0, Vis("***"));
cout<<"深度优先:"<<endl;
DFS(WG, 0, print1);
return 0;
}
程序运行结果示例:
输入图
边的文件存储形式
顶点的文件存储形式
运行结果:
总结
理解里遍历方式的运行原理之后编码还是比较容易的,但是实现宽度优先遍历的时候,把栈拿掉也可以遍历所有的节点,但是没有分层访问的效果,那么也就不是宽度优先遍历所定义的运行方式了。另外在实现过程中,我总结了一下仿函数至少具有的一下优点:
- 形式更简单,所以写出来的接口更容易阅读
- 可以拥有自己的状态,并带着状态传递,还可以在运行过程中改变状态
- 兼容函数指针,可以将函数指针当做仿函数作为接口实参
以上三点都在本博客的测试代码中有所体现。
时间: 2024-10-28 20:30:55