浅析数据结构-图的遍历

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

一、图的遍历

 

图的数据结构相对树复杂,图的任一顶点都可能和其余顶点相邻接,所以在访问了某顶点之后,可能顺着某条边又访问到了已访问过的顶点。在图遍历过程中,防止同一个顶点被访问多次,必须记下每个访问过的顶点。给顶点附加一个访问标志isVisited,其初值为false,一旦某个顶点被访问,则将其isVisited标志设为true。

图顶点数据结构

 

在上面代码图顶点的数据结构定义中,增加了一个bool类型的成员isVisited,用于在遍历时判断是否已经访问过了该顶点。一般在进行遍历操作时,会首先将所有顶点的isVisited属性置为false,访问后设置为true。

图的遍历方法主要有两种:一种是深度优先搜索遍历(Depth-First Search,DFS),另一种是广度优先搜索遍历(Breadth-First Search,BFS)。

二、深度优先搜索遍历

原理

图的深度优先遍历类似于二叉树的深度优先遍历,其基本思想是:从图中某个顶点v出发,访问此顶点,然后从v的未被访问的邻接点出发深度优先遍历图,直至图中所有和v有路径相通的顶点都被访问到。显然,这是一个递归的搜索过程。

在上图中我们坚持沿着右手边走,如果碰见标记的跳过,选择另一条。到最后时再返回遍历确认是否都有标记。其实就是树的前序遍历。以上图为例,假定A是出发点,首先访问A。这时两个邻接点B、F均未被访问,右手边为B,访问B之后,按照右手原则遍历到C,相同原理A→B→C→D→E→F,在F上向右为A,因为A遍历过,则访问G。最后到H,再走发现到D,E,两者都访问了。关键此时,还有没访问到的,按照原路返回,返回到G,一条条返回,最后得到的访问序列为A→B→C→D→E→F→G→H→I。

2、代码实现

深度优先遍历,也有称为深度优先搜索,简称DFS。其实,就像是一棵树的前序遍历。

算法思想: 它从图中某个结点v出发,访问此顶点,然后从v的未被访问的邻接点出发深度优先遍历图,直至图中所有和v有路径相通的顶点都被访问到。若图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中的所有顶点都被访问到为止。

这是类似树的前序遍历算法,所以在程序实现上,很像前序遍历,同时在算法复杂度呈现出不同。

//邻接矩阵的深度递归算法
void  GraphData::DFS(GraphArray *pArray,int i)
{
    int j = 0;
    printf("%c",pArray->vex[i]);   //打印顶点,也可以其他操作
    for (j = 0; j< pArray->numVertexes;j++)
    {
        if (!bVisited[j]&&pArray->arg[i][j] == 1)    //关键之前初始化时,有关系的边赋值为1,所以由此进行判断
        {
            DFS(pArray,j);        //对为访问的邻接顶点递归调用
        }
    }
}
//邻接矩阵的深度遍历操作
void  GraphData::DFSTraverse(GraphArray *pArray)
{
    int i;
    for (i = 0;i < pArray->numVertexes;i++)
    {
        bVisited[i] = false;
    }
    for(i = 0; i< pArray->numVertexes;i++)
    {
        if (!bVisited[i])
        {
            DFS(pArray,i);
        }
    }
}

 

//邻接表的深度递归算法
void GraphData::DFS(GraphList *pList,int i)
{
    EdgeNode *itemNode;
    bVisited[i] = true;
    printf("%c",pList->vertexList[i].nNodeData); //打印顶点数据
    itemNode = pList->vertexList[i].pFirstNode;
    while(itemNode)
    {
        if (!bVisited[itemNode->nNodevex])
        {
            DFS(pList,itemNode->nNodevex);
        }
        itemNode = itemNode->next;
    }
}
//邻接表的深度遍历操作
void  GraphData::DFSTraverse(GraphList* pList)
{
    int i;
    GraphList *pGraphList = pList;
    for ( i = 0;pGraphList->numVertess;i++)
    {
        bVisited[i] = false;
    }
    for (i = 0;i < pGraphList->numVertess;i++)
    {
        if (!bVisited[i])
        {
            DFS(pGraphList,i);
        }
    }
}

对比两个不同的存储结构的深度优先遍历算法,对于n个顶点e条边的图来说,邻接矩阵由于是二维数组,要查找某个顶点的邻接点需要访问矩阵中的所有元素,因为需要O(n2)的时间。而邻接表做存储结构时,找邻接点所需的时间取决于顶点和边的数量,所以是O(n+e)。显然对于点多边少的稀疏图来说,邻接表结构使得算法在时间效率上大大提高。

三、广度优先搜索遍历

1、原理

图的广度优先遍历算法是一个分层遍历的过程,和二叉树的广度优先遍历类似,其基本思想在于:从图中的某一个顶点Vi触发,访问此顶点后,依次访问Vi的各个为层访问过的邻接点,然后分别从这些邻接点出发,直至图中所有顶点都被访问到。广度优先遍历类似树的层序遍历。

首先访问v1 和v1 的邻接点v2 和v3,然后依次访问v2 的邻接点v4 和v5 及v3 的邻接点v6 和v7,最后访问v4 的邻接点v8。由于这些顶点的邻接点均已被访问,并且图中所有顶点都被访问,由些完成了图的遍历。得到的顶点访问序列为:

v1→v2 →v3 →v4→ v5→ v6→ v7 →v8

和深度优先搜索类似,在遍历的过程中也需要一个访问标志数组。并且,为了顺次访问路径长度为2、3、…的顶点,需附设队列以存储已被访问的路径长度为1、2、… 的顶点。

//邻接矩阵的广度遍历操作
void GraphData::BFSTraverse(GraphArray *pArray)
{
    queue<int> itemQueue;
    int i,j;
    for(i = 0 ;i< pArray->numVertexes;i++)
    {
        bVisited[i] = false;
    }
    for(i = 0;i< pArray->numVertexes;i++)//每个顶点循环
    {
        if (!bVisited[i])//访问未被访问的顶点
        {
            bVisited[i] = true;
            printf("%c",pArray->vex[i]);
            itemQueue.push(i);   //将此结点入队
            while(!itemQueue.empty())
            {
                int m = itemQueue.front();   //结点出队
                itemQueue.pop();
                for(j = 0;j< pArray->numVertexes;j++)
                {
                    //判断其他顶点与当前顶点存在边且未被访问过。
                    if (!bVisited[j] && pArray->arg[m][j] == 1)
                    {
                        bVisited[j] = true;
                        printf("%c",pArray->vex[j]);
                        itemQueue.push(j);
                    }
                }
            }
        }
    }
}
//邻接表的广度遍历操作
void GraphData::BFSTraverse(GraphList *pList)
{
    queue<int> itemQueue;
    int i,j;
    EdgeNode* pitemNode;
    for(i = 0 ;i< pList->numVertess;i++)
    {
        bVisited[i] = false;
    }
    for(i = 0;i<pList->numVertess;i++)
    {
        if (!bVisited[i])
        {
            bVisited[i] = true;
            printf("%c",pList->vertexList[i].nNodeData);
            itemQueue.push(i);
            while(!itemQueue.empty())
            {
                int m = itemQueue.front();
                itemQueue.pop();
                pitemNode = pList->vertexList[m].pFirstNode;
                while(pitemNode)
                {
                    if (bVisited[pitemNode->nNodevex])
                    {
                        bVisited[pitemNode->nNodevex] = true;
                        printf("%c",pList->vertexList[pitemNode->nNodevex].nNodeData);
                        itemQueue.push(pitemNode->nNodevex);
                    }
                    pitemNode = pitemNode->next;
                }

            }
        }
    }
}

总结:对比图的深度优先遍历与广度优先遍历算法,会发现,它们在时间复杂度上是一样的,不同之处仅仅在于对顶点的访问顺序不同。可见两者在全图遍历上是没有优劣之分的,只是不同的情况选择不同的算法。

时间: 2024-08-19 09:38:39

浅析数据结构-图的遍历的相关文章

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

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

数据结构-图的遍历

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

浅析数据结构-图的基本概念

线性表和树两类数据结构,线性表中的元素是“一对一”的关系,树中的元素是“一对多”的关系,本章所述的图结构中的元素则是“多对多”的关系.图(Graph)是一种复杂的非线性结构,在图结构中,每个元素都可以有零个或多个前驱,也可以有零个或多个后继,也就是说,元素之间的关系是任意的. 一.图的定义与术语 定义:图(Graph)是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E),其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合. 1.图的分类 图是按照无方向和有方向分为无向

数据结构-图的遍历之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]=