搜索---广度优先遍历、深度优先遍历、回溯法

参考文章:https://github.com/CyC2018/CS-Notes/blob/master/notes/Leetcode%20%E9%A2%98%E8%A7%A3%20-%20%E6%90%9C%E7%B4%A2.md

广度优先搜索(BFS)

广度优先搜索是按层来处理顶点的,距离开始点最近的那些顶点首先被访问,而最远的那些顶点则最后被访问。BFS的代码使用了一个队列。搜索的步骤:

  1. 首先选择一个顶点作为起始顶点,将起始顶点放入队列中
  2. 从队列首部选出一个顶点,将与之相邻并且没有被访问的结点依次加入到队列的队尾,然后访问这些与之相邻并且没有被访问过的结点,将队列队首的结点删去。
  3. 按照步骤2处理队列中下一个结点,直到找到要找的结点或者队列中没有结点结束。
void BFS()
{定义队列;定义备忘录,用于记录已经访问的位置;判断边界条件,是否能直接返回结果的。将起始位置加入到队列中,同时更新备忘录。while (队列不为空) {获取当前队列中的元素个数。for (元素个数) {取出一个位置节点。判断是否到达终点位置。获取它对应的下一个所有的节点。条件判断,过滤掉不符合条件的位置。新位置重新加入队列。}}}

我们用一道LeedCode上面的题目讲解,题目位置:

https://leetcode-cn.com/problems/shortest-path-in-binary-matrix/
这里我们需要注意三点:

  1. 需要一个队列,来记录下一次访问的结点,因为该队列是记录结点位置的(访问结点的下标),如果一维数组可以搞定,定义个int queue[QUEUE_SIZE],如果是二维,定义int queue[QUEUW_SIZE][2].
  2. 需要一个备忘录,记录已经被访问的结点,备忘录用数组表示
  3. 每一层结点数就是可以访问的结点,这里题目是 8 个方向上的单元格
#include <stdio.h>
#include <string.h>
#include <stdlib.h>#define QUEUE_SIZE 10001
int BFS()
{int grid[3][3] = {{0,0,0},{1,1,0},{1,1,0}};int gridSize=3;int ans=0; /*判断边界条件,即结束条件*/if(grid[gridSize-1][gridSize-1]==1 || grid[0][0]==1)return -1;/* 获取队列 */if(gridSize==1)return 1;int m=gridSize,n=m*m,front=0,rear=0,pre_rear=0,cnt=1;/* 设置队列 */ int **cularr = (int **)malloc(sizeof(int)*n);for(int i=0;i<n;i++){cularr[i]=(int *)malloc(sizeof(int)*2);}/* 将首结点入队 并将其设置已经访问过了*/cularr[rear][0]=0;cularr[rear++][1]=0;/*将其修改为1  表示这个数据不需要在访问 */grid[0][0]=1;/* 根据题目要求广度优先都要有8个结点 */int temp[8][2] = {{-1,-1},{0,-1},{1,-1},{1,0},{1,1},{0,1},{-1,1},{-1,0}};while(front<rear){pre_rear = rear;/* 遍历每一层上的结点 */while(front<pre_rear){for(int i=0;i<8;i++){int x = cularr[front][0]+temp[i][0];int y = cularr[front][1]+temp[i][1];//此时是最后一个结点if((m-1==x)&&(m-1==y)){return cnt+1;}//将没有访问符合的符合条件的加入队列中if(x<m && x>=0 && y<m && y>=0 && grid[x][y]==0){cularr[rear][0]=x;cularr[rear++][1]=y;grid[x][y]=1;}}front++;   //获取下一个队首元素}cnt++;     //广度优先遍历了一层,要加1}return -1;}int main(void)
{int num = BFS();printf("%d\n",num);return 0;
}

深度优先遍历(DFS)

在这里插入图片描述

广度优先搜索一层一层遍历,每一层得到的所有新节点,要用队列存储起来以备下一层遍历的时候再遍历。

而深度优先搜索在得到一个新节点时立即对新节点进行遍历:从节点 0 出发开始遍历,得到到新节点 6 时,立马对新节点 6 进行遍历,得到新节点 4;如此反复以这种方式遍历新节点,直到没有新节点了,此时返回。返回到根节点 0 的情况是,继续对根节点 0 进行遍历,得到新节点 2,然后继续以上步骤。

从一个节点出发,使用 DFS 对一个图进行遍历时,能够遍历到的节点都是从初始节点可达的,DFS 常用来求解这种 可达性 问题。

在程序实现 DFS 时需要考虑以下问题:

  • 栈:用栈来保存当前节点信息,当遍历新节点返回时能够继续遍历当前节点。可以使用递归栈。
  • 标记:和 BFS 一样同样需要对已经遍历过的节点进行标记。
  • 每个结点有多少个子树(也就是一个结点遍历完,深度优先遍历时,有几个可选的结点可以遍历,比如上图的0,可以有1、2、5、6四个结点可以选择)

我们用LeedCode上面的题目来讲解:

695. 岛屿的最大面积

#include <stdio.h>
#include <stdlib.h>int dfs(int **grid,int gridSize,int *gridColSize,int row,int col)
{//条件判断if(row<0 || row>=gridSize || col<0 || col>=gridColSize[0] || grid[row][col]==0){return 0;}//将1置为0,表示已经访问过该结点grid[row][col]=0;//遍历所有可以遍历的情况return 1+dfs(grid,gridSize,gridColSize,row,col+1)+dfs(grid,gridSize,gridColSize,row,col-1)+dfs(grid,gridSize,gridColSize,row+1,col)+dfs(grid,gridSize,gridColSize,row-1,col);
}int maxAreaOfIsland(int** grid, int gridSize, int* gridColSize) 
{int area=0,max=0,i,j;for(i=0;i<gridSize;i++){for(j=0;j<gridColSize[0];j++){area = dfs(grid,gridSize,gridColSize,i,j);max = max>area?max:area;}}return max;
}int main(void)
{int grid[][13] = {{0,0,1,0,0,0,0,1,0,0,0,0,0},{0,0,0,0,0,0,0,1,1,1,0,0,0},{0,1,1,0,1,0,0,0,0,0,0,0,0},{0,1,0,0,1,1,0,0,1,0,1,0,0},{0,1,0,0,1,1,0,0,1,1,1,0,0},{0,0,0,0,0,0,0,0,0,0,1,0,0},{0,0,0,0,0,0,0,1,1,1,0,0,0},{0,0,0,0,0,0,0,1,1,0,0,0,0}};int gridColSize =13;int **p = malloc(sizeof(int)*8);for(int i=0;i<8;i++){p[i]=grid[i];}int max = maxAreaOfIsland(p,8,&gridColSize);printf("max=%d\n",max);return 0;}

求岛屿最大面积,也就是从一个结点出发,我们按前后左右方向走,可以走的最大格子数(可达最大区域)。

我们访问过一个位置后,使用深度优先遍历的话,我们可以有四个选择,也就是水平或者竖直的四个方向上,这对应深度优先遍历,就是每个结点都有四个子树。
对于可以访问的结点,访问过后,我们将其值1置为0,表示已经访问过了(0在这个题目当中不需要访问)。
对于栈,这题我们用递归,因为有四个选择,我们在递归时,需要加上四个dfs函数。
结果:
在这里插入图片描述

回溯法

Backtracking(回溯)属于 DFS。

  • 普通 DFS 主要用在 可达性问题 ,这种问题只需要执行到特点的位置然后返回即可。
  • 而 Backtracking 主要用于求解 排列组合 问题,例如有 { ‘a’,‘b’,‘c’ } 三个字符,求解所有由这三个字符排列得到的字符串,这种问题在执行到特定的位置返回之后还会继续执行求解过程。

因为 Backtracking 不是立即返回,而要继续求解,因此在程序实现时,需要注意对元素的标记问题:

  • 在访问一个新元素进入新的递归调用时,需要将新元素标记为已经访问,这样才能在继续递归调用时不用重复访问该元素;
  • 但是在递归返回时,需要将元素标记为未访问,因为只需要保证在一个递归链中不同时访问一个元素,可以访问已经访问过但是不在当前递归链中的元素。
def backtrack(路径, 选择列表):if 满足结束条件:result.add(路径)returnfor 选择 in 选择列表:做选择backtrack(路径, 选择列表)撤销选择以上三种定义:
1、路径:也就是已经做出的选择
2、选择列表:也就是当前可以做的选择
3、结束条件:也就是到达决策树底层,无法再做选择的条件
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
/*** 本解答模仿 https://www.cnblogs.com/wuyuegb2312/p/3273337.html 编写* 本解答以供自己学习提升和分享 MasterXU*/
static char g_ch[10][4] = {"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz",
};
static int g_total[10] ={0,0,3,3,3,3,3,4,3,4}; 
/*** number: 输入待转换数字* answer:解空间,存放路径上每个节点的选择,由于是递归实现 需要保存1条路径节点即可* index:  当前深度* depth:  最大路径深度* ans:    返回结果* pathIdx:当前路径编号*/
void recursive(char *number, int *answer, int index, int depth, char **ans, int *pathIdx)
{//当当前深度等于最大深度时,表示此时DFS遍历完一条路径了,需要将这条路径的元素记录if(index == depth){//分配存放该路径上元素的内存ans[*pathIdx] = (char *)malloc((depth+1)*sizeof(char));//获取该路径每层的元素,number[i]-'0'表示选的层数,answer[1]表示选该层的元素for(int i=0;i<depth;i++){ans[*pathIdx][i]=g_ch[number[i]-'0'][answer[i]];}ans[*pathIdx][depth]='\0';//下一次记录下一路径的元素(*pathIdx)++;//返回表示该路径已经记录完毕return;}/*index+1:表示遍历下一层answer[index+1]++:遍历下一个兄弟结点 */for(answer[index]=0;answer[index]<g_total[number[index]-'0'];answer[index]++){recursive(number,answer,index+1,depth,ans,pathIdx);}
}/*** Note: The returned array must be malloced, assume caller calls free().*/
char ** letterCombinations(char * digits, int* returnSize){int a[100] = {0};   //记录深度遍历每条路径上结点的位置int depth = strlen(digits); //深度优先遍历的最大深度int num = (int)pow(4,depth);    //深度优先遍历的最多路径个数*returnSize = 0;    //路径计数if(depth == 0)return NULL;char **ans = (char **)malloc(num*sizeof(char *));recursive(digits,a,0,depth,ans,returnSize);return ans;}int main(void)
{char *digits = "23";int returnSize =0;char **ans = letterCombinations(digits,&returnSize);printf("returnSize = %d\n",returnSize);for(int i =0;i<returnSize;i++){puts(ans[i]);}return 0;
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/379634.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何更改Visual Studio 2008中类文件引用的默认名称空间?

在编写程序的时候&#xff0c;如果某些名称空间经常用到&#xff0c;每次创建一个文件的时候&#xff0c;都需要手工添加名称空间&#xff0c;是不是很烦人呢&#xff1f;多说人会回答&#xff1a;是的。如果新建文件的时候就自动加上自己需要的名称空间该多好啊。&#xff1a;…

Java ClassLoader findLoadedClass()方法与示例

ClassLoader类findLoadedClass()方法 (ClassLoader Class findLoadedClass() method) findLoadedClass() method is available in java.lang package. findLoadedClass()方法在java.lang包中可用。 findLoadedClass() method is used to return the Class with the given binar…

Linux内核设计与实现---内存管理

内存管理1 页2 区3 获得页获得填充为0的页释放页4 kmalloc()gfp_mask标志kfree()5 vmalloc()6 slab层slab层的设计7 slab分配器的接口8 在栈上的静态分配9 高端内核的映射永久映射临时映射10 每个CPU的分配11 新的每个CPU的接口编译时的每个CPU数据运行时每个CPU数据12 使用每个…

多语言开发 之 通过基页类及Session 动态响应用户对语言的选择

在用户通过UserLogin.aspx登录系统时 提供其对语言的选择选择后 将所选存入Session 以便登录系统后的其他页面进行按语言显示当然相关页面需要支持多语言具体信息可参看使用 根据语言环境不同 而显示不同的 资源本地化 ASP.NET 网页 App_Code下定义基页类 BasePage.cs Codeusin…

Java ClassLoader findSystemClass()方法与示例

ClassLoader类findSystemClass()方法 (ClassLoader Class findSystemClass() method) findSystemClass() method is available in java.lang package. findSystemClass()方法在java.lang包中可用。 findSystemClass() method is used to find the class with the given binary …

TFS 链接不上

C:\Users\Administrator>net use 会记录新的网络连接。 列表是空的。 C:\Users\Administrator>net use \\192.168.1.61\ipc$ wangkun /user:wangkun 命令成功完成。 转载于:https://www.cnblogs.com/rhythmK/archive/2012/06/04/2534066.html

Linux内核设计与实现---虚拟文件系统

虚拟文件系统1 通用文件系统2 文件系统抽象层3 Unix文件系统4 VFS对象及其数据结构其他VFS对象5 超级快对象超级块操作6 索引节点对象索引节点操作7 目录项对象目录项状态目录项缓存目录项操作8 文件对象9 和文件系统相关的数据结构10 和进程相关的数据结构11 Linux中的文件系统…

Oracle 查询历史数据(转帖)

回复误删除数据信息。 1、执行 alter table table_name enable row movement; 2、执行 FlashBack table table_name to timestamp to_timestamp(2012-05-24 14:59:36,yyyy-mm-dd hh24:mi:ss); 查询历史操作数据信息。 比较合理的方法是先从闪回区查找出被误删的数据&#xff0c…

Java里面的几种路径的区别

1&#xff0c;相对路径 相对路径就是指由这个文件所在的路径引起的跟其它文件&#xff08;或文件夹&#xff09;的路径关系。 也就是说&#xff1a; 对于如图所示&#xff1a;一news.html为例 在WEB15工程下的WebContent下的WEB-INF下的news.html 当我访问的news.html的时候…

Linux内核设计与实现---块I/O层

块I/O层1 解刨一个块设备2 缓冲区和缓冲区头3 bio结构体新老方法对比4 请求队列5 I/O调度程序I/O调度程序的工作Linus电梯最终期限I/O调度程序预测I/O调度程序完全公正的排队I/O调度程序空操作的I/O调度程序I/O调度程序的选择系统中能够 随机访问 固定大小数据片的设备被称为块…

Java字符类isUpperCase()方法与示例

角色类isUpperCase()方法 (Character class isUpperCase() method) isUpperCase() method is available in java.lang package. isUpperCase()方法在java.lang包中可用。 isUpperCase() method is used to check whether the given char value is uppercase or not. isUpperCas…

javascript:history.go()和History.back()的区别

javascript:history.go()和History.back()的区别收藏 <input typebutton value刷新 οnclick"window.location.reload()"> <input typebutton value前进 οnclick"window.history.go(1)"> <input typebutton value后…

sql查询行转列

--SQL 面试题 /* 问题&#xff1a;假设有张学生成绩表(tb)如下: 姓名 课程 分数 张三 语文 74 张三 数学 83 张三 物理 93 李四 语文 74 李四 数学 84 李四 物理 94 想变成(得到如下结果)&#xff1a; 姓名 语文 数学 物理 ---- ---- ---- ---- 李四 74 84 94 张三 74 83 93 --…

算法---数

数1 最大公约数2 最小公约数3 进制转换4 阶乘统计阶乘尾部0的个数5 字符串加法减法二进制加法6 多数投票问题数组中出现次数多于n/2的元素7 相遇问题改变数组元素使所有元素都相同1 最大公约数 欧几里得算法&#xff1a;两个整数的最大公约数等于其中较小的那个数和两数相除余…

Java ByteArrayOutputStream reset()方法及示例

ByteArrayOutputStream类reset()方法 (ByteArrayOutputStream Class reset() method) reset() method is available in java.io package. reset()方法在java.io包中可用。 reset() method is used to reset this stream (i.e. it removes all currently consumed output in thi…

使用存储过程修改sql server 2005 用户密码

exec sp_password null,新密码,用户名转载于:https://www.cnblogs.com/SXLBlog/archive/2009/07/10/1520590.html

winform TopMost

当设置winform窗体的TopMost属性为true时&#xff0c;有时会出现不好使的状况&#xff0c;这可能是因为窗体设置了MdiParent属性。转载于:https://www.cnblogs.com/magic-cube/archive/2012/06/10/2544216.html

Linux内核设计与实现---进程地址空间

进程地址空间1 内存描述符分配内存描述符销毁内存描述符mm_struct与内核线程2 内存区域VMA标志VMA操作内存区域的树形结构和内存区域的链表结构3 操作内存区域find_vma()find_vma_prev()find_vma_intersection()4 mmap()和do_mmap()&#xff1a;创建地址空间mmap&#xff08;&a…

JavaScript中带有示例的Math.log10()方法

JavaScript | Math.log10()方法 (JavaScript | Math.log10() Method) Math operations in JavaScript are handled using functions of math library in JavaScript. In this tutorial on Math.log10() method, we will learn about the log10() method and its working with e…

JSP技术

一、jsp脚本和注释 jsp脚本&#xff1a; 1&#xff09;<%java代码%> ----- 内部的java代码翻译到service方法的内部 2&#xff09;<%java变量或表达式> ----- 会被翻译成service方法内部out.print() 3&#xff09;<%!java代码%> ---- 会被翻译成servlet的成…