码迷,mamicode.com
首页 > 其他好文 > 详细

每日一题31:图的遍历

时间:2015-05-30 12:16:38      阅读:206      评论:0      收藏:0      [点我收藏+]

标签:图算法   深度优先   广度优先   仿函数   

算法概述

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

算法实现

#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. 兼容函数指针,可以将函数指针当做仿函数作为接口实参

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

每日一题31:图的遍历

标签:图算法   深度优先   广度优先   仿函数   

原文地址:http://blog.csdn.net/liao_jian/article/details/46272571

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!