• 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 " "
    while i < n
      j= 0
      while j < n
        case map[i][j]
        when "#"
          print "◼︎ "
        when "g"
          print "☿ "
        when "."
          print "  "
        end
        j += 1
      end
      print " "
      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: "
    startx = gets.to_i
    print "请输入起点坐标y: "
    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 " "
    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} "
      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: "
    startx = gets.to_i
    print "请输入起点坐标y: "
    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} "
    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 " "
    end
    $book.each do |x|
      x.each do |y|
        if y == "x"
          print "❌ "
        else
          print "#{y} "
        end
      end
      print " "
    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} #{$y}. "
      # 当前水管是直管的情况 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} "
    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"

    分析:

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

  • 相关阅读:
    并发队列、线程池、锁
    JVM、垃圾收集器
    Socket网络编程
    Netty入门
    SpringCloud微服务负载均衡与网关
    Android监听耳机按键事件
    利用本地不同磁盘文件夹作为git远程仓库进行灾备
    chrome浏览器form中button每点击一次,form就会提交一次
    [企业路由器] 一对一NAT映射设置指导
    win7镜像自带IE9的卸载
  • 原文地址:https://www.cnblogs.com/chentianwei/p/8460069.html
Copyright © 2020-2023  润新知