算法学习广深度优先搜索算法

Posted 一个傻子的随记

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法学习广深度优先搜索算法相关的知识,希望对你有一定的参考价值。

1

广、深度优先搜索算法


——算法学习

#本文编抄自互联网,习题改编自《啊哈!算法》,在此鸣谢大叔安利。#



想走出RPG里诡异的迷宫吗?


想在迷宫里解救心爱的公主吗?


想在鲍鱼海域的孤岛上立足吗?


想接好一片水管吗?(这好像不太想)


那就千万别放过这篇推文!


【算法学习】广、深度优先搜索算法



【算法学习】广、深度优先搜索算法


上次说过,程序的学习有三个角度。在尝试了计算器程序后我再也不想解决这种问题了。


【算法学习】广、深度优先搜索算法

 

然后我准备开始学习算法。(当然也是小白算法)

 

准备从最基础的开始学,就慢慢学吧。


所以这次就来个简单的问题。

 

不同于上一篇,这次的文章不学C++的,学C的,甚至是别的语言的应该都能看(dl就不必来嘲讽辣),而且应该能看懂大部分。

 

开始安利了~


【算法学习】广、深度优先搜索算法


【算法学习】广、深度优先搜索算法


【算法学习】广、深度优先搜索算法

目录

01搜索算法

02算法简介

03问题讲解

04总结分析



01

搜索算法



搜索算法是利用计算机的高性能来有目的地穷举一个问题解空间的部分或所有的可能情况,从而求出问题的解的一种方法。

 

简而言之,就是穷举所有可能情况并找到合适的答案,所以最基本的问题就是罗列出所有可能的情况。

 

我们将所要解答的问题划分成若干个阶段或者步骤,当一个阶段计算完毕,下面往往有多种可选选择,所有的选择共同组成了问题的解空间。

 

在大规模实验环境中,通常通过在搜索前,根据条件降低搜索规模;根据问题的约束条件进行剪枝;利用搜索过程中的中间解,避免重复计算这几种方法进行优化。

 

看不懂?不懂没关系,我也不懂。反正就是了解一下大概啦。



02

算法简介



深度优先搜索(Depth First Search ,DFS)是由John E.Hopcroft和RobertE.Tarjan发现的。1971-1972他们在斯坦福大学研究图的连通性和平面性的边相互不交叉,在电路板上设计布线的时候,发现的这个奇妙的算法。

 

用一句话概括深度优先搜索,就是“不撞南墙不回头”。

 

它的基本思想是:

step 1       先选择某一种可能情况向前探索,并生成一个子节点。

step 2       探索过程中,一旦发现原来的选择不符合要求,就回溯至父亲结点重新选择另一方向,重新生成子结点,继续向前探索。

step 3       如此反复进行,直至求得最优解。

 

(据说在开发爬虫早期使用较多,但我也不知道诶╮(╯_╰)╭  )

 

广度优先搜索(Breadth First Search,BFS)由 Edward F. Moore 在 1950 年发表,起初被用于在迷宫中寻找最短路径。

 

它的基本思想是:

step 1       访问初始点,并将其标记为已访问过。

step 2       访问初始点的所有未被访问过可到达的相邻的点,并均标记为已访问过。

step 3       按照第一次访问的次序,访问每一个点的所有未被访问过的相邻的点,并均标记为已访问过。

step 4       依此类推,直到图中所有和初始点有路径相通的顶点都被访问过为止。

 

怎么样,有没有看懂呢?


应该大概有了一点感觉了。那接下来我们就在具体问题中开始分析吧。



03

具体问题



1

深度优先搜索解决纸牌问题:



问题描述:输入一个1~9的正整数n,输出1~n的这n个正整数的全排列。

抽象成纸牌问题:有n个盒子,手中有n张牌,把牌放入盒子中。


我们基于深度优先搜索的思想,制定一些放牌规则,让一次放牌能涵盖所有可能的情况:







我们把这n个箱子中的数看作一个整数,实际上,每一次放完手中的牌(即走到虚拟的六号箱时)所构成的整数是按从小到大排列的。所以,一定能包含所有的排列方式。

 

例:

step 1       放入1   ;放入2   ;放入3   。

牌的情况:1  2  3

step 2       收回3(不放入,手中无牌)   ;收回2   ;放入3   。

牌的情况:1  3  2  

step 3       收回2   ;收回3(不放入,手中的2比收回的3小)   ;收回1,放入2   ;放入1   ;放入3   。

牌的情况:2  1  3

。。。。。。

 

看到这里,是不是大致了解了呢?


我们可以写出DFS的伪代码:

Void dfs(step)

{

判断边界;

For (尝试所有可能)

     当下如何做

     下一步如何做(调用dfs(step+1)函数)

 

 

2

深度优先搜索解救小欣问题:


 

问题描述:呆呆的小欣在迷宫里迷路了。小周得知后便立即去解救小欣。假如小周已经知道迷宫的地图和小欣的位置,且迷宫中有障碍物,小周是不能够飞过障碍物的(小周很想飞过去)。小周怎样走能够最快的找到小欣?

 

地图由一个二维数组组成,用0表示可到达的位置,1表示不可到达的位置。

 

我们利用深度优先搜索的思想给出行动规则:

  • 运动方向的优先度按顺时针排列分别尝试往前走。

  • 走到终点时回头;尝试过所有方向后回头。

  • 回头时,下一次只向比原先方向优先度低的方向前进。

容易理解这种走法可以遍历全图。

 

我们按照之前的框架写出解救小欣问题的伪代码:

 

Void dfs(step)

{

判断是否到达小欣的位置;

For (尝试上下左右四个方向)

     If(走到过这个位置)

     If(没有越界)

     If(不是墙)

     标记该点走过

     前进到下一个点(即调用dfs(step+1)函数)

 

 

3

广度优先搜索解救小欣:


 

按照广度优先搜索的方式,遍历全图是没有问题的。

我们运用队列存储每一个未经判断的点。

 

#队列:利用数组和头尾两个指针head,tail(不是*p的指针,人为定义的两个指示位置的指标)。队列只能从尾进入从头出来。出队是head++,入队是将数据存入数组,tail++。(tail一般指向空)#

 

 

伪代码:

 

//在队列外的主函数中初始化队列,输入第一组数据,略

 

Void bfs()

{

While(队列中有数据){

    For(尝试所有可能){

    If(满足条件)

       存入新点

    If(到达终点)

       退出循环

        去掉旧点

 

我们再次按照之前的框架写出解救小欣问题的伪代码:

 

Void bfs()

{

While(head<tail){

    For(尝试四个方向){

    If(没有越界,没有碰到障碍物,没有走过)

       将新坐标入列

    If(到达终点)

       标记小欣的位置,退出循环

       去掉旧点

 

 

习题·鲍岛逃生:小周落入了一片海域,周围全是鲍鱼。小周很想知道自己能在远离鲍鱼的陆地上有多少活动空间。

 

鲍鱼生活的海域用0表示,陆地用1表示。


开动你的智慧,从鲍岛中找到立足之地吧!

 

#如果陆地用不同数字表示海拔,我们可以用搜索算法将陆地染为同色。这就是油漆桶、魔棒工具(加上一个范围)的算法,也叫FloodFill 漫水填充法(也称种子填充法)。#



4

水管工游戏


游戏大致规则是:小周家是N*M的矩形,现在家低下已经埋设了一些水管。

水管将从矩形土地的左上角左部边缘(从左进),延伸到右下角右部边缘(从右出)。

水管只有两种:弯管和直管

弯管有四种状态:∟及其90,180,270度旋转(自行脑补)。

直管有两种状态:一、|。

在地图中1,2,3,4,5,6分别表示:1,2,3,4表示弯管四种状态。5,6表示直管两种状态。

程序需要判断进水口:进水口在左边用1表示;在上边用2表示;在右边用3表示;在下边用4表示。

 

 

啊哈算法中给出了深度优先搜索的C语言程序代码:


/*水管工:铺设管道,求输出路径*/
#include<stdio.h>
int a[51][51];//假设土地大小不超过50*50int book[51][51];int n, m, flag = 0;
//加入一个栈struct note{ int x;//横坐标 int y;//纵坐标}s[100];int top = 0;void dfs(int x, int y, int front)//front:定义四个方向左上右下分别为1234{ int i; //判断是否到达终点,(n,m+1) //另外判断是否到达终点必须放在越界前面判断 if (x == n && y == m + 1) { flag = 1; //找到铺设方案 for (i = 1; i <= top; i++) printf(" (%d,%d) ", s[i].x, s[i].y); printf(" "); return; }
//判断是否越界 if (x<1 || x>n || y<1 || y>m) return; //判断这个管道是否在路径中已经使用过 if (book[x][y] == 1) return; book[x][y] = 1;//标记使用当前这个管道
//将当前尝试的坐标入栈 top++; s[top].x = x; s[top].y = y;
//当前管道是直管情况 if (a[x][y] >= 5 && a[x][y] <= 6) { //直管有四个方向,需要逐个判断 if (front == 1) //水从左边进 { dfs(x, y + 1, 1);//只能使用5号这种摆放方式 }
if (front == 2)//水从上边进 { dfs(x + 1, y, 2);//6号 }
if (front == 3)//水从右边进 { dfs(x - 1, y, 3);//5号 }
if (front == 4)//水从下边进 { dfs(x, y - 1, 4);//6号 }
}
//当前水管是弯管的情况 if (a[x][y] >= 1 && a[x][y] <= 4) { if (front == 1)//水从左边进 { dfs(x + 1, y, 2); //3号 dfs(x - 1, y, 4);//4号 } if (front == 2)//水从上边进 { dfs(x, y + 1, 1);//1号 dfs(x, y - 1, 3);//4号 } if (front == 3)//水从右边进 { dfs(x - 1, y, 4);//1号 dfs(x + 1, y, 2);//2号 } if (front == 4)//水从下边进 { dfs(x, y + 1, 1);//2号 dfs(x, y - 1, 3);//3号 }
} book[x][y] = 0;//取消标记 top--;//将当前尝试的坐标出栈 return;}
int main(){ int i, j, num = 0; scanf_s("%d %d", &n, &m);
//读入游戏地图 for (i = 1; i <= n; i++) for (j = 1; j <= m; j++) scanf_s("%d", &a[i][j]);
//开始搜索,从(1,1)点开始,进水方向是1 dfs(1, 1, 1); //判断是否找到铺设方案 if (flag == 0) printf("impossible "); else printf("管道铺设成功 ");
getchar(); getchar(); return 0;}



我们也可以尝试用广度优先搜索算法来写这个问题的代码。

#往下看之前可以先思考一下为什么书中用的是深度而不是广度。#

 

在用广度写时,我们会遇到一个问题。

先写出思路:

尝试所有情况,找出下一个点

判断是否符合条件

标记入列

判断终点

 

下面问题来了:在管道是弯管时,下一个点有两种情况。而我们以下的几步,都是针对一个具体的点进行的。怎么办?从这里开始分类吗?代码会很冗长;装订成函数?好像也挺恶心的。

 

于是我尝试把过程换一换:

判断上一个点是否可行

不可,用一个temp记录上一次的tail,tail=temp,break

可以,找出下一点

标记入列

判断终点

 

这就相当于把判断的过程延后。

但是,用DFS写就不会有这个问题,因为DFS只要在不同情况下调用递归函数就好了,很轻松地解决了多种情况的问题。

 

接下来给出我写的C++部分代码(这样学C和C++的同学就都可以看了哈):


 

//BFS解水管工问题部分代码
class note(){ int x; int y; int front; //前一点水管的位置 }
while (head<tail){ tx=que[head].x; ty=que[head].y; while (true){ if(x<1 || x>n || y<1 || y>m ) //注意,这样判断会导致数组越界,因此在定义是要多定义一些 tail=temp;break; temp=tail; //更新标记 //当前水管是直管的情况 if (pipe[x][y] >= 5 && pipe[x][y] <= 6){ //进水有四个方向,需要逐个判断 if (que[head].front == 1)//水从左边进 { que[tail].y=ty+1; que[tail].x=tx; que[tail].front=1; tail++; } //其余略~~~ } //当前水管是弯管的情况 if (a[x][y] >= 1 && a[x][y] <= 4){ if (front == 1)//水从左边进 que[tail].y=ty; que[tail].x=tx+1; que[tail].front=2; //弯管有两种情况,应入队两个点 tail++ ; que[tail].y=ty; que[tail].x=tx+1; que[tail].fornt=4; tail++; //其余略~~ if (tx=n&&ty+1=m) //判断终点 { flag=true break; } } head++ }



注意,这个代码我没试过,可能是错误百出,请小心食用哦~~



04

总结分析



深度优先的核心是递归,或者说栈。

广度优先的核心是队列,或者链表。

 

深度遇到一个点的多种情况时直接递归到下一步即可。

广度遇到一个点的多种情况时需要把所有所有情况置入队列中,并分别判断。

 

深度优先不保留全部结点,扩展完的结点从数据库中弹出删去,这样,一般在数据库中存储的结点数就是深度值,因此它占用空间较少。

所以,当结点较多时,用其它方法易产生内存溢出时,深度优先有效。

 

广度优先一般需存储产生的所有结点,占用的存储空间要比深度优先搜索大得多,因此,程序设计中,必须考虑溢出和节省内存空间的问题。

广度优先一般无回溯操作,即入栈和出栈的操作,所以运行速度比深度优先搜索要快些。


1


那么,这篇文章就到此结束了!


下期提示:单源路径最短算法


算法学习中,代码未编写;更新未知数,拖延无极限。


那么,我什么时候才能开始本周的学习内容禁忌搜索?


哎,太了。


点击下图查看往期内容:


【水货】从“计算器”中学C++


【舟寒的读书笔记】孤独于心,踏碎这一场,盛世烟花




1


#

-The End-

文/怎么学都学不会C++的傻子

版/怎么赶都赶不上进度的傻子

码/被武汉的天气冻的快死了的傻子

审/抱歉还是没人审核(傻子还是懒得审)

#



喜欢de请点在看



以上是关于算法学习广深度优先搜索算法的主要内容,如果未能解决你的问题,请参考以下文章

数据结构学习笔记——图的遍历算法(深度优先搜索和广度优先搜索)

数据结构与算法图遍历算法 ( 深度优先搜索代码示例 )

关于广/宽度优先搜索

深度优先搜索算法解释下?

集册 | Java 实现图的广度优先搜索(BFS)算法,一起深度学习算法思维!

广度优先搜索与深度优先搜索