算法概述
圖的遍歷是指訪問圖中每個節點一次。圖的遍歷方式主要有兩種,一種是深度優先,即能走多遠就先走多遠的遍歷方式,這就意味着,對於每個節點的遍歷完後,下一個訪問的節點應該是他的鄰接點,而不是兄弟節點。另一種方式是深度優先的方式,這是一種分層遍歷,對於沒一個節點訪問完後,就訪問它的兄弟節點,而不是優先考慮鄰接頂點。深度優先算法使用遞歸實現比較直觀,而廣度優先遍歷則需要一個隊列輔助,和分層遍歷一棵二叉樹的算法是一樣一樣的。
算法實現
#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;
}
程序運行結果示例:
輸入圖
邊的文件存儲形式
頂點的文件存儲形式
運行結果:
總結
理解裏遍歷方式的運行原理之後編碼還是比較容易的,但是實現寬度優先遍歷的時候,把棧拿掉也可以遍歷所有的節點,但是沒有分層訪問的效果,那麼也就不是寬度優先遍歷所定義的運行方式了。另外在實現過程中,我總結了一下仿函數至少具有的一下優點:
- 形式更簡單,所以寫出來的接口更容易閱讀
- 可以擁有自己的狀態,並帶着狀態傳遞,還可以在運行過程中改變狀態
- 兼容函數指針,可以將函數指針當做仿函數作爲接口實參
以上三點都在本博客的測試代碼中有所體現。