设为首页收藏本站

LUPA开源社区

 找回密码
 注册
文章 帖子 博客
LUPA开源社区 首页 业界资讯 技术文摘 查看内容

自己动手实现图的BFS和DFS(附完整源码)

2014-2-25 15:24| 发布者: 红黑魂| 查看: 12831| 评论: 2|原作者: 兰亭风雨|来自: CSDN博客

摘要: 图的存储结构 本文的重点在于图的深度优先搜索(DFS)和广度优先搜索(BFS),因此不再对图的基本概念做过多的介绍,但是要先大致了解下图的几种常见的存储结构。 邻接矩阵 邻接矩阵既可以用来存储无向图,也可以 ...

图的存储结构

    本文的重点在于图的深度优先搜索(DFS)和广度优先搜索(BFS),因此不再对图的基本概念做过多的介绍,但是要先大致了解下图的几种常见的存储结构。

    邻接矩阵

    邻接矩阵既可以用来存储无向图,也可以用来存储有向图。该结构实际上就是用一个二维数组(邻接矩阵)来存储顶点的信息和顶点之间的关系(有向图的弧或无向图的边)。其描述形式如下:

  1. //图的邻接矩阵存储表示  
  2. #define MAX_NUM 20 // 最大顶点个数  
  3. enum GraphKind{GY,GN}; // {有向图,无向图}  
  4. typedef struct  
  5. {  
  6.    VRType adj; // 顶点关系类型。对无权图,用1(是)或0(否)表示是否相邻;对带权图,则为权值  
  7.    InfoType *info; // 与该弧或边相关信息的指针(可无)  
  8. }ArcCell,AdjMatrix[MAX_NUM][MAX_NUM]; // 二维数组  
  9. typedef struct  
  10. {  
  11.    VertexType vexs[MAX_NUM]; // 顶点向量  
  12.    AdjMatrix arcs; // 邻接矩阵  
  13.    int vexnum,arcnum; // 图的当前顶点数和弧(边)数  
  14.    GraphKind kind; // 图的种类标志  
  15. }Graph;  
    我们分别看下面两个图,左边为有向图,右边为无向图


    上面两个图均为无权图,我们假设存储的时候,V0的序号为0,V1的序号为1,V2的序号为2。。。,且adj为1表示两顶点间没有没有连接,为0时表示有连接。则有向图的邻接矩阵如下图左边的矩阵所示,无向图的邻接矩阵如下图右边的矩阵所示;


    根据邻接矩阵很容易判断图中任意两个顶点之间连通与否,并可以求出各个顶点的度。
    1、对于无向图,观察右边的矩阵,发现顶点Vi的度即是邻接矩阵中第i行(或第i列)的元素之和。
    2、对于有向图,由于需要分别计算出度和入读,观察左边的矩阵,发现顶点Vi的出度即为邻接矩阵第i行元素之和,入度即为邻接矩阵第i列元素之和,因此顶点Vi的度即为邻接矩阵中第i行元素和第i列元素之和。
    很明显,邻接矩阵所占用的存储空间与图的边数或弧数无关,因此适用于边数或弧数较多的稠密图。

    邻接表

    邻接表是图的一种链式存储结构,既适合于存储无向图,也适合于存储有向图。在邻接表中,用一个一维数组存储图中的每个顶点的信息,同时为每个顶点建立一个单链表,链表中的节点保存依附在该顶点上的边或弧的信息。其描述形式如下
  1. //图的邻接表存储表示  
  2. #define MAX_NUM 20  
  3. enum GraphKind{GY,GN}; // {有向图,无向图}  
  4. typedef struct   
  5. {  
  6.    int adjvex; // 该弧所指向的顶点或边的另一个顶点的位置  
  7.    ArcNode *nextarc; // 指向下一条弧或边的指针  
  8.    InfoType *info; // 与弧或边相关信息的指针(可无)  
  9. }ArcNode;// 表结点  
  10. typedef struct  
  11. {  
  12.    VertexType data; // 顶点信息  
  13.    ArcNode *firstarc; // 第一个表结点的地址,指向第一条依附该顶点的弧或边的指针  
  14. }VNode,AdjList[MAX_NUM]; // 头结点  
  15. struct   
  16. {  
  17.    AdjList vertices;  
  18.    int vexnum,arcnum; // 图的当前顶点数和弧(边)数  
  19.    GraphKind kind; // 图的种类标志  
  20. }Graph;  
    依然以上面的有向图和无向图为例,采用邻接表来存储,可以得到对应的存储形式如下:


    在邻接表上容易找到任意一个顶点的第一个邻接点和下一个邻接点,但是要判定任意两个顶点之间是否有边或弧需搜索两个顶点对应的链表,不及邻接矩阵方便。
    很明显,邻接表所占用的存储空间与图的边数或弧数有关,因此适用于边数或弧数较少的稀疏图。

    十字链表

    十字链表也是一种链式存储结构,用来表示有向图,它在有向图邻接表的基础上加入了指向弧尾的指针。表示形式如下:
  1. //有向图的十字链表存储表示  
  2. #define MAX_NUM 20  
  3. typedef struct // 弧结点  
  4. {  
  5.   int tailvex,headvex; // 该弧的尾和头顶点的位置  
  6.   ArcBox *hlink,*tlink; // 分别为弧头相同和弧尾相同的弧的链域  
  7.   InfoType *info; // 该弧相关信息的指针,可指向权值或其他信息(可无)  
  8. }ArcBox;  
  9. typedef struct // 顶点结点  
  10. {  
  11.   VertexType data;  
  12.   ArcBox *firstin,*firstout; // 分别指向该顶点第一条入弧和出弧  
  13. }VexNode;  
  14. struct  
  15. {  
  16.   VexNode xlist[MAX_NUM]; // 表头向量(数组)  
  17.   int vexnum,arcnum; // 有向图的当前顶点数和弧数  
  18. }Graph;      
    其思想也很容易理解,这里不再细说。
    在十字链表中,既容易找到以某个顶点为尾的弧,也容易找到以某个顶点为头的弧,因而容易求得顶点的出度和入度。

    邻接多重表

    邻接多重表也是一种链式存储结构,用来表示无向图,与有向图的十字链表相似,这里也不再细说,直接给出其表示形式:
  1. //无向图的邻接多重表存储表示  
  2.  #define MAX_NUM 20  
  3.  typedef struct   
  4.  {  
  5.    VisitIf mark; // 访问标记  
  6.    int ivex,jvex; // 该边依附的两个顶点的位置  
  7.    EBox *ilink,*jlink; // 分别指向依附这两个顶点的下一条边  
  8.    InfoType *info; // 该边信息指针,可指向权值或其他信息  
  9.  }EBox;  
  10.  typedef struct  
  11.  {  
  12.    VertexType data;  
  13.    EBox *firstedge; // 指向第一条依附该顶点的边  
  14.  }VexBox;  
  15.  typedef struct   
  16.  {  
  17.    VexBox adjmulist[MAX_NUM];  
  18.    int vexnum,edgenum; // 无向图的当前顶点数和边数  
  19.  }Graph;  

图的遍历

    图的遍历比树的遍历要复杂的多,因为图的任一顶点都有可能与其他的顶点相邻接,因此,我们需要设置一个辅助数组visited[]来标记某个节点是否已经被访问过。图的遍历通常有两种方法:深度优先遍历(BFS)和广度优先遍历(DFS),两种遍历方式的思想都不难理解。下面我们就以如下图所示的例子来说明图的这两种遍历方式的基本思想,并用邻接表作为图的存储结构,给出BFS和DFS的实现代码(下图为无向图,有向图的BFS和DFS实现代码与无向图的相同):

    我们以邻接表作为上图的存储结构,并将A、B、C、D、E、F、G、H在顶点数组中的序号分别设为0、1、2、3、4、5、6、7。我们根据上图所包含的信息,精简了邻接表的存储结构,采取如下所示的结构来存储图中顶点和边的相关信息:

  1. #define NUM 8          //图中顶点的个数  
  2.   
  3. /* 
  4. 用邻接表作为图的存储结构 
  5. 在邻接表中,用一个一维数组存储图中的每个顶点的信息, 
  6. 同时为每个顶点建立一个单链表,链表中的节点保存依附在该顶点上的边或弧的信息 
  7. */  
  8. typedef struct node  
  9. {   //链表中的每个节点,保存依附在该节点上的边或弧的信息  
  10.     int vertex;          //在有向图中表示该弧所指向的顶点(即弧头)的位置,  
  11.                          //在无向图中表示依附在该边上的另一个顶点的位置  
  12.     struct node *pNext;  //指向下一条依附在该顶点上的弧或边  
  13. }Node;  
  14. typedef struct head  
  15. {   //数组中的每个元素,保存图中每个顶点的相关信息  
  16.     char data;          //顶点的数据域  
  17.     Node *first;        //在有向图中,指向以该顶点为弧尾的第一条弧  
  18.                         //在无向图中,指向依附在该顶点上的第一条边  
  19. }Head,*Graph;           //动态分配数组保存每个顶点的相关信息  

    那么用邻接表来表示上图中各顶点间的关系,如下图所示:

    深度优先搜索

    深度优先搜索(DFS)类似于树的先序遍历(如尚未掌握图的先序遍历,请移步这里:http://blog.csdn.net/ns_code/article/details/12977901)。其基本思想是:从图中某个顶点v出发,遍历该顶点,而后依次从v的未被访问的邻接点出发深度优先遍历图中,直到图中所有与v连通的顶点都已被遍历。如果当前图只是需要遍历的非连通图的一个极大连通子图,则另选其他子图中的一个顶点,重复上述遍历过程,直到该非连通图中的所有顶点都被遍历完。很明显,这里要用到递归的思想。

    结合上面的例子来分析,假设从A点开始遍历该图,根据图中顶点的存储关系,会按照下面的步骤来遍历该图:

    1、在访问完顶点A后,选择A的第一个邻接点B进行访问;

    2、而后看B的邻接点,由于B的第一个邻接点A已经被访问过,故选择其下一个邻接点D进行访问;

    3、而后看D的邻接点,由于D的第一个邻接点B已经被访问过,故选择其下一个邻接点H进行访问;

    4、而后看H的邻接点,由于H的第一个邻接点D已经被访问过,故选择其下一个邻接点E进行访问;

    5、而后看E的邻接点,由于E的第一个邻接点B已经被访问过,那么看其第二个邻接点H,也被访问过了,E的邻接点已经全部被访问过了。

    6、这时退回到上一层递归,回到顶点H,同样H的邻接点也都被访问完毕,再退回到D,D的邻接点也已访问完毕,再退回到B,一直退回到A;

    7、由于A的下一个邻接点C还没有被访问,因此访问C;

    8、而后看C的邻接点,由于C的第一个邻接点A已经被访问过,故选择其下一个邻接点F进行访问;

    9、而后看F的邻接点,由于F的第一个邻接点C已经被访问过,故选择其下一个邻接点G进行访问;

    10、而后看G的邻接点,由于G的临界点都已被访问完毕,因此会退到上一层递归,看F顶点;

    11、同理,由F再回退到C,再由C回退到A,这是第一层的递归,A的所有邻接点也已都被访问完毕,遍历到此结束。



酷毙

雷人

鲜花

鸡蛋

漂亮
发表评论

最新评论

引用 游客 2018-8-24 11:28
: 看着牙疼!
引用 游客 2018-8-24 11:20
看着牙疼!

查看全部评论(2)

(200字以内)
验证问答 换一个 验证码 换一个

  • 快毕业了,没工作经验,
    找份工作好难啊?
    赶紧去人才芯片公司磨练吧!!

最新评论

关于LUPA|人才芯片工程|人才招聘|LUPA认证|LUPA教育|LUPA开源社区 ( 浙B2-20090187  

返回顶部