2小时。
先是是纠错,通过对代码运行过程的测试。发现是变量打错。以及录入地图❌。
重构练习题,改使用while..end代替for in.
⚠️ : 在while(k <= n)中如果用到next,为了不陷入死♻️,注意条件的判断, 一般要在next前k += 1。
题:爆炸小子炸怪兽
代码:
1.广度优先搜索
设定一个点,先访问这个点的每个周边点,然后再扩展到每个周边点的还未访问的点。
代码:使用队列(栈)。
# 地图
map = [
["#","#","#","#","#","#","#","#","#","#","#","#","#"],
["#","g","g",".","g","g","g","#","g","g","g",".","#"],
["#","#","#",".","#","g","#","g","#","g","#","g","#"],
["#",".",".",".",".",".",".",".","#",".",".","g","#"],
["#","g","#",".","#","#","#",".","#","g","#","g","#"],
["#","g","g",".","g","g","g",".","#",".","g","g","#"],
["#","g","#",".","#","g","#",".","#",".","#",".","#"],
["#","#","g",".",".",".","g",".",".",".",".",".","#"],
["#","g","#",".","#","g","#","#","#",".","#","g","#"],
["#",".",".",".","g","#","g","g","g",".","g","g","#"],
["#","g","#",".","#","g","#","g","#",".","#","g","#"],
["#","g","g",".","g","g","g","#","g",".","g","g","#"],
["#","#","#","#","#","#","#","#","#","#","#","#","#"]
]
# 地图副本book,用于标记走过的点。用compact复制出新的array。
# 因为不能嵌套复制。需要用map
book = map.map{|item| item.compact }
# 输出地图(对输出结果进行美化)
i = 0
n = map.size
n.times do |x|
print "#{x} "
end
print "\n"
while i < n
j= 0
while j < n
case map[i][j]
when "#"
print "◼︎ "
when "g"
print "☿ "
when "."
print " "
end
j += 1
end
print "\n"
i += 1
end
# 杀怪统计Method
def getnum(startx, starty, map)
# 四个方向各自统计杀怪数,加到sum中
sum = 0
x = y = 0
# 向下,遇到“#”,停止。(爆炸受阻)
x = startx
y = starty
while map[x][y] != "#"
if map[x][y] == "g"
sum += 1
end
x += 1
end
# 上
x = startx
y = starty
while map[x][y] != "#"
if map[x][y] == "g"
sum += 1
end
x -= 1
end
# 右
x = startx
y = starty
while map[x][y] != "#"
if map[x][y] == "g"
sum += 1
end
y += 1
end
# 左
x = startx
y = starty
while map[x][y] != "#"
if map[x][y] == "g"
sum += 1
end
y -= 1
end
return sum
end
# 设定开始起点位置
print "请输入起点坐标x:\n"
startx = gets.to_i
print "请输入起点坐标y:\n"
starty = gets.to_i
# wide first search (核心)
x = []
y = []
# 指针head , tail(队列最后一位的后面空白处)
head = 0
tail = 1
x[head] = startx
y[head] = starty
# 坐标点周边的四个方向,目的是访问相邻的4个点
next_direction = [
[0, 1],#右
[1, 0],#下
[0,-1],#左
[-1,0] #上
]
# 统计最大击杀
kill_max = 0
max_x = max_y = 0
# 边界的判断
nx = map[0].size - 2
ny = map.size - 2
# 大循环,wide first search
while head < tail
# 每个坐标点周边点的情况判定。
k = 0
while k <= 3
tx = x[head] + next_direction[k][0]
ty = y[head] + next_direction[k][1]
# 界限
if tx < 1 || tx > nx || ty <1 || ty > ny
# k += 1必不可少,否则会死循环。用for语法的话可以避免。
k += 1
next
end
# 判断是否是平地,而且没有走过
if map[tx][ty] == "." && book[tx][ty] == "."
# 在地图上标记已经走过
book[tx][ty] = "X"
x << tx
y << ty
# 队列尾巴的后一位,用于判断是否结束大循环
tail += 1
# 统计杀怪数量,调用Method
sum_kill = getnum(tx, ty, map)
if sum_kill > kill_max
kill_max = sum_kill
max_x = tx
max_y = ty
end
end
k += 1
end
# 头部指针前移动
head += 1
end
p "最大击杀#{kill_max}"
p "坐标:(#{max_x},#{max_y})"
2.深度优先搜索
核心,设定一个边界(目标),延一个方向走到满足条件的极限,然后回朔换方向走,一旦不满足条件就回朔。只关注当下
代码: dfs()嵌套 + 界限
# 地图
$map = [
["#","#","#","#","#","#","#","#","#","#","#","#","#"],
["#","g","g",".","g","g","g","#","g","g","g",".","#"],
["#","#","#",".","#","g","#","g","#","g","#","g","#"],
["#",".",".",".",".",".",".",".","#",".",".","g","#"],
["#","g","#",".","#","#","#",".","#","g","#","g","#"],
["#","g","g",".","g","g","g",".","#",".","g","g","#"],
["#","g","#",".","#","g","#",".","#",".","#",".","#"],
["#","#","g",".",".",".","g",".",".",".",".",".","#"],
["#","g","#",".","#","g","#","#","#",".","#","g","#"],
["#",".",".",".","g","#","g","g","g",".","g","g","#"],
["#","g","#",".","#","g","#","g","#",".","#","g","#"],
["#","g","g",".","g","g","g","#","g",".","g","g","#"],
["#","#","#","#","#","#","#","#","#","#","#","#","#"]
]
# 地图副本book,用于标记走过的点。用compact复制出新的array。
# 因为不能嵌套复制。需要用map
$book = $map.map{|i| i.compact }
# 输出地图(对输出结果进行美化)
i = 0
nx = $map.size
ny = $map.first.size
nx.times do |n|
if n >=10
print "#{n}"
else
print "#{n} "
end
end
print "\n"
while i < nx
j = 0
while j < ny
case $map[i][j]
when "#"
print "◼︎ "
when "g"
print "☿ "
when "."
print " "
end
j += 1
end
print "#{i}\n"
i += 1
end
# 杀怪统计Method
def getnum(startx, starty)
# 四个方向各自统计杀怪数,加到sum中
sum = 0
x = y = 0
# 向下,遇到“#”,停止。(爆炸受阻)
x = startx
y = starty
while $map[x][y] != "#"
if $map[x][y] == "g"
sum += 1
end
x += 1
end
# 上
x = startx
y = starty
while $map[x][y] != "#"
if $map[x][y] == "g"
sum += 1
end
x -= 1
end
# 右
x = startx
y = starty
while $map[x][y] != "#"
if $map[x][y] == "g"
sum += 1
end
y += 1
end
# 左
x = startx
y = starty
while $map[x][y] != "#"
if $map[x][y] == "g"
sum += 1
end
y -= 1
end
return sum
end
#深度优先搜索。----------------------------
# 设定开始起点位置
print "请输入起点坐标x:\n"
startx = gets.to_i
print "请输入起点坐标y:\n"
starty = gets.to_i
# 统计最大击杀
$kill_max = 0
$max_x = $max_y = 0
def dfs(x,y)
# 计算杀敌总数。(深度优先搜索的边界)
sum = getnum(x,y)
if sum > $kill_max
$kill_max = sum
$max_x = x
$max_y = y
end
# 坐标点周边的四个方向,目的是访问相邻的4个点
next_direction = [
[0, 1],#右
[1, 0],#下
[0,-1],#左
[-1,0] #上
]
# 四个方向枚举
k = 0
while k <= 3
tx = x + next_direction[k][0]
ty = y + next_direction[k][1]
# 判断是否越界
if tx < 1 || tx > 11 || ty <1 || ty > 11
# k += 1必不可少,否则会死循环。用for语法的话可以避免。
k += 1
next
end
#判断是否围墙,或走过。
if $map[tx][ty] == "." && $book[tx][ty] == "."
$book[tx][ty] = "X"
dfs(tx,ty) #通过不断的深度嵌套,再通过回朔改变方向,来完成遍历每个点。
end
k += 1
end
end
dfs(startx,starty)
print "最大击杀:#{$kill_max}\n"
p "坐标(#{$max_x},#{$max_y})"
宝岛探险:
广度,深度,和上色。
给所有岛着色(深度):嵌套循环,遍历所有岛(大于0)并标记,着色(num,不同的岛用不同的数表示。)
# 地图,0代表海洋,1-9代表陆地及其海拔。
$map = [
[1,2,1,0,0,0,0,0,2,3],
[3,0,2,0,1,2,1,0,1,2],
[4,0,1,0,1,2,3,2,0,1],
[3,2,0,0,0,1,2,4,0,0],
[0,0,0,0,0,0,1,5,3,0],
[0,1,2,1,0,1,5,4,3,0],
[0,1,2,3,1,3,6,2,1,0],
[0,0,3,4,8,9,7,5,0,0],
[0,0,0,3,7,8,6,0,1,2],
[0,0,0,0,0,0,0,0,1,0]
]
# 飞机降落位置坐标。
startx = 0
starty = 0
# 复制一个全新的地图用于在图上做标记。
$book = $map.map { |e| e.compact }
# 用于给不同的到上不同的颜色
$map_color = $map.map { |e| e.compact }
# 记录岛的面积
$sum = 0
# dfs方法 深度优先搜索。
def dfs(startx, starty,num)
# 方向坐标,按照右,下,左,上顺序排列。
next_direction = [
[0, 1],
[1, 0],
[0,-1],
[-1,0]
]
# 边界,本题不需要
i = 0
for i in 0..3 do
tx = startx + next_direction[i][0]
ty = starty + next_direction[i][1]
# 判断是否越界
if tx < 0 || tx > 9 || ty < 0 || ty > 9
next
end
# 判断是否为没发现的陆地
if $map[tx][ty] > 0 && $book[tx][ty] != "x"
$book[tx][ty] = "x"
$sum += 1
$map_color[tx][ty] = num
#陆地染色为num
dfs(tx,ty,num)
end
end
end
num = 0 #不同小岛的编号
# 遍历所有陆地(>0),每块陆地,一旦走过就$book标记“x”
i = j = 0
for i in 0..9 do
for j in 0..9 do
if $map[i][j] > 0 && $book[i][j] != "x"
num -= 1
$map_color[i][j] = num
#陆地染色为num
$book[i][j] = "x"
$sum += 1
#调用函数,相邻的陆地都会染色相同num
dfs(i,j,num)
end
end
end
p "岛的面积#{$sum}"
$map_color.each do |x|
x.each do |y|
printf("%3s",y)
end
print "\n"
end
$book.each do |x|
x.each do |y|
if y == "x"
print "❌ "
else
print "#{y} "
end
end
print "\n"
end
水管工
深度优先搜索实现。
⚠️ :判断每个管子的进水口inflow,用dfs方法时,除了x,y坐标参数,还有inflow进水口参数。每一次调用dfs(x,y,inflow)都是下一个管道,此时的出水口,是下一个的进水口,如果用东南西北表示,要注意改成进水口方向。例子:如果当前管子的出水口是东,那么下一个管子的进水口就是西。
$map = [
[5,3,5,3],
[1,5,3,0],
[2,3,5,1],
[6,1,1,5],
[1,5,5,5]
]
$book = $map.map { |e| e.compact }
# 用1-6分别表示水管的6种排列形式。4个弯管,2个直管
# n,m+1时候停止
# 参数inflow为进水方向。W进水口在西,N进水口在上,E进水口在right,S进水口在下。
inflow = ""
# 判断是否成功出水
$flag = "no"
#用于记录路径,栈:后进的先出,ruby用pop方法,去掉数组最后一个,也可以加个指针
$x = []
$y = []
def dfs(x, y, inflow)
p "开始dfs"
# 界限:停止dfs得出结果
# 判断水流是否走出地图,看地图东南角的边界。
if x == 4 && y == 4
$flag = "yes"
len = $x.size #队列的长度,然后输出连个数组x,y
len.times do |n|
printf("(%d,%d)->",$x[n],$y[n])
end
return
end
# 判断是否边界
if x < 0 || x > 4 || y < 0 || y > 3
return
end
# 判断是否已经标记了
if $book[x][y] == "Y"
return
end
$book[x][y] = "Y"
# 当前坐标入栈
$x << x
$y << y
print "#{$x}\n#{$y}.\n"
# 当前水管是直管的情况 5表示东西走向,6表示南北走向。
if $map[x][y] >= 5 && $map[x][y] <= 6
# "直管"
case inflow
when "W" #水从西进,下一个管道也是从西进,第三个参数是进水方向
dfs(x, y+1, "W")
when "N"
dfs(x+1, y , "N")
when "E"
dfs(x, y-1, "E")
when "S"
dfs(x-1, y ,"S")
end
end
# 当前水管是弯管的情况 1表示东北口,2表示东南口,3表示西南口,4表示西北口
if $map[x][y] >= 1 && $map[x][y] <= 4
# "弯管"
case inflow
when "N" #当水从北流入,弯管只有两种流出结构,但inflow参数是进水方向。
dfs(x, y+1, "W") #下一个管,和进水方向,不要混淆出水方向。
dfs(x, y-1, "E")
when "E" #当水从东流入,弯管只会两种流出方向,N,S
dfs(x-1, y, "S")
dfs(x+1, y, "N")
when "S"
dfs(x, y+1, "W")
dfs(x, y-1 ,"E")
when "W"
dfs(x-1, y, "S")
dfs(x+1, y, "N")
end
end
# 在dfs方法不再深度后,执行下面代码,取消标记 。
# 目的:用于回溯,产生其他方案
$book[x][y] = "N"
# 栈后进先出,去掉不合格的
$x.pop
$y.pop
return
end
dfs(0,0,"W")
if $flag == "yes"
print "找到出水方案"
else
print "没找到出水方案"
end
问题处理:
第一问:我在整体代码最后希望输出管道路径,代码如下:
10.times do |n|
$map[$x[n]][$y[n]] = "X"
end
$map.each do |x|
print "#{x}\n"
end
出错❌: no implicit conversion from nil to integer (TypeError)
试❌:加入代码p $x 发现得到[],竟然是空数组,难怪错误提示是nil to integer.
因为,dfs方法结束后 $x,$y已经清空。变为空数组 。可以放到dfs方法中的判断水流是否走出地图的if..end中,注意单独复制一个地图用于储存路径,$route = $map.map{|e|e.compact}
第二问: 代码正确得到结果,但是改变地图结构,就不能得到正确结构。
出错❌ 代码:
# 目的:用于回溯,产生其他方案
$book[x][y] == "N"
分析:
粗心多写了一个=。导致不能正确回朔。之前的代码正确也是瞎猫碰死耗子。