2018-2-22 《啊哈,算法》再练习广度优先搜索,题:炸怪兽, 2-23改用深度优先搜索。宝岛探险(广度,深度,及地图着色)2-24水管工游戏,2-25测试水管工代码...

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"

分析:

    粗心多写了一个=。导致不能正确回朔。之前的代码正确也是瞎猫碰死耗子。 

 

转载于:https://www.cnblogs.com/chentianwei/p/8460069.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值