前言

人生如逆旅,我亦是行人。————苏轼《临江仙·送钱穆父》


广度优先搜索介绍

广度优先搜索算法(又称宽度优先搜索)是最简便的图的搜索算法之一,这一算法也是很多重要的图的算法的原型。 Dijkstra 单源最短路径算法和 Prim 最小生成树算法都采用了和宽度优先搜索类似的思想。

核心思想:

从初始节点开始,应用算符生成第一层节点,检查目标节点是否在这些后继节点中,若没有,再用产生式规则将所有第一层的节点逐一扩展,得到第二层节点,并逐一检查第二层节点中是否包含目标节点。若没有,再用算符逐一扩展第二层的所有节点……,如此依次扩展,检查下去,直到发现目标节点为止。

代码算法描述:

/*** 广度优先搜索算法描述 **/ int Bfs(){//初始化,初始状态存入队列//队列首指针 head = 0,尾指针 tail = 1  do{//指针head后移一位,指向待扩展结点for(int i=1; i<=max; i++)    //max为产生子结点的规则数 {if(子结点符合条件){tail尾指针加一,把新结点存入队尾if(新结点与原已产生的结点重复)删去该结点(取消入队,尾指针tail减1)else if(新结点是目标结点)//TODO//输出并退出} }        }while(head < tail);     //不满足此条件说明队列为空 }

代码算法描述:

  1. 每生成一个子结点,就要提供指向它们父结点的指针。当解出现时候,通过逆向跟踪,找到从根结点到目标结点的一条路径。当然如果不要求输出路径,就没必要记父结点。
  2. 生成的结点要与前面所有已经产生结点比较,以免出现重复结点,浪费时间和空间,还有可能陷入死循环。
  3. 如果目标结点的深度与路径长度成正比,那么,找到的第一个解就是最优解,此时的搜索速度要比深度搜索更快,再求最优解的时候往往采用广度优先搜索;如果目标结点的路径不与深度成正比,第一次找到的解就不一定是最优解。
  4. 广度优先搜索的效率还有赖于目标结点所在位置情况,如果目标结点深度处于较深层时,需搜索的结点数基本上以指数增长。此时使用深度搜索就相对更为便利一点。(具体问题还是要具体分析

题目


【例1】

图中表示了一个从A到H的图,现在要找出一条从A到H且经过城市最少的一条路线。

使用队列的思想,数组 arr 用来存储扩展结点,作为一个队列,arr[i] 记录经过的结点,数组 b[i] 记录前趋结点,通过倒推得出最短的路线,具体过程如下:

  1. A 入队,队头为 0,队尾为 1
  2. 将队头所有可以直接指向的结点入队(如果该结点在队列中出现过就不入队,可用一布尔数组 s[i] 来记录判断),将入队结点的前趋结点保存在 b[i] 中。然后将队头加 1,得到新的队头结点。
  3. 重复以上步骤,直到搜到结点 H 时,则搜索结束。利用 b[i] 可倒推出经过最少结点的路线。

代码书写:

#include<iostream>
#include<cstring>using namespace std;/*
* 9*9的矩阵 : A/B/C/D/E/F/G/H
* 数组 `arr` 用来存储扩展结点,作为一个队列,`arr[i]` 记录经过的结点,数组 `b[i]` 记录前趋结点,通过倒推得出最短的路线
*///定义一个数组表示矩阵
int matrix[9][9] = {{0,0,0,0,0,0,0,0,0},{0,1,0,0,0,1,0,1,1},{0,0,1,1,1,1,0,1,1},{0,0,1,1,0,0,1,1,1},{0,0,1,0,1,1,1,0,1},{0,1,1,0,1,1,1,0,0},{0,0,0,1,1,1,1,1,0},{0,1,1,1,0,0,1,1,0},{0,1,1,1,1,0,0,0,1}};int arr[101],b[101];
bool s[9];          //布尔数组:记录判断经过的结点的历史,最多经过9个结点且不出现重复//输出过程
int out(int d)
{cout << char(arr[d] + 64);while(b[d])       //前趋结点不为空结点 {d = b[d];cout << "--" << char(arr[d] + 64); } cout << endl;
} void doit(void)
{int head, tail, i;//队头为0,队尾为1 head=0;tail=1;arr[1] = 1;      //记录经过的结点b[1] = 0;     //记录前趋结点s[1] = 1;      //表示该结点已经到过了do{head++;                    //队头加一,出队 for(i=1; i<=8; i++)     //搜索可直接到达的结点{    //TODOif((matrix[arr[head]][i] == 0) && (s[i] == 0))   //判断城市是否走过{tail++;            //队尾加一,入队arr[tail] = i; b[tail] = head;s[i]=1;if(i == 8){//TODO:第一次搜索到H结点时路线最短 out(tail);head = tail;break;}} }}while(head < tail);
}//主函数
int main()
{memset(s,false,sizeof(s));doit();      //进行广度优先搜索操作(Bfs)return 0;
}

实验结果:

memset()函数

  • 重要:定义变量时一定要进行初始化,尤其是数组和结构体这种占用内存大的数据结构。

    在使用数组的时候经常因为没有初始化而产生“烫烫烫烫烫烫”这样的野值,俗称“乱码”。
    每种类型的变量都有各自的初始化方法,memset() 函数可以说是初始化内存的“万能函数”,通常为新申请的内存进行初始化工作。它是直接操作内存空间,mem即“内存”(memory)的意思。该函数的原型为:

    # include <string.h>          //#include<cstring>:c++
    void *memset(void *s, int c, unsigned long n);
    
    • 函数功能: 将指针变量 s 所指向的前 n 个字节的内存单元用一个 “整数” c 替换(也可以是其他类型),(注:cint 型,svoid * 型的指针变量,所以它可以为任何类型的数据进行初始化。)
    • memset() 的作用是在一段内存块中填充某个给定的值。因为它只能填充一个值,所以该函数的初始化为原始初始化,无法将变量初始化为程序中需要的数据。用memset初始化完后,后面程序中再向该内存空间中存放需要的数据。
    • memset 一般使用“0”初始化内存单元,而且通常是给数组或结构体进行初始化。一般的变量如 char、int、float、double 等类型的变量直接初始化即可,没有必要用 memset。如果用 memset 的话反而显得麻烦。
    • 当然,数组也可以直接进行初始化,但 memset 是对较大的数组或结构体进行清零初始化的最快方法,因为它是直接对内存进行操作的。

    字符串数组最好用 "\0" 进行初始化。

    • 使用 memset() 对字符串数组进行初始化:虽然参数 c 要求是一个整数,但是整型和字符型是互通的。但是赋值为 ‘\0’ 和 0 是等价的,因为字符 ‘\0’ 在内存中就是 0。所以在 memset 中初始化为 0 也具有结束标志符 ‘\0’ 的作用,所以通常我们就写“0”。
    • memset 函数的第三个参数 n 的值一般用 sizeof() 获取。
    • 注意: 如果是对指针变量所指向的内存单元进行清零初始化,那么一定要先对这个指针变量进行初始化,即要让该指针事先指向一个有效的地址。而且如果使用 memset 函数,对指针 p 所指向的内存单元进行初始化时,n 千万别写成 sizeof(p),这是新手经常会犯的错误。因为 p 是指针变量,不管 p 指向什么类型的变量,sizeof ( p) 的值都是 4

    C语言中的指针和数组名不完全等价,不能将它们混为一谈。

编写一个程序:

# include <stdio.h>
# include <string.h>
int main(void)
{int i;  //循环变量char str[10];char *p = str;memset(str, 0, sizeof(str));  //只能写sizeof(str), 不能写sizeof(p)for (i=0; i<10; ++i){printf("%d\x20", str[i]);}printf("\n");return 0;
}

结果:

根据memset函数的不同,输出结果也不同,分为以下几种情况:

  • memset(p, 0, sizeof(p)); //地址的大小都是4字节
    0 0 0 0 -52 -52 -52 -52 -52 -52
  • memset(p, 0, sizeof(*p)); //*p表示的是一个字符变量, 只有一字节
    0 -52 -52 -52 -52 -52 -52 -52 -52 -52
  • memset(p, 0, sizeof(str));
    0 0 0 0 0 0 0 0 0 0
  • memset(str, 0, sizeof(str));
    0 0 0 0 0 0 0 0 0 0
  • memset(p, 0, 10); //直接写10也行, 但不专业
    0 0 0 0 0 0 0 0 0 0

【例2】

一矩形阵列由数字09组成,数字0到9代表细胞,细胞的定义为沿细胞数字上下左右还是细胞数字则为同一细胞,求给定矩形阵列的细胞个数。如:

分析:

  1. 从文件中读入m*n矩阵阵列,将其转换为boolean矩阵存入 bz 数组中;
  2. 沿 bz 数组矩阵从上到下,从左到右,找到遇到的第一个细胞;
  3. 将细胞的位置入队h,并沿其上、下、左、右四个方向上的细胞位置入队,入队后的位置 bz 数组置为 flase
  4. h 队的队头出队,沿其上、下、左、右四个方向上的细胞位置入队,入队后的位置 bz 数组置为 flase
  5. 重复4,直至 h 队空为止,则此时找出了一个细胞;
  6. 重复2,直至矩阵找不到细胞;
  7. 输出找到的细胞数。

代码:

#include<cstdio>using namespace std;//表示上、下、左、右四个方向:{ x, y,-x,-y};
int dx[4] = {-1, 0, 1, 0},dy[4] = { 0, 1, 0,-1};int bz[100][100], num=0, n, m; void doit(int p, int q)
{int x,y,t,w,i;int h[1000][10];num++;bz[p][q]=0;t=0;w=1;h[1][1]=p;h[1][2]=q;       //遇到的第一个细胞入队do{t++;                                          //队头指针加1for (i=0;i<=3;i++)                      //沿细胞的上下左右四个方向搜索细胞{x=h[t][1]+dx[i];y=h[t][2]+dy[i];if ((x>=0)&&(x<m)&&(y>=0)&&(y<n)&&(bz[x][y]))  //判断该点是否可以入队{w++;h[w][1]=x;h[w][2]=y;bz[x][y]=0;}                                         //本方向搜索到细胞就入队}}while (t<w);                            //直至队空为止
}//主函数
int main()
{int i,j; char s[100],ch;scanf("%d%d\n",&m,&n);for (i=0; i<=m-1;i++ )for (j=0;j<=n-1;j++ )bz[i][j]=1;                              //初始化for (i=0;i<=m-1;i++){gets(s); for (j=0;j<=n-1;j++)if (s[j]=='0') bz[i][j]=0;}for (i=0;i<=m-1;i++)for (j=0;j<=n-1;j++)if (bz[i][j])doit(i,j);                                //在矩阵中寻找细胞printf("NUMBER of cells=%d",num);return 0;
} 

结果:


【例3】迷宫问题

如下图所示,给出一个N*M的迷宫图和一个入口、一个出口。
编一个程序,打印一条从迷宫入口到出口的路径。这里黑色方块的单元表示走不通(用 -1 表示),白色方块的单元表示可以走(用 0 表示)。只能往上、下、左、右四个方向走。如果无路则输出 “no way.”

分析:

  • 只要输出一条路径即可,所以这是一个经典的回溯算法问题,

1. 回溯算法的 深度优先搜索 的程序代码:

#include<iostream>using namespace std;int n, m, desx, desy, soux, souy, totstep, a[51], b[51], map[51][51];bool f;
int move(int x, int y, int step)
{map[x][y] = step;         //走一步,做标记,把步数记下来a[step] = x;b[step] = y;        //记路径if((x==desx)&&(y==desy)){f=1;totstep = step;}else{if ((y!=m)&&(map[x][y+1]==0))  move(x,y+1,step+1);         //向右if ((!f)&&(x!=n)&&(map[x+1][y]==0))  move(x+1,y,step+1);   //往下if ((!f)&&(y!=1)&&(map[x][y-1]==0))  move(x,y-1,step+1);   //往左if ((!f)&&(x!=1)&&(map[x-1][y]==0))  move(x-1,y,step+1);   //往上   }
}int main()
{int i, j;cin >> n >> m;for(i=1; i<=n; i++)          //读入迷宫,0表示通,-1表示不通 {for(j=1; j<=m; j++){cin >> map[i][j];}}cout << "input the enter:";cin >> soux >> souy;       //入口cout << "input the exit:";cin >> desx >> desy;      //出口f=0;                       //f=0表示无解,f=1表示找到了一个解move(soux, souy, 1);if(f){//TODOfor(i=1; i<=totstep; i++){//TODOcout << a[i] << "," << endl;}} else    cout << "no way" << endl;return 0;
}

2. 回溯算法的 广度优先搜索 的程序代码:

#include <iostream>
using namespace std;
int u[5]={0,0,1,0,-1},w[5]={0,1,0,-1,0};
int n,m,i,j,desx,desy,soux,souy,head,tail,x,y,a[51],b[51],pre[51],map[51][51];
bool f;
int print(int d)
{if (pre[d]!=0) print (pre[d]);             //递归输出路径cout<<a[d]<<","<<b[d]<<endl;
}
int main()
{int i,j;cin>>n>>m;                                       //n行m列的迷宫for (i=1;i<=n;i++)                               //读入迷宫,0表示通,-1表示不通for (j=1;j<=m;j++) cin>>map[i][j];cout<<"input the enter:";cin>>soux>>souy;                             //入口cout<<"input the exit:";cin>>desx>>desy;                             //出口head=0;tail=1;f=0;map[soux][souy]=-1;a[tail]=soux;  b[tail]=souy; pre[tail]=0;while (head!=tail)                           //队列不为空{head++;for (i=1;i<=4;i++)                         //4个方向{x=a[head]+u[i];  y=b[head]+w[i];if ((x>0)&&(x<=n)&&(y>0)&&(y<=m)&&(map[x][y]==0)){                                                  //本方向上可以走tail++;a[tail]=x;  b[tail]=y;  pre[tail]=head;map[x][y]=-1;if ((x==desx)&&(y==desy))     //扩展出的结点为目标结点{f=1;print(tail);break;}}}if (f) break;}if (!f) cout<<"no way."<<endl;return 0;
}

广度优先算法学习(BFS)相关推荐

  1. 广度优先算法(BFS)入門理解

    简介 广度优先算法(BFS)和深度优先算法(DFS)是图这种数据结构最基础的算法,所以需要我们花心思去学习.能知道BFS和DFS这个名词就证明同学们已经在书上看到了他们的介绍.不过对于初入门的同学来说 ...

  2. ES聚合算法原理深入解读:深度优先算法(DFS)和广度优先算法(BFS)(三)

    本文为:ES聚合算法原理深入解读:深度优先算法(DFS)和广度优先算法(BFS)第三篇 深度优先算法(DFS)和广度优先算法(BFS):DFS 和 BFS 在 ES 中的应用(一) 深度优先算法(DF ...

  3. 广度优先算法(BFS)

    根据访问节点的顺序与方式,可以分为广度优先算法(BFS)和深度优先算法(DFS),本文我打算先介绍广度优先算法: 广度优先算法 1. 算法概述 广度优先搜索算法(又称宽度优先搜索.BFS)是最简便的图 ...

  4. ES聚合算法原理深入解读:深度优先算法(DFS)和广度优先算法(BFS)(一)

    本文为 ES聚合算法原理深入解读:深度优先算法(DFS)和广度优先算法(BFS)第一篇 深度优先算法(DFS)和广度优先算法(BFS):DFS 和 BFS 在 ES 中的应用(一) 深度优先算法(DF ...

  5. 数据结构与算法学习⑤(BFS和DFS 贪心算法 二分查找)

    数据结构与算法学习⑤ 数据结构与算法学习⑤ 1.BFS和DFS 1.1.深度优先搜索算法 1.2.广度优先搜索算法 面试实战 102. 二叉树的层序遍历 104. 二叉树的最大深度 515. 在每个树 ...

  6. 深度优先算法(DFS)和广度优先算法(BFS)时间复杂度和空间复杂度计算精讲

    现在我们设定任务为到山东菏泽曹县买牛逼,需要利用深度优先算法(DFS)和广度优先算法(BFS)在中国.省会.市.区县这张大的树中搜索到曹县,那么这个任务Goal就是找到曹县. 假如图的最大路径长度m和 ...

  7. c语言bfs算法走迷宫,使用广度优先算法(BFS)走迷宫

    前面介绍广度优先算法的时候提及了多次走迷宫,我们就真正的走一次迷宫试试! 要求如下: 输入给出迷宫矩阵的行数和列数,并给出迷宫(使用点 (.) 表示路,使用星 (*) 表示障碍物,使用S表示起点,T表 ...

  8. java广度优先算法,算法之广度优先搜索

    一.引言 上一次介绍的算法是深度优先搜索 这次我们来研究一下广度优先搜索,看看怎么理解以及写出这个算法 这个算法需要数据结构的基础--队列,如果没有这个基础的同学去恶补一下. 二.小小问题 Q:在一个 ...

  9. 深度广度优先算法、A*算法

    深度广度优先算法.A*算法 深度优先算法(DFS) 实现步骤 实现代码 广度优先算法(BFS) 实现步骤 实现代码 A*算法 Dijkstra算法 A*算法介绍 实现流程 实现过程 实现代码 深度优先 ...

最新文章

  1. ThinkPHP快捷方法使用总结
  2. jmeter—操作数据库
  3. 关于ejabberd限制单点登录
  4. Bootstrap4+MySQL前后端综合实训-Day10-AM【实训汇报-下午返校、项目代码(7个包+7个Html页面)】
  5. 怎么清理文件缓存文件云服务器,服务器运行内存怎么清理缓存
  6. 路飞学城-python爬虫密训-第三章
  7. mysql 强制读主库_laravel(lumen)配置读写分离后,强制读主(写)库数据库,解决主从延迟问题...
  8. dedecms 在模板里引入php文件夹,dedecms如何添加并引入php文件
  9. 博客园的第一篇文章-----述学习编程的开始与经历
  10. hadoop eclipse插件
  11. 关于ext4 simg fill chunk type
  12. linux终端 图形库,Linux终端图形库 Curses简介和实例分析
  13. 七个国外免费杀毒软件
  14. 装双系统win10和android,厉害了小米6!不仅骁龙835,还支持安卓和win10双系统
  15. unity3D一些教程
  16. VMware 14 Pro 虚拟机下CentOS 7操作系统安装教程
  17. php 将数字转为大写,将数字小写转为大写 php
  18. Python迭代器和生成器详解(包括yield详解)
  19. 公益中国系列活动进社区之 “健康进社区”系列活动
  20. 《信息安全系统设计基础》第1周问题总结

热门文章

  1. oracle 0-5-1-中文注解
  2. “5G新体验,合作赢未来”中国移动携合作伙伴打造5G合作生态
  3. MySQL与mybatis查询精华(本人总结)持续更新中
  4. 路透社:网约车巨头Lyft上市 日本乐天赚近10亿美元
  5. 点击Visual Studio 2019启动项无反应,无法启动
  6. 安装Chrome的时候提示“已安装更高版本的Google Chrome”
  7. 删除的文件如何恢复? 5种简单数据恢复方法分享
  8. linux 下qq自动退出解决方法
  9. 简单的图片格式转换器
  10. 【调剂】福州大学紫金地矿学院采矿系李兵磊组招生