图的遍历

图的遍历

图的作用

  • 图的应用范围: 在一个问题中,如果存在多个节点,并且节点之间并不是类似于树的1:N的关系.那么我们都可以考虑用图来解决问题.
  • 图的表示方法:
    • 根据图的规模或者节点与节点之间的对应关系我们可以用不同的方式来表示图:邻接矩阵或者邻接表.

    • 邻接矩阵 :
      我们想象这样一个问题,我们有N个节点,这N个节点互相连接产生了n条边(N != n).此时我们应该如何在计算机中表示出这N个节点和它们的连接关系呢?
      这个时候我们就可以用一个N*N的二维数组来表示它们之间的关系,用一个长度为N的数组(记做数组A)来表示节点.数组A中节点的序号与二维数组中一致.此时我们就可以将这个二维数组称为邻接矩阵.

    • 邻接表 :
      还是邻接矩阵中的那个问题,我们能否换一种方法实现图的结构呢?答案是肯定的.
      我们可以建立一个长度为N的一维数组,其中存放的是一个链表结构,链表的初始节点就是我们图中的节点,而后续的节点则是与这个节点发生关联的其他节点.这样的一个数组就可以被成为邻接表.

    • 邻接表与邻接矩阵的区别 :
      选择图的表示结构的时候主要考虑的是所给数据的情况,如果我们用邻接矩阵来表示这个图,而这个矩阵是稀疏的,那么我们可以修改结构转用邻接表去表示.
      也就是说,如果N >> n,那么此时邻接表就更适合作为图的数据表现形式,因为它对于内存的要求更小.

      在时间方面,邻接矩阵的优点是可以在常量时间内确定一条边是否存在,并且只需要访问一次内存.
      而邻接表则需要遍历整个链表,但它的优点是我们可以快速找到所有相连的节点.

无向图的深度优先搜索

  • 迷宫问题
    在讨论搜索之前我们可以先看一个迷宫问题.假设我们在一个迷宫中行进,想要找到这个迷宫的出口,而此时我们被允许使用粉笔和细绳.那么我们应该怎么做呢?
    在探索迷宫的过程中,我们可以通过粉笔来标记访问过的路口,以避免兜圈子.而我们可以通过细绳来引导我们回到之前的关卡,从而使我们能继续探索上一个关卡中我们发现了但是没有走过的路口.

  • 无向图的搜索
    回到我们的问题上来.在无向图的搜索中我们也可以利用迷宫问题的思路,借助粉笔和细绳来解决问题.而在计算机中,粉笔我们可以用一个存储着bool值的数组(称为C)来代表,每访问一个节点,我们就将其对应序号的数组C中的bool值变为true,这样我们就可以知道这个节点已经被访问过,下次就不需要再访问了.迷宫中的细绳我们可以通过栈来表示,即每次都将关联节点压入栈中,利用栈后入先出的特点,我们就可以完成从某一个节点出发图的深度优先搜索.

  • 例题
    题目链接在这里:Evaluate Division
    将代码贴上:

class Solution {
public:
    typedef pair<string, double> pointAndValue;
    typedef vector<pointAndValue> pathList;
    //通过Map模拟邻接表
    map<string, pathList> pointList;
    //通过Map模拟'粉笔',用来记录节点访问情况
    map<string, bool> pointVisitted;

    //构造邻接表,因为是无向图,所以两个节点都要插入进去
    void initPointList(vector<pair<string, string>> equations, vector<double>& values) {
        pointList.clear();
        for (size_t i = 0; i < equations.size(); i++) {
            if(pointList.find(equations[i].first) == pointList.end()) {
                pathList tempa;
                pointList[equations[i].first] = tempa;
            }
            pointVisitted[equations[i].first] = false;
            pointList[equations[i].first].push_back(pointAndValue(equations[i].second, values[i]));

            if(pointList.find(equations[i].second) == pointList.end()) {
                pathList tempb;
                pointList[equations[i].second] = tempb;
            }
            pointVisitted[equations[i].second] = false;
            pointList[equations[i].second].push_back(pointAndValue(equations[i].first, 1 / values[i]));
        }
    }
    //每次查询过后都要重置节点访问情况.
    void initVisitted() {
        for(auto it = pointVisitted.begin(); it != pointVisitted.end(); it++) {
            it->second = false;
        }
    }

    //通过递归的方式模拟栈的行为
    //如果找到了目标节点则返回值,并且将值递归返回并层层计算.
    //如果没有找到目标节点并且已经没有可走的路径则返回-1
    double graphBFS(string now, string end) {
        if(pointList.find(now) == pointList.end() || pointVisitted[now]) {
            return -1.0;
        }

        pointVisitted[now] = true;

        for (size_t i = 0; i < pointList[now].size(); i++) {
            if(end == pointList[now][i].first) {
                return pointList[now][i].second;
            } else {
                double temp = graphBFS(pointList[now][i].first, end);
                if(temp != -1.0){
                    return pointList[now][i].second * temp;
                }
            }
        }
        return -1.0;
    }
    //主函数
    vector<double> calcEquation(vector<pair<string, string>> equations, vector<double>& values, vector<pair<string, string>> queries) {
        vector<double> results;
        initPointList(equations, values);
        for (size_t i = 0; i < queries.size(); i++) {
            results.push_back(graphBFS(queries[i].first,queries[i].second));
            initVisitted();
        }
        return results;
    }
};
posted @ 2017-03-03 20:53  XLLL  阅读(208)  评论(0编辑  收藏  举报