广度优先搜索应用举例:计算网络跳数

图结构在解决许多网络相关的问题时直到了重要的作用。

比如,用来确定在互联网中从一个结点到另一个结点(一个网络到其他网络的网关)的最佳路径。一种建模方法是采用无向图,其中顶点表示网络结点,边代表结点之间的联接。使用这种模型,可以采用广度优先搜索来帮助确定结点间的最小跳数。

如图1所示,该图代表Internet中的6个网络结点。以node1作为起点,有不止1条可以通往node4的路径。<node1,node2,node4>,<node1,node3,node2,node4>,<node1,node3,node5,node4>都是可行的路径。广度优先搜索可以确定最短路径选择,即<node1,node2,node4>,一共只需要两跳。

我们以bfs作为广度优先搜索的函数名(见示例1及示例2)。该函数用来确定互联网中两个结点之间的最小跳数。这个函数有3个参数:graph是一个图,在这个问题中就代表整个网络;start代表起始的顶点;hops是返回的跳数链表。函数bfs会修改图graph,因此,如果有必要的话在调用该函数前先对图创建拷贝。另外,hops中返回的是指向graph中实际顶点的指针,因此调用者必须保证只要访问hops,graph中的存储空间就必须保持有效。

graph中的每一个顶点都是一个BfsVertex类型的结构体(见示例1),该结构体有3个成员:data是指向图中顶点的数据域指针,color在搜索过程中维护顶点的颜色,hops维护从起始顶点开始到目标顶点的跳数统计。

match函数是由调用者在初始化graph时作为参数传递给graph_init的。match函数应该只对BfsVertex结构体中的data成员进行比较。

bfs函数将按照前面介绍过的广度优先搜索的方式来计算。为了记录到达每个顶点的最小跳数,将每个顶点的hop计数设置为与该顶点邻接的顶点的hop计数加1。对于每个发现的顶点都这样处理,并将其涂成灰色。每个顶点的颜色和跳数信息都由邻接表结构链表中的BfsVertex来维护。最后,加载hops中所有跳数未被标记为-1的顶点。这些就是从起始顶点可达的顶点。

bfs的时间复杂度为O(V+E),这里V代表图中的顶点个数,E是边的个数。这是因为初始化顶点的颜色属性以及确保起始顶点存在都需要O(V)的运行时间,广度优先搜索中的循环的复杂度是O(V+E),加载跳数统计链表的时间为O(V)。

示例1:广度优先搜索的头文件

/*bfs.h*/
#ifndef BFS_H
#define BFS_H #include "graph.h"
#include "list.h" /*定义广度优先搜索中的顶点数据结构*/
typedef struct BfsVertex_
{
void *data;
VertexColor color;
int hops;
}BfsVertex; /*函数接口定义*/
int bfs(Graph *graph, BfsVertex *start, List *hops); #endif // BFS_H

示例2:广度优先搜索的实现

/*bfs.c*/
#include <stdlib.h>
#include "bfs.h"
#include "graph.h"
#include "list.h"
#include "queue.h" /*bfs */
int bfs(Graph *graph, BfsVertex *start, List *hops)
{
Queue queue;
AdjList *adjlist, *clr_adjlist;
BfsVertex *clr_vertex, *adj_vertex;
ListElmt *element, *member; /*初始化图中的所有结点为广度优先结点*/
for(element = list_head(&graph_adjlists(graph)); element != NULL; element = list_next(element))
{
clr_vertex = ((AdjList *)list_data(element))->vertex;
if(graph->match(clr_vertex,start))
{
/*初始化起始顶点*/
clr_vertex->color = gray;
clr_vertex->hops = ;
}
else
{
/*初始化非起始顶点*/
clr_vertex->color = white;
clr_vertex->hops = -;
}
}
/*初始化队列,并将起始顶点的邻接表入队*/
queue_init(&queue,NULL);
/*返回起始顶点的邻接表,存储到clr_adjlist*/
if(graph_adjlist(graph,start,&clr_adjlist) != )
{
queue_destroy(&queue);
return -;
}
/*将顶点的邻接表入队到队列*/
if(queue_enqueue(&queue,clr_adjlist) != )
{
queue_destroy(&queue);
return -;
} /*执行广度优先探索*/
while(queue_size(&queue) > )
{
adjlist = queue_peek(&queue); /*遍历邻接表中的每一个顶点*/
for(member = list_head(&adjlist->adjacent); member != NULL; member = list_next(member))
{
adj_vertex = list_data(member); /*决定下一个邻接点的颜色*/
if(graph_adjlist(graph,adj_vertex,&clr_adjlist) != )
{
queue_destroy(&queue);
return -;
}
clr_vertex = clr_adjlist->vertex; /*把白色的顶点标成灰色,并把它的邻接顶点入队*/
if(clr_vertex->color == white)
{
clr_vertex->color = gray;
clr_vertex->hops = ((BfsVertex *)adjlist->vertex)->hops + ; if(queue_enqueue(&queue,clr_adjlist) != )
{
queue_destroy(&queue);
return -;
}
}
} /*将当前顶点邻接表从队列中移除并涂成黑色*/
if(queue_dequeue(&queue,(void **)&adjlist) == )
{
((BfsVertex *)adjlist->vertex)->color = black;
}
else
{
queue_destroy(&queue);
return -;
}
} queue_destroy(&queue); /*返回每一个顶点的hop计数到一个链表中*/
list_init(hops,NULL); for(element = list_head(&graph_adjlists(graph)); element != NULL; element = list_next(element))
{
/*跳过那些没有被访问到的节点(hops = -1)*/
clr_vertex = ((AdjList *)list_data(element))->vertex;
if(clr_vertex->color != -)
{
if(list_ins_next(hops,list_tail(hops),clr_vertex) != )
{
list_destroy(hops);
return -;
}
}
}
return ;
}

深度优先搜索的应用举例:拓扑排序

有时候,我们必须根据各种事物间的依赖关系来确定一种可接受的执行顺序。比如,在大学里必须满足一些先决条件才能选的课程,或者一个复杂的项目,其中某个特定的阶段必须在其他阶段开始之前完成。要为这一类问题建模,可以采用优先级图,其采用的是有向图的思路在优先级图中,顶点代表任务,而边代表任务之间的依赖关系以必须先完成的任务为起点,以依赖于此任务的其他任务为终点,画一条边即可。

如下图所示,它表示7门课程及其先决条件组成的一份课程表:S100没有先决条件,S200需要S100,S300需要S200和M100,M100没有先决条件,M200需要M100,M300需要S300和M200,并且S150没有先决条件同时也不是先决条件。

通过对这些课程执行拓扑排序,深度优先搜索有助于确定出一中可接受的顺序。

拓扑排序将顶点排列为有向无环图,因此所有的边都是从左到右的方向。正规来说,有向无环图G=(V,E)的拓扑排序是其顶点的一个线性排序,以便如果G中存在一条边(u,v),那么线性顺序中u出现在v的前面,在许多情况下,满足此条件的顺序有多个。

下面的代码示例实现了函数dfs,即深度优先搜索。该函数在这里用来对任务做拓扑排序。dfs有两个参数:graph代表图,在这个问题中则代表需要排序的任务;而参数ordered是完成拓扑排序后返回的顶点链表。调用该函数会修改图graph,因此如果有必要需要在调用前先对graph创建一个副本。另外,函数返回后链表ordered中保存了指向图graph中顶点的指针,因此调用者必须保证,一旦访问ordered中的元素就必须保证graph中的存储空间保持有效。graph中的每一个顶点都是一个DfsVertex结构体,该结构体拥有两个成员:data是指向顶点数据域部分的指针;而color在搜索过程中负责维护顶点的颜色信息。match函数是由调用者在初始化graph时通过参数传递给graph_init的,该函数应该只对DfsVertex结构体中的data成员进行比较。

dfs按照深度优先的方式进行搜索。dfs_main是实际执行搜索的函数。dfs中的最后一个循环保证对图中所有未相连的元素完成了检索。在dfs_main中逐个完成顶点的搜索并将其涂黑,然后插入链表ordered的头部。最后,ordered就包含完整拓扑排序后的顶点。

dfs的时间复杂度是O(V+E),V代表图中的顶点个数,而E代表边的个数。这是因为初始化顶点的颜色信息需要O(V)的时间,而dfs_main的时间复杂度为O(V+E)。

示例3:深度优先搜索的头文件

/*dfs.h*/
#ifndef DFS_H
#define DFS_H #include "graph.h"
#include "list.h" /*为深度优先搜索中的所有节点定义一个结构体*/
typedef struct DfsVertex_
{
void *data;
VertexColor color;
}DfsVertex; /*公共接口*/
int dfs(Graph *graph,List *ordered); #endif // DFS_H

 示例4:深度优先搜索的函数实现

/*dfs.c*/
#include <stdlib.h> #include "dfs.h"
#include "graph.h"
#include "list.h" /*dfs_main*/
static int dfs_main(Graph *graph, AdjList *adjlist, List *ordered)
{
AdjList *clr_adjlist;
DfsVertex *clr_vertex, *adj_vertex;
ListElmt *member; /*首先,将起始顶点涂成灰色,并遍历它的邻接顶点集合*/
((DfsVertex *)adjlist->vertex)->color = gray; for(member = list_head(&adjlist->adjacent); member != NULL; member = list_next(member))
{
/*决定下一个集合顶点的颜色*/
adj_vertex = list_data(member); if(graph_adjlist(graph,adj_vertex,&clr_adjlist) != )
{
return -;
} clr_vertex = clr_adjlist->vertex;
/*如果当前顶点是白色,则递归搜索它的邻接点*/
if(clr_vertex->color == white)
{
if(dfs_main(graph,clr_adjlist,ordered) != )
return -;
}
} /*把当前顶点涂成“黑”色,并加入到链表头部*/
((DfsVertex *)adjlist->vertex)->color = black; if(list_ins_next(ordered, NULL, (DfsVertex *)adjlist->vertex) != )
return -; return ;
} /*dfs*/
int dfs(Graph *graph, List *ordered)
{
DfsVertex *vertex;
ListElmt *element; /*初始化图中的所有顶点*/
for(element = list_head(&graph_adjlists(graph)); element != NULL; element = list_next(element))
{
vertex = ((AdjList *)list_data(element))->vertex;
vertex->color = white;
} /*执行广度优先搜索*/
list_init(ordered,NULL); for(element = list_head(&graph_adjlists(graph)); element != NULL; element = list_next(element))
{
/*确保图中的每个顶点都能被检索到*/
vertex = ((AdjList *)list_data(element))->vertex; if(vertex->color == white)
{
if(dfs_main(graph, (AdjList *)list_data(element), ordered) != )
{
list_destroy(ordered);
return -;
}
}
} return ;
}

广度优先(bfs)和深度优先搜索(dfs)的应用实例的更多相关文章

  1. 利用广度优先搜索(BFS)与深度优先搜索(DFS)实现岛屿个数的问题(java)

    需要说明一点,要成功运行本贴代码,需要重新复制我第一篇随笔<简单的循环队列>代码(版本有更新). 进入今天的主题. 今天这篇文章主要探讨广度优先搜索(BFS)结合队列和深度优先搜索(DFS ...

  2. matlab练习程序(广度优先搜索BFS、深度优先搜索DFS)

    如此经典的算法竟一直没有单独的实现过,真是遗憾啊. 广度优先搜索在过去实现的二值图像连通区域标记和prim最小生成树算法时已经无意识的用到了,深度优先搜索倒是没用过. 这次单独的将两个算法实现出来,因 ...

  3. (转)广度优先搜索BFS和深度优先搜索DFS

    1. 广度优先搜索介绍 广度优先搜索算法(Breadth First Search),又称为"宽度优先搜索"或"横向优先搜索",简称BFS. 它的思想是:从图中 ...

  4. 数据结构和算法总结(一):广度优先搜索BFS和深度优先搜索DFS

    前言 这几天复习图论算法,觉得BFS和DFS挺重要的,而且应用比较多,故记录一下. 广度优先搜索 有一个有向图如图a 图a 广度优先搜索的策略是: 从起始点开始遍历其邻接的节点,由此向外不断扩散. 1 ...

  5. 广度优先搜索(BFS)与深度优先搜索(DFS)的对比及优缺点

    深搜,顾名思义,是深入其中.直取结果的一种搜索方法. 如果深搜是一个人,那么他的性格一定倔得像头牛!他从一点出发去旅游,只朝着一个方向走,除非路断了,他绝不改变方向!除非四个方向全都不通或遇到终点,他 ...

  6. 深度优先搜索DFS和广度优先搜索BFS简单解析(新手向)

    深度优先搜索DFS和广度优先搜索BFS简单解析 与树的遍历类似,图的遍历要求从某一点出发,每个点仅被访问一次,这个过程就是图的遍历.图的遍历常用的有深度优先搜索和广度优先搜索,这两者对于有向图和无向图 ...

  7. 深度优先搜索DFS和广度优先搜索BFS简单解析

    转自:https://www.cnblogs.com/FZfangzheng/p/8529132.html 深度优先搜索DFS和广度优先搜索BFS简单解析 与树的遍历类似,图的遍历要求从某一点出发,每 ...

  8. 【算法入门】深度优先搜索(DFS)

    深度优先搜索(DFS) [算法入门] 1.前言深度优先搜索(缩写DFS)有点类似广度优先搜索,也是对一个连通图进行遍历的算法.它的思想是从一个顶点V0开始,沿着一条路一直走到底,如果发现不能到达目标解 ...

  9. 深度优先搜索 DFS 学习笔记

    深度优先搜索 学习笔记 引入 深度优先搜索 DFS 是图论中最基础,最重要的算法之一.DFS 是一种盲目搜寻法,也就是在每个点 \(u\) 上,任选一条边 DFS,直到回溯到 \(u\) 时才选择别的 ...

随机推荐

  1. 前端的UI设计与交互之数据展示篇

    合适的数据展示方式可以帮助用户快速地定位和浏览数据,以及更高效得协同工作.在设计时有以下几点需要注意:依据信息的重要等级.操作频率和关联程度来编排展示的顺序.注意极端情况下的引导.如数据信息过长,内容 ...

  2. MySQL数据库学习二 MSQL安装和配置

    2.1 下载和安装MySQL软件 2.1.1 基于客户端/服务器(C/S)的数据库管理系统 服务器:MySQL数据库管理系统 客户端:操作MySQL服务器 2.1.2 MySQL的各种版本 社区版(C ...

  3. 【Python】 子进程创建与使用subprocess

    subprocess *****本文参考了Vamei大神的http://www.cnblogs.com/vamei/archive/2012/09/23/2698014.html 运用subproce ...

  4. 源码实现 --> itoa函数实现

    itoa函数实现 itoa()函数的功能是将一个整数转换为一个字符串 例如12345,转换之后的字符串为"12345",-123转换之后为"-123",欢迎大家 ...

  5. cisco交换机实现端口聚合

    0x00前言: 今天听老师讲端口聚合,为了方便日后复习故此有 了本篇随笔. 0x01准备工具: cisco模拟器 0x02:目录 为什么要用端口聚合? 广播风暴? 扩展:SMTP 0x03正文: 为什 ...

  6. Python第二话 初识复杂数据类型(list、dictionary、tuple)

    上一篇我们简单认识了数据类型:数字number和字符串string,这篇我们就来隆重介绍一下重量级的数据类型:列表list.字典dictionary和元组tuple. 一.列表List: ①列表是什么 ...

  7. 理解JAVA内存模型

    实际上java内存模型是如上图所示一样 每个线程有自己的栈内存,存放共享对象的副本,本地变量 每个线程自己的本地变量是不可见的,但是共享对象对每个线程都是可见的. 如果想实现线程通信的话, 线程对共享 ...

  8. alpha-咸鱼冲刺day8

    一,合照 emmmmm.自然还是没有的. 二,项目燃尽图 三,项目进展 正在进行页面整合.然后还有注册跟登陆的功能完善-- 四,问题困难 数据流程大概是搞定了.不过语法不是很熟悉,然后还有各种判定. ...

  9. 201621123060《JAVA程序设计》第三周学习总结

    1. 本周学习总结 1.1写出你认为本周学习中比较重要的知识点关键词,如类.对象.封装等. 关键词:类.方法.属性.对象.多态.继承.封装.面向对象.> 1.2 用思维导图或者Onenote或其 ...

  10. 张金禹 C语言--第0次作业

    1:在填报专业的时候,我也犹豫了很久,但最后还是选择了计算机专业.因为在上大学之前我就对编程.设计等有浓厚的兴趣,但繁重的高中学习任务使我没有过多的去关注,所以我选择了计算机专业去培养我在这方面的兴趣 ...