前言

数据结构,一门数据处理的艺术,精巧的结构在一个又一个算法下发挥着他们无与伦比的高效和精密之美,在为信息技术打下坚实地基的同时,也令无数开发者和探索者为之着迷。

也因如此,它作为博主大二上学期最重要的必修课出现了。由于大家对于上学期C++系列博文的支持,我打算将这门课的笔记也写作系列博文,既用于整理、消化,也用于同各位交流、展示数据结构的美。

此系列文章,将会分成两条主线,一条“数据结构基础”,一条“数据结构拓展”。“数据结构基础”主要以记录课上内容为主,“拓展”则是以课上内容为基础的更加高深的数据结构或相关应用知识。

欢迎关注博主,一起交流、学习、进步,往期的文章将会放在文末。


简述

图论,毋庸置疑是数据结构知识体系中的一颗璀璨的明星,是几种逻辑结构中复杂性最高,最变化莫测的结构。因此在初学时众多角度不同的问题总是会成为各式各样的绊脚石。拓扑序列、最短路径、最小生成树…等等等等

而学习计算机科学专业画风的图论与数学专业画风的图论有着大相径庭的方向。很显然,计算机学家更注重图论算法的表示和实现而数学家更倾向于研究图论高级的性质。

在众多计算机领域需要讨论的图论内容中。图的存储结构及其遍历方法无疑是一切算法实现的起点。

俗话说:基础不牢,地动山摇。在所有基础图论算法学习过程中遇到的阻碍,都能在其中或多或少地窥到对存储和遍历内容掌握不牢的身影。

所以,下面就让我们来夯实基础,梳理一下几种存储结构之下的两种遍历方案吧~


下文的内容会与图的存储结构紧密相关,对于常用图的存储掌握不熟悉的同学们可以先观看上一篇文章:图的存储结构

深度优先遍历

深度优先遍历,英文全称叫做"deepfirstsearchdeep \ first\ searchdeep first search",简称"DFSDFSDFS"。

所谓深度优先,就是每次向着更深层次的结点走,走到尽头再进行回溯的遍历方案。

这种遍历方案具有递归调用自身的显著特征,所以在实现时,也常常是使用递归结构。

单单堆砌概念很难精准把握其意思,我们还是抓紧上代码开始实战吧,哦吼。

在开始遍历之前,还是要有一些前置约定:

  • 遍历过的点打上标记不用再遍历,可以使用如下的一个数组来完成这个任务
bool vis[N];
//vis[k] = true 表示结点k被遍历过
//vis[k] = false 表示结点k未被遍历过
  • 该图为有向无权图

邻接矩阵

使用邻接矩阵存储的图,在寻找可遍历的子节点时特点就是需要枚举所有结点查看是否存在边。

我们约定邻接矩阵定义如下:

bool am[N][N];
//am[x][y] = true 表示x到y之间存在边

那么遍历图的递归程序的基本结构实现如下:

void dfs(int k){//当前结点vis[k] = true;//该点被遍历过,打上标记for(int i = 1;i <= n;i++){if(!am[k][i]){//如果k与i之间没有边相连,则跳过continue;}if(vis[i]){//如果该节点已经被遍历过,则跳过continue;}dfs(i);//遍历更深层次的结点,完成该节点遍历后再进行其他后继节点遍历}
}

邻接表

使用邻接表存储图的一大特点就是将每个结点可到达的结点信息分开存储在各自的链表中,而非像邻接矩阵一样存储在同一个二维矩阵中。

每个链表的链首被统一记录在一个头结点数组中,我们给定邻接表定义如下:

struct Edge{//边链表结点int vertex;//边终点//如果有权值可以继续添加字段Edge * next;//链表后继指针
};Edge * head[N];//链首数组,存储所有链表头结点

那么遍历过程如下:

void dfs(int k){vis[k] = true;//标记遍历过for(Edge * edge = head[k];edge != NULL;edge = edge->next){if(vis[edge->vertex]){//如果终点已经被遍历过则跳过continue;}dfs(edge->vertex);}
}

数组邻接表

数组模拟的邻接表的特点就是放弃了动态内存的管理,而使用已经声明好的数组静态内存来管理边结点的加入。他仍然是链表结构,但不再使用指针获取后继元素位置,取而代之的是使用在数组中的下标来表示。

数组的邻接表定义如下:

struct Edge{int vertex;//终点int next;//后继边结点在数组中的下标,0为空
};
Edge nxt[M];//存放边结点的数组
int head[N];//存放各节点边链表头结点的数组
int tot;//边结点计数

那么dfs的实现如下:

void dfs(int k){vis[k] = true;//节点被遍历过,打上标记for(int i = head[k];i != 0;i++){if(vis[nxt[i].vertex]){continue;}dfs(nxt[i].vertex);}
}

经典应用:有向图判定环

有向图找环其实是个老生常谈的问题,他非常的常见,也有很多的变体,包括寻找最大环,负环等等。

使用深度优先遍历的一大特点就是每次只能遍历一条线路上的结点,路线上结点的先后次序呈现栈的结构。

根据这一特点,如果某个结点的后继在当前栈中则可以说明遍历过程中寻找到了一个环。

所以只需要将上文的遍历方案稍加改造,加入一个全局的判定数组用以标记结点是否在栈中,当遍历到结点时将其入栈,遍历结束后将其出栈。

这里还需要注意区分栈的标记数组和是否遍历的标记数组,他们的功能并不相同。

同时需要函数增加一个返回值,表示有没有寻找到环

存储结构采用邻接表:

bool stack[N];//标记结点是否位于栈中
bool dfs(int k){vis[k] = true;//标记该节点遍历过stack[k] = true;//结点入栈for(Node * node = head[k];node != NULL;node = node->next){if(stack[node->vertex]){//判断后继节点是否在栈中,如果在,则找到一个环return true;}if(vis[node->vertex]){//如果该节点已经被遍历,则跳过continue;}if(dfs(node->vertex)){//遍历后继节点,如果后继节点找到环,则直接放回truereturn true;}}   stack[k] = false;//结点出栈return false;//没有找到环
}

广度优先遍历

广度优先遍历,英文全称为"breadthfirstsearchbreadth\ first\ searchbreadth first search",简称BFSBFSBFS

宽度优先的意思是优先遍历同一层次的结点,当一个层次的结点遍历完了,再遍历下一层次的结点。这与深度优先遍历的方案恰好相对。

有意思的是,不仅是逻辑结构上相对,在结点次序对应的数据结构上,他们也分别对应两个特殊的线性结构——栈和队列。

在上文中我们说深度优先遍历对应的结点次序结构是一种栈,现在的广度优先遍历结点次序对应的就是队列了。准确说,这个队列是预遍历序列,他们只是按照顺序等待被遍历。

如下图:

之所以说这个结构是一个队列,一个重要的原因是这些结点不是预先放置在线性表中的,而是在遍历过程中添加进去的。当遍历第k层结点时,将其子节点加入队尾,当遍历完整个第k层时,后方的所有结点就都是k+1层的结点了。

邻接矩阵

我们说,实现广度优先遍历的关键是实现队列,那么为了方便演示。队列就是用数组和两个动态下标表示:

int queue[N];//队列数组
int l = 1;//队首
int r = 0;//队尾k = queue[l++];//获取出队元素
queue[++r] = k;//入队元素

同时为了防止结点被多次加入队列而多次被遍历,我们还要加入一个数组,用以标记结点已经进入队列。

bool inQueue[N];//标记结点是否已经入队

那么,使用邻接矩阵存储的图,使用广度优先遍历的方式如下:

int k;
while(l <= r){//队列不为空条件k = queue[l++];for(int i = 1;i <= n;i++){if(!dis[k][i]){//如果没有边,跳过continue;}if(inQueue[i]){//如果该节点已经加入队列,跳过continue;}//结点i入栈inQueue[i] = true;queue[++r] = i;}
}

邻接表

我们应该清楚,在目前的遍历规则下,结点队列和图的存储结构是两个不同的维度,相互之间的影响非常有限。因此改变的图的存储结构,从邻接矩阵改变为邻接表对于遍历实现的影响也非常小,只需要改变访问子节点的方式即可:

int k;
while(l <= r){k = queue[l++];for(Edge * edge = head[k];edge != NULL;edge = edge->next){if(inQueue[edge->vertex]){continue;}inQueue[i] = true;queue[++r] = edge->vertex;}
}

数组邻接表

int k;
while(l <= r){k = queue[l++];for(int i = head[k];i != 0;i = nxt[i].next){if(inQueue[nxt[i].vertex){continue;}inQueue[i] = true;queue[++r] = nxt[i].vertex;}
}

经典应用:最短合法路径

宽度优先遍历的特点就是其按照层次进行遍历。在寻求最短路径的问题上也有着自己独特的优势。尤其是我们现在要讨论的问题:无权图的最短合法路径

这类问题非常的常见,一个经典的例子是迷宫游戏

但是这里不说这个模型,迷宫的规则总是复杂的,有权的迷宫也不在少数,所以不妨来讨论一个更简单且异曲同工的模型:

在上图中,从点1出发,到达终点点7,最短的一条路需要走多少步。

在这个问题中,到达终点的路径可能有很多,肉眼看去,答案显而易见为1−>6−>71->6->71−>6−>7,需要两步。

如果使用深度优先遍历的话,即使找到了一条通往终点的路径,但是在没有穷尽所有可能的前提下,无法确定其最短方案是否就是当前的一种。所以深度优先遍历只能一点点的记录答案最小值,不断地迭代更新。

但使用宽度优先遍历的方案,问题就变得不一样了,因为无论怎么遍历,第一次出现的终点结点的层数一定是最小的,其方案也一定是最短的,这样就可以将解决方案优化。

对于上图来说,这意味着如果我们将1定义为第0层的话,那么7最早在第二层出现。

下面就让我们使用代码来实现它吧:

输入格式:
第一行为四个整数n,m,s,t,表示点数和边数以及起点和终点,保证终点能够到达
接下来m行,每行两个整数x,y,表示结点x和y之间存在一条边
输出格式:
一个整数表示起点到终点的最短距离

#include<iostream>
using namespace std;
const int N = 2000;struct Edge{int vertex;//终点Edge * next;Edge(int vertex,Edge * next){//构造函数this->vertex = vertex;this->next = next;}
};
Edge * head[N];//链表头结点数组
int deep[N];//结点深度
bool inQueue[N];//是否进入过队列
int queue[N];//队列void link(int x,int y){//建立双向边Edge * edge;edge = new Edge(y,head[x]);head[x] = edge;edge = new Edge(x,head[y]);head[y] = edge;
}int main(){int n,m,s,t;cin >> n >> m >> s >> t;for(int i = 0,x,y;i < m;i++){//建边cin >> x >> y;link(x,y);}int l = 1,r = 0;int k;queue[++r] = s;inQueue[s] = true;while(l <= r){//遍历队列k = queue[l++];if(k == t){//如果出队元素为终点,则找到答案break;}for(Edge * edge = head[k];edge != NULL;edge = edge->next){//遍历下一层结点if(inQueue[edge->vertex]){//如果该节点已经进入过队列,跳过continue;}queue[++r] = edge->vertex;//结点入队inQueue[edge->vertex] = true;deep[edge->vertex] = deep[k] + 1;//计算该节点深度}}cout << deep[t];
}

运行,结果如下:


往期博客

  • 【数据结构基础】数据结构基础概念
  • 【数据结构基础】线性数据结构——线性表概念 及 数组的封装
  • 【数据结构基础】线性数据结构——三种链表的总结及封装
  • 【数据结构基础】线性数据结构——栈和队列的总结及封装(C和java)
  • 【算法与数据结构基础】模式匹配问题与KMP算法
  • 【数据结构与算法基础】二叉树与其遍历序列的互化 附代码实现(C和java)
  • 【数据结构与算法拓展】 单调队列原理及代码实现
  • 【数据结构基础】图的存储结构
  • 【数据结构与算法基础】并查集原理、封装实现及例题解析(C和java)
  • 【数据结构与算法拓展】二叉堆原理、实现与例题(C和java)
  • 【数据结构与算法基础】哈夫曼树与哈夫曼编码(C++)
  • 【数据结构与算法基础】最短路径问题
  • 【数据结构与算法基础】堆排序原理及实现
  • 【数据结构与算法基础】最小生成树算法原理及实现

参考资料:

  • 《数据结构》(刘大有,杨博等编著)
  • 《算法导论》(托马斯·科尔曼等编著)
  • OI WiKi

【数据结构基础】图的遍历方法与应用相关推荐

  1. c++ 遍历list_数据结构之图的遍历,一篇文章get全部考点

    图的概念 举个例子,假设你的微信朋友圈中有若干好友:张三.李四.王五.赵六.七大姑.八大姨. 而你七大姑的微信号里,又有若干好友:你.八大姨.Jack.Rose. 微信中,许许多多的用户组成了一个多对 ...

  2. 【数据结构】图的遍历(BFS和DFS)

    图的遍历 图的遍历是指从图中的某一顶点出发,按照某种搜索方式沿着途中的边对图中所有顶点访问一次且仅访问一次.图的遍历主要有两种算法:广度优先搜索和深度优先搜索. 广度优先遍历BFS 广度优先遍历(BF ...

  3. 数据结构之图的遍历:深度优先遍历(DFS)

    图的遍历:深度优先遍历 思维导图: 深度优先遍历的原理: 深度优先遍历的代码实现: 深度优先遍历的性能: 深度优先生成树: 遍历与连通性的关系: 思维导图: 深度优先遍历的原理: ps: 实现方法是递 ...

  4. 数据结构之图的遍历:广度优先遍历(BFS)

    图的遍历:广度优先遍历 思维导图: 广度优先遍历的原理: 广度优先遍历的代码实现: 广度优先遍历的性能分析: 无权图单源最短路径问题: 广度优先生成树: 思维导图: 广度优先遍历的原理: 类似与树的层 ...

  5. 数据结构基础 后序遍历和中序遍历还原二叉树

    [问题描写叙述] 二叉树            A        /       /        B       C      /   /   /   /      D   E   F   G   ...

  6. 数据结构基础 - 链表的遍历

    链表的遍历跟数组的遍历很相似,不过不同的是,数组可以通过索引值随机访问数据,而链表一定要通过遍历的方式访问其中的节点,如果要知道第n个节点的内容,就需要遍历n-1个节点. C代码:  1#includ ...

  7. 数据结构(廿五) -- C语言版 -- 图 - 图的遍历 -- 邻接矩阵 - 深度/广度优先遍历/搜索(DFS、BFS)

    内容预览 零.读前说明 一.概 述 二.深度优先遍历(DFS) 2.1.无向图的遍历过程 2.2.有向图的遍历过程 2.3.总结说明 2.4.实现源代码 三.广度优先遍历(BFS) 3.1.广度优先的 ...

  8. 数据结构(廿六) -- C语言版 -- 图 - 图的遍历 -- 邻接表 - 深度/广度优先遍历/搜索(DFS、BFS)

    内容预览 零.读前说明 一.深度优先遍历 1.1.深度优先的遍历过程 1.2.深度优先的遍历实现代码 二.广度优先遍历 2.1.广度优先的遍历过程 2.2.广度优先的遍历实现代码 三.源码测试效果 3 ...

  9. 算法:C++实现图的遍历

    ​​​​​​ 目录 图的遍历 深度优先搜索法 广度优先搜索法 代码及注释部分 图的遍历,属于数据结构中的内容.指的是从图中的任一顶点出发,对图中的所有顶点访问一次且只访问一次.图的遍历操作和树的遍历操 ...

最新文章

  1. 深层学习:心智如何超越经验2.3 解释变化
  2. SettingsPLSQLDeveloper
  3. Java中的瞬态关键字及其使用
  4. Android onActivityResult中requestCode与resultCode区别
  5. dede使用方法----调用导航
  6. 云熙板式家具参数化拆单软件免锁版_数控开料机拆单软件如何选择?
  7. BZOJ 1066[SCOI2007]蜥蜴
  8. 测试驱动开发(一)-我们要的不仅仅是“质量”
  9. android镊 姩瀹夎 apk,用java寫的jodconverter借用openoffice來轉換office成pdf的示例代碼...
  10. plSQL中修改代码字体的大小
  11. ps使用脚本生成fnt
  12. Debian10: 安装兄弟DCP-7080D打印机
  13. 上海航芯 | 热敏打印机方案分享
  14. 怎么在电脑桌面添加待办便签小助手
  15. java 葫芦娃.rmvb,课内资源 - 基于JAVA的葫芦娃 — 最终之战
  16. 智能车OS照搬安卓没有出路,特别是在中国
  17. 将Windows电脑上的浏览器书签同步至iPad中的Safari
  18. 不知道如何分析选择基金、股票?Python来教你
  19. 人脸识别中的活体检测算法综述
  20. 163vip邮箱手机快速注册,163手机邮箱如何登录?

热门文章

  1. EF实体框架之CodeFirst二
  2. 数据分析案例--学生用户消费分析
  3. Google 街景(Street View)一年回顾
  4. qt 增加的翻译没有作用上_Qt语言翻译工具的使用 及 QT-Linguist国际化的简单实现...
  5. 【木马】利用Python制作一个接受式免杀载荷
  6. 哈夫曼树(Huffman Tree)
  7. vs2010c语言生成dll,VS2010下生成dll的方法
  8. php婚礼请帖,结婚请柬上的浪漫句子 婚礼请帖唯美的句子
  9. itop4412开发版linux实验手册,【分享】iTOP-4412开发板使用之初体验[多图]
  10. 商城管理系统服务器,基于Python实现的购物商城管理系统