每日一题31:图的遍历

算法概述

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

算法实现

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

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

时间: 2024-08-28 12:25:51

每日一题31:图的遍历的相关文章

老男孩教育每日一题-2017年5月17日-使用三剑客进行变化格式

1.题目 原始数据: 17/Apr/2015:09:29:24 +0800 17/Apr/2015:09:30:26 +0800 17/Apr/2015:09:31:56 +0800 18/Apr/2015:09:34:12 +0800 18/Apr/2015:09:35:23 +0800 19/Apr/2015:09:23:34 +0800 19/Apr/2015:09:22:21 +0800 20/Apr/2015:09:45:22 +0800 期望结果: 2015-04-17 09:29:

&#8203;老男孩教育每日一题-第99天-服务器上有哪些常用的操作系统,各有什么特点?

Linux发行版版本选择 Linux桌面系统 Ubuntu(乌班图)(开发人员开发平台) 服务器端linux系统 推荐CentOS系统,有钱任性RedHat Enterprise Linux 如果对安全要求很高 Debian或FreeBSD 使用数据库高级服务或电子邮件网络用户 SUSE(德国多) 想新技术,新功能 Fedora,相当于是RedHatEnterprise Linux的测试版 新功能新特性会先放在 Fedora里面 中文Linux 红旗linux,麒麟linux 注意: Redha

C语言每日一题之No.9

再做决定之前,我还是做好自己该做的.我不希望几年后会悔恨自己为什么在最该努力的时候不愿意吃苦.尊敬的女王陛下,请接题: 一.题目:有已按升序排好顺序的字符串a,编写程序将字符串s中的每个字符按升序的规则插到字符串a中,最后输出"abdefghjkmnptwy". 二.思路:既然是已经排好序的,就用二分法查找的思想 将字符串s中的每个字符依次作为key拿来和字符串a做比较并且插入 三.程序 1 #include <stdio.h> 2 #include <string.

C语言每日一题之No.3

题目:从键盘输入一个字符串,按照字符顺序从小到大进行排序,并要求删除重复字符.如输入"ad2f3adjfeainzzzv",则输出"23adefijnvz" 思路:先定义一个字符数组用来存储字符串 讲数字转化成字符(ASCII) 排序 进行遍历,删除重复字符 程序: C语言每日一题之No.3,布布扣,bubuko.com

C语言每日一题之No.1

鉴于在学校弱弱的接触过C,基本上很少编程,C语言基础太薄弱.刚好目前从事的是软件编程,难度可想而知.严重影响工作效率,已无法再拖下去了.为此,痛下决心恶补C语言.此前只停留在看书,光看好像也记不住,C这东西毕竟是练出来的,所以从今天开始,每日一道C语言题目,从题目入手来补知识漏洞.题目比较基础,如不堪入目,还请见谅. 题目:输入三个整数,输出最大的数 思路:定义三个变量用来存储输入的整数 比较三个变量的大小,找到最大的数 定义一个变量存储来存储最大的数 程序: 1 #include <stdio

老男孩教育每日一题-day59-Linux root 密码忘了,如何找回来?

1.开机时手要快按任意键,因为默认时间5s 图1.1 倒数计时 2.grub菜单,只有一个内核,没什么好上下选的,按e键.不过如果你升级了系统或安装了Xen虚拟化后,就会有多个显示了. 图2.1 选择系统内核并进行编辑 3.接下来显示如下,选择第二项,按e键 图3.1 选择编辑的项目 图3.2 只修改第二项-内核相关的启动参数 4.接下来显示如下,在rhgb quiet最后加"空格",然后键入"1"或"s"或"S"或"

第五章 图的遍历(深度遍历,广度遍历,城市地图,最少转机)

深度和广度优先搜索: 单词分解:首先是搜索 深度和广度:是针对图的遍历而言的 图:由顶点和边组成 图的遍历:把图中每一个顶点都访问一次 一: 输入: 5 5(顶点数,边数) 1 2 1 3 1 5 2 4 3 5 输出: 1 2 4 3 5 (按时间戳输出)深度遍历 1 2 3 5 4 (按时间戳输出)广度遍历 1 #include <stdio.h> 2 int map[10][10], book[10], n, m,sum; 3 void dfs(int cur) 4 { 5 int i;

C语言每日一题之No.8

正式面对自己第二天,突然一种强烈的要放弃的冲动,在害怕什么?害怕很难赶上步伐?害怕这样坚持到底是对还是错?估计是今天那个来了,所以身体激素有变化导致情绪起伏比较大比较神经质吧(☆_☆)~矮油,女人每个月总有这么几天的....晚上闺蜜打电话来,共同探讨了作为单身女性身在一线城市的生活,互相安慰互相关心,心里一下子就温暖了许多.总在这个时候,你会觉得,这个冷静的城市里你不是一个人在行走,还有另一颗心牵挂着你.嘿嘿,回来该学习还学习.现在不管坚持是对的还是错的,你都踏上了研发这条不归路,那就一条黑走到

15 图-图的遍历-基于邻接矩阵实现的BFS与DFS算法

算法分析和具体步骤解说直接写在代码注释上了 TvT 没时间了等下还要去洗衣服 就先不赘述了 有不明白的欢迎留言交流!(估计是没人看的了) 直接上代码: 1 #include<stdio.h> 2 #include<queue> 3 #include<iostream> 4 using namespace std; 5 typedef struct{ 6 int Vex[10];//顶点表 7 int Edge[10][10]; 8 int vexnum,arcnum;