【数据结构】图的遍历

What is 遍历

访问图中的每一个元素一次,仅仅一次。访问,可以是输出打印,改写啊,这样的,根据ADT使用者的回调函数而定。

图的遍历常用的有2种:深度优先搜索,广度优先搜索。

深度优先搜索(Deepth First Search . DFS)

深度优先搜索和树的先序遍历道理是一样的。

需要考虑以下几点:

1、为了避免重复访问,我们需要用一个  bool类型的访问标记数组(visited flag  array),来标记顶点是否已经被访问。

2、要考虑到 非连通图中的 “孤岛”,他们是孤立的子图,不能通过路径可达,但也要遍历到。

3、和树的遍历一样,有2种方法:递归和非递归。

说到这里,我又要吐槽书了,对!就是严蔚敏的数据结构。将visited访问标记数组定义为全局变量, 实在看不下去了。

我的改进是,用“壳子”函数 ArrayGraph_DFS来创建局部访问标记数组,而真正完成遍历的是ArrayGraph_DFS_traverse,

将visited作为ArrayGraph_DFS_traverse的参数传递,这样递归的各层

函数就能共享这个数组了。细心的同学发现我将ArrayGraph_DFS_traverse声明为static,其作用是隐藏ArrayGraph_DFS_traverse于此源文件。

可以将访问操作定义为一个回调函数,让API使用者决定如何访问。但是我没有这样做,而是硬编码,用printf打印作为访问操作。想写的简单些。

深度优先搜索的递归实现:

#include<stdio.h>
#define MAX_VERTEX  4

typedef char DataType;                 //图中元素的目标数据类型 

typedef struct
{
    DataType vertexArr[MAX_VERTEX];        //顶点元素数组 

    int edgeArr[MAX_VERTEX][MAX_VERTEX];   //边矩阵二维数组 

}ArrayGraph;

void ArrayGraph_init(ArrayGraph *pGraph);
void ArrayGraph_create(ArrayGraph *pGraph);
void ArrayGraph_DFS(ArrayGraph * pGraph,int n);
static void  ArrayGraph_DFS_traverse(ArrayGraph * pGraph,int n,bool*visited);

int main()
{
    ArrayGraph g;
    ArrayGraph_init(&g);       //初始化图
    ArrayGraph_create(&g);     //创建图
    ArrayGraph_DFS(&g,3);       //遍历 ,从索引为3的顶点开始
return 0;
}

//初始化为一个无圈图 ,也就是边矩阵中,主对角线元素都是0
void ArrayGraph_init(ArrayGraph *pGraph)
{

    for (int i = 0; i < MAX_VERTEX; i++)

        pGraph->edgeArr[i][i] = 0;

}

void ArrayGraph_create(ArrayGraph *pGraph)
{

    for (int i = 0; i < MAX_VERTEX; ++i)    //填充顶点数组,也就是输入顶点元素
    {
        printf("输入第%d个顶点值\n",i+1);

        scanf(" %c",&(pGraph->vertexArr[i])); 

    }

    for (int j = 0; j <MAX_VERTEX; ++j)   //填充边关系
    {
        for (int i = j+1; i < MAX_VERTEX; ++i)
        {

            printf("若元素%c和%c有边,则输入1,否则输入0\t",pGraph->vertexArr[j],pGraph->vertexArr[i]);

            scanf("%d",&( pGraph->edgeArr[j][i]));
            pGraph->edgeArr[i][j] = pGraph->edgeArr[j][i];     //对称
        }
    }

}

static void  ArrayGraph_DFS_traverse(ArrayGraph * pGraph,int n,bool*visited)
{

    printf("%c\t",pGraph->vertexArr[n]);

    visited[n] = true;

    for(int i=0;i<MAX_VERTEX;++i)    //以当前已访问的顶点为中心, 在其他所有的顶点中寻找
    {
        if(pGraph->edgeArr[n][i]!=0 && visited[i]==false)  //如果和当前顶点有边,且他们没有被访问过。则访问他们。
        {

            ArrayGraph_DFS_traverse(pGraph,i,visited);
        }
    }

    for(int i=0;i<MAX_VERTEX;++i)  //对图中可能出现的“孤岛”做一次清查
      if(visited[i]==false)    //如果有孤岛存在,则用同样的方法,遍历他们。
      {
           ArrayGraph_DFS_traverse(pGraph,i,visited);
      }

} 

void ArrayGraph_DFS(ArrayGraph * pGraph,int n)
{

    bool visited[MAX_VERTEX];      //访问标记数组,
    for(int i=0;i<MAX_VERTEX;++i)   //局部变量初始化
       visited[i] = false;         

      ArrayGraph_DFS_traverse(pGraph,n,visited);

}

看见递归,全局变量,老司机都会邹起眉头。原因不多说啊。下面是非递归实现。

非递归实现强调一个回退动作,当遍历到尽头时,需要退回来,尝试另一条支路,所以,在递进到下一层之前,我们需要保存此刻的顶点的索引到栈中,为回退做准备。

这个和狗狗在路边尿尿做标记很类似  :)

对于非递归实现,附上一张gif图,便于理解非递归的方法  前进和回退 过程。这里画成了树结构,因为我觉得画成图了,看起来就很费劲了。树也是一种图,所以不影响的,道理是一样的。

50帧啊,一帧一帧的画,oh  my  god    (;′⌒` )    播放速度可能有点快,可以下载了用看图王 看。

深度优先搜索的非递归实现:

#include<stdio.h>
#include<stack>

using std::stack;
#define MAX_VERTEX  4

typedef char DataType;                 //图中元素的目标数据类型 

typedef struct
{
    DataType vertexArr[MAX_VERTEX];        //顶点元素数组 

    int edgeArr[MAX_VERTEX][MAX_VERTEX];   //边矩阵二维数组 

}ArrayGraph;

void ArrayGraph_init(ArrayGraph *pGraph);
void ArrayGraph_create(ArrayGraph *pGraph);
void ArrayGraph_DFS(ArrayGraph * pGraph,int n);

int main()
{
    ArrayGraph g;
    ArrayGraph_init(&g);       //初始化图
    ArrayGraph_create(&g);     //创建图
    ArrayGraph_DFS(&g,3);       //遍历 

    return 0;
}

//初始化为一个无圈图 ,也就是边矩阵中,主对角线元素都是0
void ArrayGraph_init(ArrayGraph *pGraph)
{

    for (int i = 0; i < MAX_VERTEX; i++)

        pGraph->edgeArr[i][i] = 0;

}

void ArrayGraph_create(ArrayGraph *pGraph)
{

    for (int i = 0; i < MAX_VERTEX; ++i)    //填充顶点数组,也就是输入顶点元素
    {
        printf("输入第%d个顶点值\n",i+1);

        scanf(" %c",&(pGraph->vertexArr[i])); 

    }

    for (int j = 0; j <MAX_VERTEX; ++j)   //填充边关系
    {
        for (int i = j+1; i < MAX_VERTEX; ++i)
        {

            printf("若元素%c和%c有边,则输入1,否则输入0\t",pGraph->vertexArr[j],pGraph->vertexArr[i]);

            scanf("%d",&( pGraph->edgeArr[j][i]));
            pGraph->edgeArr[i][j] = pGraph->edgeArr[j][i];     //对称
        }
    }

}

void ArrayGraph_DFS(ArrayGraph * pGraph,int n)
{

    bool visited[MAX_VERTEX];       //局部访问标记数组
    for(int i=0;i<MAX_VERTEX;++i)   //局部变量需要手动初始化哦!
       visited[i] = false;          //局部变量是一次性数据,调用完,就回收。 

    bool  complete  = false;       //是否真正遍历完成?,主要用来对付非连通图

    stack<int> backStack;          //回退记录栈 

    int peekVertexIndex ;         //回退栈的栈顶存储的顶点的索引
    int i;

    do{

        printf("%c\t",pGraph->vertexArr[n]);    //访问当前子图的源点,first blood!!!
        visited[n] = true;
        backStack.push(n);

        while(!backStack.empty())     //当回退栈为空时,说明已经无路可退,顶点遍历完了
        {
            peekVertexIndex = backStack.top();  //从先前访问过的顶点开始

            for(i=0;i<MAX_VERTEX;++i)
            {
                                              //寻找他的一个未被访问的邻接点
                if(pGraph->edgeArr[peekVertexIndex][i]!=0 && visited[i]== false)
                {
                    printf("%c\t",pGraph->vertexArr[i]);   //一旦找到一条可行的支路连接的新顶点,则访问它 

                    visited[i] = true;
                    backStack.push(i);                     //访问过后,将他入栈,作为新的栈顶元素。
                    break;              

                }

            }

            if(i==MAX_VERTEX)       //一个前进的路径也没找到,说明到了某条支路的尽头,或者此顶点的邻结点都被访问过了
            {
                backStack.pop();    //回退,尝试先前访问过的顶点的另一条支路
            }

         } //end of while

         complete = true;    //当遍历完一个连通子图后,假定完成了所有的遍历 

         for(i = 0;i<MAX_VERTEX;++i) //对所有的顶点清查,
         {
             if(visited[i]==false)    //发现还有顶点没访问到,出现了孤岛
             {
                 complete = false;    //将完成标记 置为false
                 n = i;               //记下这个孤岛的源顶点点索引
                 break;
            }  

         }

    }while(!complete);        //没有真正完成所有的遍历,则再来一次
}

记得看过一本书上说:过多的注释是对自己代码的不自信,我觉得这话有道理。但是注释就是代码的笔记,多一点总比少一点好吧。

广度优先搜索(Breadth First Search . BFS)

广度优先搜索和数的层遍历也是一个道理。

它对有向图和无向图都适用。

它同样需要访问标记数据,同样需要考虑非连通图的问题。

同样我也用了gif图来宏观的描述这个过程。(我怀疑我是个图形极客  - - ! )

可以发现,当发现一个可以访问的顶点后,广度优先搜索不会像深度优先搜索那么立刻递进到下一层,而是再去找同层的“兄弟”顶点,直到同层再也找不到更多的可访问的“兄弟”了,才进入下一层。这个差异在代码中就是 break的有无体现出来的。

从这点差异我们也可以体会到,为什么一个叫深度优先,一个叫广度优先。深度优先搜索是先纵向伸展开,而广度优先则是先横向拉伸。

同一层的顶点是用队列来存储的。我用了C++ STL中的queue模版类。

代码:

#include<stdio.h>
#include<queue>

using std::queue;
#define MAX_VERTEX  4

typedef char DataType;                 //图中元素的目标数据类型 

typedef struct
{
    DataType vertexArr[MAX_VERTEX];        //顶点元素数组 

    int edgeArr[MAX_VERTEX][MAX_VERTEX];   //边矩阵二维数组 

}ArrayGraph;

void ArrayGraph_init(ArrayGraph *pGraph);
void ArrayGraph_create(ArrayGraph *pGraph);
void ArrayGraph_BFS(ArrayGraph * pGraph,int n);

int main()
{
    ArrayGraph g;
    ArrayGraph_init(&g);       //初始化图
    ArrayGraph_create(&g);     //创建图
    ArrayGraph_BFS(&g,0);       //遍历 

    return 0;
}

//初始化为一个无圈图 ,也就是边矩阵中,主对角线元素都是0
void ArrayGraph_init(ArrayGraph *pGraph)
{

    for (int i = 0; i < MAX_VERTEX; i++)

        pGraph->edgeArr[i][i] = 0;

}

void ArrayGraph_create(ArrayGraph *pGraph)
{

    for (int i = 0; i < MAX_VERTEX; ++i)    //填充顶点数组,也就是输入顶点元素
    {
        printf("输入第%d个顶点值\n",i+1);

        scanf(" %c",&(pGraph->vertexArr[i])); 

    }

    for (int j = 0; j <MAX_VERTEX; ++j)   //填充边关系
    {
        for (int i = j+1; i < MAX_VERTEX; ++i)
        {

            printf("若元素%c和%c有边,则输入1,否则输入0\t",pGraph->vertexArr[j],pGraph->vertexArr[i]);

            scanf("%d",&( pGraph->edgeArr[j][i]));
            pGraph->edgeArr[i][j] = pGraph->edgeArr[j][i];     //对称
        }
    }

}

void ArrayGraph_BFS(ArrayGraph * pGraph,int n)
{

    bool visited[MAX_VERTEX];
    for(int i=0;i<MAX_VERTEX;++i)
       visited[i] = false;         

    bool  complete  = false;       

    queue<int> layerQueue;        //层队列   

    int frontVertexIndex ;
    int i;

    do{

        printf("%c\t",pGraph->vertexArr[n]);
        visited[n] = true;
        layerQueue.push(n);

        while(!layerQueue.empty())
        {
            frontVertexIndex = layerQueue.front();  //获取队列 队首顶点
            layerQueue.pop();

            for(i=0;i<MAX_VERTEX;++i)
            {
                                              //将 队首顶点 的邻结点全部访问,并全部入栈
                if(pGraph->edgeArr[frontVertexIndex][i]!=0 && visited[i]== false)
                {
                    printf("%c\t",pGraph->vertexArr[i]);   

                    visited[i] = true;
                    layerQueue.push(i);               

                }

            }

         } //end of while

         complete = true;    //当遍历完一个连通子图后,假定完成了所有的遍历 

         for(i = 0;i<MAX_VERTEX;++i) //对所有的顶点清查,
         {
             if(visited[i]==false)    //发现还有顶点没访问到,出现了孤岛
             {
                 complete = false;    //将完成标记 置为false
                 n = i;               //记下这个孤岛的源顶点点索引
                 break;
            }  

         }

    }while(!complete);        //没有真正完成所有的遍历,则再来一次
}

下一篇:图的最小生成树

时间: 2024-10-05 04:27:37

【数据结构】图的遍历的相关文章

C#与数据结构--图的遍历

C#与数据结构--图的遍历 8.2 图的存储结构 图 的存储结构除了要存储图中各个顶点的本身的信息外,同时还要存储顶点与顶点之间的所有关系(边的信息),因此,图的结构比较复杂,很难以数据元素在存储区 中的物理位置来表示元素之间的关系,但也正是由于其任意的特性,故物理表示方法很多.常用的图的存储结构有邻接矩阵.邻接表.十字链表和邻接多重表. 8.2.1  邻接矩阵表示法 对于一个具有n个顶点的图,可以使用n*n的矩阵(二维数组)来表示它们间的邻接关系.图8.10和图8.11中,矩阵A(i,j)=1

浅析数据结构-图的遍历

上一篇了解图的基本概念,包括图的分类.术语以及存储结构.本篇就是应用图的存储结构,将图进行数据抽象化,应用遍历方法,对数据进行遍历.由于图复杂的数据结构,一定保证图中所有顶点被遍历.如果只访问图的顶点而不关注边的信息,那么图的遍历十分简单,使用一个foreach语句遍历存放顶点信息的数组即可.但是,如果为了实现特定算法,就必须要根据边的信息按照一定的顺序进行遍历.图的遍历算法是求解图的连通性问题.拓扑排序和求解关键路径等算法的基础. 一.图的遍历   图的数据结构相对树复杂,图的任一顶点都可能和

数据结构-图的遍历

图的遍历是指从一个顶点出发,访问且仅一次访问图中其余所有顶点,不是所有边的处理.是求图的连通性,拓扑排序,路径求解等问题的基础. 非常基本的图的遍历方法有深度优先搜索法和广度(宽度)优先搜索法. 深度优先搜索,Depth First Search,DFS 深度优先搜索法是树的先根遍历的推广,它的基本思想是:从图G的某个顶点v0出发,访问v0,然后选择一个与v0相邻且没被访问过的顶点vi访问,再从vi出发选择一个与vi相邻且未被访问的顶点vj进行访问,依次继续.如果当前被访问过的顶点的所有邻接顶点

数据结构-图的遍历之Bellman-Ford算法和SPFA算法

一.Bellman-Ford算法 用于解决单源最短路径的问题,但也能够处理有负权边的情况.这是与Djikstra算法不同的地方. 关于复杂度,要比Djikstra的复杂度更高一点.O(VE),而Djikstra复杂度是O(V^2),V是点的数量,E是边的数量 原理,就是会出现负环的情况,会使得最短路径越来越小,进而产生错误:如果出现负环,源点无法到达,那么也是不会影响求解的. 设置d数组,用于存储最短路径的距离,如果存在可以到达的负环,那么返回false:如果不存在,那么数组d中存储的就是最短距

42. 蛤蟆的数据结构笔记之四十二图的遍历之广度优先

42. 蛤蟆的数据结构笔记之四十二图的遍历之广度优先 本篇名言:"生活真象这杯浓酒 ,不经三番五次的提炼呵 , 就不会这样一来可口 ! -- 郭小川" 继续看下广度优先的遍历,上篇我们看了深度遍历是每次一个节点的链表是走到底的. 欢迎转载,转载请标明出处:http://write.blog.csdn.net/postedit/47029275 1.  原理 首先,从图的某个顶点v0出发,访问了v0之后,依次访问与v0相邻的未被访问的顶点,然后分别从这些顶点出发,广度优先遍历,直至所有的

41 蛤蟆的数据结构笔记之四十一图的遍历之深度优先

41  蛤蟆的数据结构笔记之四十一图的遍历之深度优先 本篇名言:"对于我来说 , 生命的意义在于设身处地替人着想 , 忧他人之忧 , 乐他人之乐. -- 爱因斯坦" 上篇我们实现了图的邻接多重表表示图,以及深度遍历和广度遍历的代码,这次我们先来看下图的深度遍历. 欢迎转载,转载请标明出处: 1.  原理 图遍历又称图的遍历,属于数据结构中的内容.指的是从图中的任一顶点出发,对图中的所有顶点访问一次且只访问一次.图的遍历操作和树的遍历操作功能相似.图的遍历是图的一种基本操作,图的许多其它

数据结构快速回顾——图的遍历

图的遍历指的是从图中的任一顶点出发,对图中的所有顶点访问一次且只访问一次.图的遍历操作和树的遍历操作功能相似.图的遍历是图的一种基本操作,图的许多其它操作都是建立在遍历操作的基础之上. 图的遍历方法目前有深度优先搜索法和广度(宽度)优先搜索法两种算法. 深度优先搜索法DFS 深度优先搜索法的基本思想是:从图G的某个顶点v0出发,访问v0,然后选择一个与v0相邻且没被访问过的顶点vi访问,再从vi出发选择一个与vi相邻且未被访问的顶点vj进行访问,依次继续.如果当前被访问过的顶点的所有邻接顶点都已

图的遍历 - 数据结构

图的遍历 - 数据结构 概述 图的遍历是指从图中的任一顶点出发,对图中的所有顶点访问一次且只访问一次.图的遍历操作和树的遍历操作功能相似.图的遍历是图的一种基本操作,图的其它算法如求解图的连通性问题,拓扑排序,求关键路径等都是建立在遍历算法的基础之上. 由于图结构本身的复杂性,所以图的遍历操作也较复杂,主要表现在以下四个方面:① 在图结构中,没有一个“自然”的首结点,图中任意一个顶点都可作为第一个被访问的结点.② 在非连通图中,从一个顶点出发,只能够访问它所在的连通分量上的所有顶点,因此,还需考

数据结构例程——图的遍历

本文是[数据结构基础系列(7):图]中第6课时[图的遍历]的例程. 1.深度优先遍历--DFS(linklist.h是图存储结构的"算法库"中的头文件,详情请单击链接-) #include <stdio.h> #include <malloc.h> #include "graph.h" int visited[MAXV]; void DFS(ALGraph *G, int v) { ArcNode *p; int w; visited[v]=