数据结构--图的遍历

和树的遍历类似,我们希望从图中某一顶点出发访遍图中所有的顶点,且每个顶点只被访问一次,这一过程就叫“图的遍历”。图的遍历算法是求解图的连通性问题,拓扑排序和求关键路径等算法的基础。
因为图的任一顶点都可能和其余的顶点连接,所以在访问了某个顶点之后,可能沿着某条路径搜索后又回到了该顶点。为了避免同一顶点被多次访问,可以设一个辅助数组visited[1…n],初始值设为false,在遍历图的过程中,一旦访问了顶点vi,就置visited[i]为true。
通常有两条遍历图的路径:深度优先搜索和广度优先搜索。他们对无向图和有向图都适用。

一、 深度优先搜索(DFS—depth_first search)
深度优先搜索遍历类似于树的先根遍历,树树的先根遍历的推广。
它从图中某个结点v出发,访问此顶点,然后从v的未被访问的邻接点出发深度优先遍历图,直至图中所有和v有路径相通的顶点都被访问到。若图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中的所有顶点都被访问到为止。


image.png

如上图所示,从顶点v1出发进行搜索,在访问了顶点v1后,选择邻接点v2,因为v1没被访问过,所以从v2出发进行搜索,以此类推,接着从v4、v8、v5出发进行搜索,在访问了v5之后,由于v5的邻接点都以被访问,则搜索回到v8,由于同样的理由,搜索继续回到v4、v2直到v1,此时由于v1的另一个邻接点未被访问,则搜索又从v1到v3再继续进行下去,由此得到的顶点访问顺序为v1 → v2 → v4 → v8 → v5 → v3 → v6 → v7
这显然是一个递归过程,算法如下:
1、 使用邻接矩阵
图的结构定义:

typedef struct {
    char vexs[n];            //存储顶点信息
    int arc[n][n];         //邻接矩阵,可看作边
    int numVertexes, numEdges;      //图中当前的顶点数和边数
} Graph;

邻接矩阵的深度优先递归算法

void DFS(Graph g, int i) {
int j;
    visited[i] = TRUE;
    for(j = 0; j < g.numVertexes; j++) {
        if(g.arc[i][j] == 1 && !visited[j]) {
            //对未访问的邻接顶点递归调用
            DFS(g, j);                  
        }
    }
}

2、 使用邻接表
图的结构定义:

typedef struct EdgeNode {         //表结点结构
    int adjvex;         //邻接点域,存储该顶点对应的下标
    int weigth;        //用于存储权值,对于非网图可以不需要
    struct EdgeNode *next;      //链域,指向下一个邻接点
} EdgeNode;

typedef struct VertexNode  {     //头结点结构
    char data;        //数据域,存储头结点信息
    EdgeNode *firstedge;        //指向与该头结点相连的第一个结点的指针
} VertexNode, AdjList[n];
 
typedef struct {
    AdjList adjList;
    int numVertexes, numEdges;  //图中当前顶点数和边数
} GraphList;

邻接表的深度递归算法

void DFS(GraphList g, int i) {
    EdgeNode *p;
    visited[i] = TRUE;
    p = g->adjList[i].firstedge;
    while(p) {
        if(!visited[p->adjvex]) {
            //对访问的邻接顶点递归调用
            DFS(g, p->adjvex);           
        }
        p = p->next;
    }
}

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

二、广度优先搜索(BFS—breadth_first search)

广度优先搜索遍历类似于树的按层次遍历的过程。

假设从图中某顶点广度优先搜v0出发,在访问了v0之后,依次访问v0的各个未曾访问过的邻接点,然后分别从这些邻接点出发广度优先搜索遍历图,直至图中所有已被访问的顶点的邻接点都被访问到。若此时图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点做起始点,重复上述过程,直至图中所有顶点都被访问到为止。也就是说,广度优先搜索遍历图的过程是以v0为起点,由近至远,依次访问和v0有路径相同且路径长度为1、2…的顶点。

image.png

如上图,首先访问v1和v1的邻接点v2和v3,然后依次访问v2的邻接点v4和v5已经v3的邻接点v6和v7,最后访问v4的邻接点v8。至此所有顶点均访问完成。得到的顶点访问顺序为v1 → v2 → v3 → v4 → v5 → v6 → v7 → v8
和深度优先搜索类似,在遍历过程中也要一个访问标志数组。并且,为了顺次访问路径长度为2、3…的顶点,需附设队列以存储已被访问的路径长度为1、2…的顶点。
算法如下:
1、 使用邻接矩阵

void BFSTraverse(Graph g) {
    //从顶点v出发,广度优先遍历图g
    //借助一个辅助队列q,用于存储已被访问的路径长度
    Queue q;
    for(i = 0; i < g.numVertexes; i++) {//对每个顶点做循环
        if(!visited[i])  {             //若是未访问过
            visited[i] = TRUE;
            EnQueue(q, i);           //将此结点入队列
            while(!QueueEmpty(q)) {     //将队中元素出队列,赋值给m
                DeQueue(q, m);        
                for(j = 0; j < g.numVertexes; j++) {
                   //判断其他顶点若与当前顶点存在边且未访问过
                    if(g.arc[m][j] == 1 && !visited[j]) {
                        visited[j] = TRUE;
                        EnQueue(&q, j);
                    }
                }
            }
        }
    }
}

2、 使用邻接表

void BFSTraverse(GraphList g) {
    for(i = 0; i < g.numVertexes; i++) {
        if(!visited[i]) {
            visited[i] = TRUE;
            EnQueue(q, i);
            while(!QueueEmpty(q)) {
                DeQueue(q, m);
                //找到与当前顶点相连的第一个结点的指针
                p = g.adjList[m].firstedge;     
                while(p) {
                    if(!visited[p->adjvex]) {
                        visited[p->adjvex] = TRUE;
                        EnQueue(q, p->adjvex);
                    }
                    p = p->next;
                }
            }
        }
    }
}

广度优先搜索遍历图的时间复杂度和深度优先搜索遍历相同,不同之处仅仅在于对顶点的访问顺序不同。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容

  • 内容整理于鱼c工作室教程 1. 图的基本概念 1.1 图的概念 图(Graph)是由顶点的有穷非空集合和顶点之间边...
    阿阿阿阿毛阅读 3,170评论 0 2
  • https://zh.visualgo.net/graphds 浅谈图形结构https://zh.visualgo...
    狼之独步阅读 4,137评论 0 0
  • 图是一种比线性表和树更复杂的数据结构,在图中,结点之间的关系是任意的,任意两个数据元素之间都可能相关。图是一种多对...
    Alent阅读 2,298评论 1 22
  • 弗洛伊德算法适用于为图中每一个顶点求最短路径,思路如下 检查图中任何一个 到 任何另一个点能否通过第一个点降低最短...
    RichardW阅读 949评论 0 1
  • 第一章 绪论 什么是数据结构? 数据结构的定义:数据结构是相互之间存在一种或多种特定关系的数据元素的集合。 第二章...
    SeanCheney阅读 5,758评论 0 19