您好,感谢您一直对少儿编程、对“与非学堂”的关注。为了更好地为大家服务,诚邀您填写一份《关于少儿编程教与学的课件资源素材需求调查》,https://www.wjx.cn/jq/82398684.aspx。
问卷二维码
课件属性分析(每项数值最高都为10):
-
难易度:8(适用于已掌握函数、类、队列等知识的学生)
-
趣味性:5(中等)
-
讲授性:7(需要老师比较多的讲解,学生听讲理解、接受)
-
启发性:5(使用了2次关键的选择题,3次关键的问答题)
故事背景:
小D同学总是脑洞大开,他设计了这样一个游戏:在游戏界面上画上了8*8的格子,游戏角色I在其中一个格子里,它可以一次往上下左右没有墙阻挡的地方走一格。地图上的O表示宝藏所在的位置。而W的位置表示不可穿越的墙。小D同学想让角色I在程序开始后,自动找到能够到达O处最短的一条路径。
作品效果要求:
完成程序的核心逻辑部分:输入一幅地图,I和O以及W的位置随意安排,W的个数任意,程序自动输出:从I到达O处按最短路线前进,需要走的步数。
数据输入保证只有一个I和O,并且一定有至少一条从I到O的路径。
课堂教学流程:
今天我们的任务仅需要完成游戏程序的核心逻辑部分就可以了,不需要使用游戏框架把游戏做成界面显示出来。同学们要相信,只要把核心逻辑完成了,游戏就已经完成70-80%了,界面的工作属于锦上添花了。
要让角色I到O,找到按最短的路线前进需要的步数,应该怎么计算呢?注意我们只要求得到这个步数就可以了,而不需要实际输出这条路线,也不需要真的有一个界面,让角色真正走起来。得到最短路线的步骤,是我们系列任务中最简单的一个。
(请同学们先回答这个问题后继续后面的内容)
先人工看一下,从I到O,最少需要走多少步呢?
A. 8
B. 9
C. 10
D. 11
大家能否数对啊?应该是9步。
大家看,这样一个问题其实对聪明的同学们而言,简单太容易了,对吧?
但是,大家不要忘了我们的任务,不是让同学们自己数出来,而是让计算机也能够根据地图的样子,自动地数出来。
当地图上I、O、W等变换位置的时候,我们都得让计算机一下子计算出这个步数来。
意思就是说,咱们的程序需要把我们人脑解决这个问题的步骤,原封不动地用代码写出来。
其实大家已经知道了,这种解决一个问题的步骤,就叫算法。
咱们怎么用代码设计出这个寻找最短路线所用步数的算法呢?
把我们人脑解决这个问题的步骤,用代码写出来,从这个分析我们已经知道了,完成任务至少需要两大步:
1. 详细地思考,人脑到底是怎么解决这个问题的?
2. 怎么把解决这个问题的过程,用代码写出来。
那么我们首先要看第一大步:人脑到底是怎么解决这个问题的呢?
(请同学们直接回答)
大家的回答五花八门啊!老师从大家的回答中,挑出了这样一些有意思的思路:
——直接看出来最短路线,再数。
——大概看一下总共有几条路线,数完再比较。
——倒着从O走到I,看哪条路最近。
——先知道O在I的哪个方向,比如说例子中的O在I的右上方位,那么我们走的时候尽量往右和往上走。
…… ……
大家回答的这些,非常好啊!每一位同学都是动了脑筋好好思考过的,值得表扬!
不过啊,大家的想法里面,有一些是可行的,有一些呢却不是那么准确,也不能解决问题。
比如说“尽量往右和往上走”的这种办法,什么叫尽量呢?万一我们偏偏只能往左走了很远再拐到左上角再往右才能到达O呢?或者先往下才有通路。“尽量”这个词太模糊了。
再比如说“倒着从O走到I”,这个也不是特别可行。如果你正着从I到O的最短路线找不出来,那么为什么又能保证倒着从O到I的路线好找呢?
我们刚才题目中也说了,I、O的位置都可以变,其实我们完全可以把它们互换位置,所以说正着找、倒着找,对解决问题并没有实质性的帮助。
那么“直接看出来最短路线再数”,有这个想法的同学,老师要表扬你一下啊!为什么表扬呢?因为你是超人啊!“直接看出”是一种超能力吗?如果我们的地图,不是8*8,而是80*80,你怎么直接看出呢?也不是特别可行,对不对?
那么我们还有最后一种想法:看一下总共几条路线,分别把每条路线的步数数完后再比较。
这个想法其实比较接近我们的解决思路了。为什么只是接近呢?难道不是从几条路线中比较,才能找出最短路线吗?
确实,最短路线是比较才可以找出来的。没有“比较”,就没有“最”。
但是如果要把所有的路线都找出来,其实是不太可能,或者说不太高效的。
举一个极端的例子:如果地图上只剩下I和O,W全部没有了,那么我们的最短路线怎么算?难道你还要把所有可能的路线都找一遍之后,再比较出最短的吗?显然不需要。
同学们还有没有办法,把这种想法再改进一下?
(思考时间)
老师现在给大家一个任务,在纸上画出咱们这个地图,然后从I开始,向上下左右各个可以走的方向,每走一格的时候,在空白的格子里记上一个数字,表示离I走了多远,试试吧!
比如,像下面图片中曹老师给大家展示的:从I向左、上、右都可以走,所以在相应的格子里,都填上数字1,表示这个位置离I原来的位置,距离是1。而左上的1无路可走了,放弃它。右边的1处,只能往上走,于是在它上方格子里填上2。原来I上方的数字1处,这时可以往上走,于是在那里填上数字2,也可以从这里往右走,只不过刚才已经填了2了,所以不需要再填了。如果大家填的过程中,出现同一位置两个数字不一样,那么就改成比较小的数字。
大家试试,看一直这样,直到O的周围都填满了数字,那么是不是到O的距离就等于它周围的数字中最小的一个加1了啊?
(练习时间)
同学们都填得差不多了吧?大家说出来的结果是不是我们要得的最小步数啊?
一般情况下,得到的结果是对的,确实是最小步数。但是也有可能不是。如果大家一开始就挑了最长的几条路线先填,一口气填到黑,把O周围都填满了,而比较短的路线还没填数字,没来得及重新涂改O周围的数字,那就不对了。
那这么说来,难道我们还是必须得把所有可能的路线都填一遍,才能得到完美的最短路线步数了吗?
NO!这样会累死计算机的!
大家再来一个纸笔练习。
这次我们换一种思路。每一次在从I往外“扩张”数字的值时,必须保证每一个方向上上一次的离I距离更小格子都填完,都扩张完之后,再填更大的数字距离。
比如:像下图这样,距离I有4格距离的格子填完后,才能填距离为5的格子。所以第四排第一个5的填写时间,应该在第七排第五个4的填写时间之后。大家请再按这种方法完成练习。
(练习时间)
完成后的结果应该是这样的:
当我们标到9的时候,发现有一条路线已经到达O的位置了。所以完成了步数的计算:9。
大家看,一个更精确的人脑计算程序,就应该是这样的!同学们可以试着改变一下地图中I、O、W的情况,看看这个方法还是不是一样能用。
(练习时间)
好!人脑解决这个寻找最短路线所用步数,思路就是这样了。现在我们要想一想,怎么用计算机代码来实现这个思路了!
首先的首先,要想用计算机分析问题、解决问题,我们需要把这个问题化成计算机能够懂的形式。
什么意思呢?就是计算机怎么才能看懂这个地图啊?
其实要想让计算机能够处理问题,我们就需要把问题的方方面面都化为数据。大家已经学了Python的数据类型了。
Python里有数字、字符串、布尔值、列表、字典、元组等数据类型。我们应该选用哪一个,用来表示地图呢?(请同学们直接回答)
这里我们要用列表来表示这个地图。
列表怎么表示?因为地图有行有列,所以我们可以把它的每一行看成一个元素,这样的话,这个列表就有8个行元素。而每一行又有8个格子,所以每一个列表行元素,又应该含有8个子元素。
因此,我们最终要用一个二维列表,来表示整个地图。
大家请看,所有的空白格子,我们用字符串' '(引号中间为空格)来表示了,而其他的I、O、W,我们都用它们的字符串形式'I', 'O', 'W'表示了。注意所有的字母都要大写,以免后面出错。
有了这个data变量存储地图信息,我们就可以用data[7][1]表示'I',用data[1][4]表示'O'了。
为了方便,我们给起点的行和列索引分别用一个变量表示:
f_i = 7
f_j = 1
好,有了上面这些准备工作,我们就可以更进一步:怎么按刚才第二种填数字的方法,把程序写出来呢?
在思考写程序之前啊,同学们一定要弄懂一个问题。大家有没有思考过,两种填数字的方法,有什么本质的不同呢?
其实我们可以把问题看成这样:如下图。
从起点I到终点O的探路过程,其实相当于是上面这个图。从起点出发,可能有4个方向的格子跟它距离为1,然后这4个格子又分别可能有4个方向的格子(当然实际上有重复的)。第2层、第3层……一直到终点,每一层距离就增加1。
如果我们用第一种填数字的方法,相当于是从第1层选1格,再走到它的下一层,选1格,再走到它下一层,一直进行下去,直到终点,这样可以找到完整的一条路线。
改变中间的选择的格子,可以找到不同的路线。最终用路线所用的层数进行比较选最小的。
而第二种填数字的方法,相当于是先不着急着一层一层往终点走,而是先保证把所有的起点周围的第1层都填完之后(有几个就填几个),再填各个的第2层(也是有几个就填几个),所有第2层的格子都填完之后,再填第3层的……
这样,总有一个时候会填到终点,这时必定是最短的路线,而这时的层数也就是距离,其它没有填完的就不用管了,因为我们的目标已经实现了。
在计算机算法上,我们给这两种思路分别取了不同的名字,这里同学们可以了解一下:第一种叫做“深度优先搜索”,第二种叫做“广度优先搜索”。
第一种是先一条道走到黑,第二种则是每条路都同步向前推进。
大家注意啊,刚才老师说第二种方法更好,并不是说第二种算法永远比第一种好,算法并没有好坏之分,而只有适用不适用之分。
对我们求最短路线的距离这个问题,第二种广度优先搜索的算法,更适合一些。而对其他一些问题,可能就是用第一种更适合一些了。
大家现在已经明白咱们的方法本质是什么了。
可是,怎么才能做到先把第1层的格子都走完,再走第2层格子呢?
我们想啊,每一层可以上下左右走的格子数量还不是确定的,有可能4个(对起点而言),有可能是3个,也可能是2个、1个,或者0个(死胡同了)。
因此我们肯定要判断一下,在处理每一层的时候,把能够走的格子都考虑进来。这一层都考虑完之后,再考虑下一层的。
所以,大家仔细思考一下这个逻辑,我们要先考虑一些格子,再考虑另一些格子,前一批格子都填完之后,再填后一批,接着再填下一批。
(请同学们先回答这个问题后继续后面的内容)
要用程序实现先来的先处理,后来的后处理,而在处理原来的东西的过程中,待处理的东西又会不断增多,应该用什么编程方法?
A. 循环
B. 栈
C. 队列
D. 函数
大家选对了吗?这里我们要用C. 队列,才能完成任务哦!大家记住,广度优先搜索算法,总跟队列有关系。
那么我们应该用队列怎么完成这个程序呢?首先我们来复习一下队列的写法:
(练习时间)
上面的代码我们只给了大家一个最简单的队列实现方法。
队列可以入队、出队,入队永远在队尾,出队永远在队首。我们把出队函数增加了一个return,使得一会儿咱们可以直接使用它来保存出队的数据。is_empty()则用来判断队列是否为空。
有了它之后,那让我们再来思考一下整个程序的流程吧!
程序开始后,我们从索引为f_i, f_j的这个格子出发,用一个变量记录这时与初始格子的距离为0。
接着一直做下面这些事情:
(1) 一个格子进入处理队列;
(2)真正轮到(位于第一个时)这个格子时,再将它四个方向上可以走的格子也进入处理队列,同时下一层格子离初始格子的距离 = 它上一层格子的距离值+1;
(3)让队列中的第一个元素出队。
让我们一步一步来完成代码吧!
在上面第(2)条中,怎么判断哪些格子是“可以走”的,也需要有一定的技巧的。因为这个判断是需要重复很多次的,所以我们可以定义一个函数解决。
(练习时间)
在这个过程中,队列的数据结构已经写好了。我们应该决定:往队列里每次入队、出队的是什么东西呢?因为我们要表示一个格子,所以只需要把每个格子的i, j坐标值入队和出队就可以了,因此可以用(i, j)元组的形式。
我们把出发点单独在一开始就进行入队处理了。
在这之后,就可以用一个循环开启搜索过程了。由于整体循环次数是未定的,所以选用while循环,而一会儿我们在内部用条件来跳出循环,所以一开始写while True。
while True:
...
if ...
break
那么我们的循环应该什么时候终止呢?我们在格子入队时进行判断,在找到一个入队的格子,它的值为'O'时,就可以break循环了。
同时为了避免已经加到队列中的格子,被另一条路线重复加入到队列中,需要在每次入队时把这个格子是否访问过的值,标记为“V”,表示“已访问”。在写的过程中,注意当前上下左右的坐标表示。
while的最后一行,别忘了,让第一个元素出队。
(练习时间)
这样,一个能够自动计算最短路线的步数的程序,就完工了!
知识技巧总结:
-
掌握寻路算法的手工操作
-
了解深度优先搜索和广度优先搜索
-
掌握队列在广度优先搜索中的使用方法
-
掌握判断上下左右四个方向格子存在与否的判断方法
-
掌握保存每个格子距离值的方法
-
巩固循环、函数、类等的应用
课后作业:
请同学们思考在输出最短路线的步数后,如何将最短路线也输出。输入例题中的地图,输出应为:
7 , 1
6 , 1
5 , 1
4 , 1
4 , 2
3 , 2
3 , 3
3 , 4
2 , 4
1 , 4
与非学堂(codingclassonline)
一个专注青少年信息技术教育,探讨少儿编程的教与学,交流技巧、分享资源的公众号。
--------------------------------------
【课件分享】
【往期每日一题】
-
【Scratch竞赛每日一题】循环变大小
-
【Scratch竞赛每日一题】循环画画
-
【Scratch竞赛每日一题】循环变量
【近期考竞通知】
【高赞原创集锦】