每日一題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. 兼容函數指針,可以將函數指針當做仿函數作爲接口實參

以上三點都在本博客的測試代碼中有所體現。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章