【问题标题】:Backtracking recursion similiar to minesweeper game, 2d boolean array类似于扫雷游戏的回溯递归,二维布尔数组
【发布时间】:2021-04-06 05:25:56
【问题描述】:

基本上这是一个家庭作业,如果你回答我的问题,我更愿意得到答案而不是代码和答案本身。

这必须是一种递归方法,在给定的二维布尔数组中,它必须返回数组中真实区域的数量 - 数组中的行数和列数将始终相同。

真正的区域是在至少有一个真正的元素时定义的,如果它有一个相邻的另一个元素也是如此,它们仍然算作 1。 对角线元素不被视为邻居。

例如,在这个矩阵中,当 1 代表真,0 代表假时,有 3 个真区域 - 左侧的两个单元格,右侧的三个单元格,最后一行的一个单元格,一个人。

1 0 0 1
1 0 1 1
0 1 0 0

我不知道如何递归处理这个问题,我认为在简单的迭代中它会很简单,但似乎不可能使用调用自身的方法检查数组。

有人有线索吗?

【问题讨论】:

  • 递归不是回溯。有时回溯计算结构是用递归构建的——比如通过递归构建 n 嵌套循环来执行recursive-backtracking;但递归本身就是一回事。 is 这里有一个 naturally 递归解决方案。至于回溯——即在失败后重试直到成功——我看不出这里需要重试什么。您只需将一件事一分为二,计算每个(或一个?)您感兴趣的问题,然后将两件事加在一起,可能会调整计数。
  • 所以如果你坚持递归回溯解决方案,我对没有答案。
  • @WillNess 感谢您的回答。我不需要回溯解决方案,我只是想,因为这是我们研究的最后一件事,它应该是答案,但这不是必需的。
  • 尝试将其分成第一行和其余行。以某种方式为其余行生成两件事-第二行顶部的计数和“活动点”。然后将其与第一行结合,并在第一行顶部产生新的计数和新的活动点。那是它似乎可以完成的模糊轮廓。我认为 Judge 的回答也暗示了这种方式,但我不明白为什么它同时谈到垂直和水平分割(或者我误解了它)。

标签: java arrays recursion boolean minesweeper


【解决方案1】:

既不优雅也不高效,我的第一次尝试是尝试通过递归模拟所有字段的迭代,如果每个字段是一个真正的区域,则返回 1。还传递一个数组来跟踪已检查的字段。对子调用的结果求和。

// spoiler alert 

















































public class Minefield {
  public static void main(String[] args) throws Exception {
    int[][] field = { //
        { 1, 0, 0, 1 }, //
        { 1, 0, 1, 1 }, //
        { 0, 1, 0, 0 }, //
    };
    int w = field[0].length;
    int h = field.length;

    int count = count(0, 0, w, h, true, field, new int[h][w]);
    System.out.println("true zones: " + count);
  }

  private static int count(int x, int y, int w, int h, boolean checkForNewZone /* false: mark zone */, int[][] field, int[][] marked) {
    if(x < 0 || x >= w || y < 0 || y >= h) {
      return /* out of bounds */ 0;
    }

    if(checkForNewZone) {
      int count = 0;
      if(field[y][x] == 1 && marked[y][x] == 0) {
        // a new true zone -> count it
        count++;
        // and mark it
        count(x, y, w, h, false, field, marked);
      }

      // iterate to the next field
      // x++;
      // if(x >= w) {
      //   x = 0;
      //   y++;
      // }
      // count += count(x, y, w, h, true, field, marked);

      // check neighboring fields (right & down should cover everything, assuming the starting point is the top left)
      count += count(x + 1, y, w, h, true, field, marked);
      count += count(x, y + 1, w, h, true, field, marked);
      return count;
    }
    else {
      // mark zone
      if(field[y][x] == 1 && marked[y][x] == 0) {
        marked[y][x] = 1;
        count(x + 1, y, w, h, false, field, marked);
        count(x - 1, y, w, h, false, field, marked);
        count(x, y + 1, w, h, false, field, marked);
        count(x, y - 1, w, h, false, field, marked);
      }

      return 0;
    }
  }
}

【讨论】:

  • 当存在自然递归算法且 OP 要求时,为什么要模拟迭代?
  • 没有理由。这是我能做的最好的了。我也有兴趣看到从中学习的自然解决方案。
  • 正如上面的答案所暗示的,我们只需要找到一种自然的方法来以某种方式将这个问题分解成多个部分,解决每个/一个子部分,然后找到一种方法将结果组合回来。我目前的感觉是,这应该可以通过拆分一行和其余部分来实现;解决每一个(较大的部分递归);然后结合结果。当然,这种组合不仅仅是两个计数的相加。
  • 这个答案虽然暗示了两个随地吐痰的 AFAICT,一个是分开一行,另一个是一列,我不明白为什么会这样,或者在一次。 (“以上”按时间顺序排列;我几乎总是在 SO 上使用该顺序)
  • 我编辑了更多的“增长”算法,而不是逐行“迭代”。现在这是一个“真正的”递归算法吗?
【解决方案2】:

典型的递归算法由两部分组成:

  1. 基本案例
  2. “归纳步骤”

首先确定基本情况。这些可能会通过输入数组的大小来识别。例如,0x0 数组没有真正的区域。 有时会有多个基本情况。

然后确定在给定较小输入版本的答案的情况下,您可以如何计算较大输入版本的答案。这可能看起来像考虑如何从一个具有 Z 个区域的 nxm 阵列转到一个具有 Z' 区域的 (n+1)xm 阵列和一个具有 Z'' 区域的 nx(m+1) 阵列。 通常需要解决稍微复杂的问题才能成功,因为您可以对较小输入的答案进行更多假设。

将这两种情况转换为递归程序通常很容易。

【讨论】:

  • 当你只需要一个时,为什么要两个方向,而两个方向很复杂,无处可去?但一般建议是合理的;我们只是需要确定如何拆分问题以及如何将结果组合回来...这就是整个问题。跨度>
【解决方案3】:

这些本质上是connected components,使用DFS

【讨论】:

  • 您的链接描述了一种迭代算法,而不是递归算法:“在任何一种情况下,从某个特定顶点 v 开始的搜索都会在返回之前找到包含 v 的整个组件(仅此而已)。找到图的所有组件,loop [emphasis mine --wn] 通过其顶点,每当循环到达顶点时开始新的广度优先或深度优先搜索尚未包含在以前找到的组件中。”
  • @WillNess 是的,但是主要算法是递归的,任务并没有说他们根本不能使用循环。还是我错了?
  • @AndrewVershinin 是的,我没有提到这一点,但我根本不能在方法本身或与答案有关的任何其他方法中使用循环。
猜你喜欢
  • 2013-04-11
  • 1970-01-01
  • 2019-07-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-04
  • 1970-01-01
相关资源
最近更新 更多