每日一题31:图的遍历

来源:互联网 发布:淘宝店可以用手机开么 编辑:程序博客网 时间:2024/06/08 07:59

算法概述

图的遍历是指访问图中每个节点一次。图的遍历方式主要有两种,一种是深度优先,即能走多远就先走多远的遍历方式,这就意味着,对于每个节点的遍历完后,下一个访问的节点应该是他的邻接点,而不是兄弟节点。另一种方式是深度优先的方式,这是一种分层遍历,对于没一个节点访问完后,就访问它的兄弟节点,而不是优先考虑邻接顶点。深度优先算法使用递归实现比较直观,而广度优先遍历则需要一个队列辅助,和分层遍历一棵二叉树的算法是一样一样的。

算法实现

#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;}

程序运行结果示例:
这里写图片描述
输入图
这里写图片描述
边的文件存储形式
这里写图片描述
顶点的文件存储形式

运行结果:
这里写图片描述

总结

理解里遍历方式的运行原理之后编码还是比较容易的,但是实现宽度优先遍历的时候,把栈拿掉也可以遍历所有的节点,但是没有分层访问的效果,那么也就不是宽度优先遍历所定义的运行方式了。另外在实现过程中,我总结了一下仿函数至少具有的一下优点:

  1. 形式更简单,所以写出来的接口更容易阅读
  2. 可以拥有自己的状态,并带着状态传递,还可以在运行过程中改变状态
  3. 兼容函数指针,可以将函数指针当做仿函数作为接口实参

以上三点都在本博客的测试代码中有所体现。

1 0
原创粉丝点击