【问题标题】:Simplifying a 9 variable boolean expression简化一个 9 变量布尔表达式
【发布时间】:2014-01-16 16:24:02
【问题描述】:

我正在尝试创建一个井字游戏程序作为一种心理锻炼,并且我将棋盘状态存储为布尔值,如下所示:

http://i.imgur.com/xBiuoAO.png

我想简化这个布尔表达式...

(a&b&c) | (d&e&f) | (g&h&i) | (a&d&g) | (b&e&h) | (c&f&i) | (a&e&i) | (g&e&c)

我的第一个想法是使用Karnaugh Map,但网上没有支持 9 个变量的求解器。

问题来了:

首先,我如何知道布尔条件是否已经尽可能简单?

第二个:上面的布尔条件是什么简化的?

【问题讨论】:

    标签: boolean logic


    【解决方案1】:

    2。简化条件:

    原来的表达方式

    a&b&c|d&e&f|g&h&i|a&d&g|b&e&h|c&f&i|a&e&i|g&e&c
    

    可以简化为以下,知道 & 比 | 更优先

    e&(d&f|b&h|a&i|g&c)|a&(b&c|d&g)|i&(g&h|c&f)
    

    短 4 个字符,在最坏的情况下执行 18 次 &| 评估(原始计算为 23) 没有更短的布尔公式(见下文)。如果你switch to matrices,也许你可以找到另一个解决方案。

    1。确保我们得到最小的公式

    通常,很难找到最小的公式。如果您更感兴趣,请参阅this recent paper。但在我们的例子中,有一个简单的证明。

    我们将推断一个公式是 最小 就公式大小而言,其中变量 asize(a)=1、布尔运算 size(A&B) = size(A|B) = size(A) + 1 + size(B) 和否定 @ 987654334@(因此我们可以假设我们免费拥有Negation Normal Form)。 对于这个大小,我们的公式大小为 37。

    你不能做得更好的证据在于首先说明有 8 行要检查,并且总是有一对字母区分 2 个不同的行。由于我们可以将这 8 个检查与剩余的变量组合不少于 3 个,因此最终公式中的变量数至少应为8*2+3 = 19,由此我们可以推断出最小的树大小。

    详细证明

    让我们假设给定的公式F最小的 并且采用NNF 格式。

    1. F 不能包含否定变量,例如 !a。为此,请注意F 应该是单调的,也就是说,如果它返回“true”(有一个获胜行),则将其中一个变量从false 更改为true不应该改变这个结果。 According to Wikipedia, F 可以不用否定写。更好的是,我们可以证明我们可以删除否定。在this answer之后,我们可以转换DNF格式,删除中间的否定变量或将它们替换为true

    2. F 不能包含子树,例如两个变量 a|b 的析取。 要使此公式有用且不能与ab 交换,这意味着存在相互矛盾的分配,例如 F[a|b] = trueF[a] = false,因此 a = falseb = true 因为单调性。此外,在这种情况下,将b 转换为false 会使整个公式为false,因为false = F[a] = F[a|false] >= F[a|b](b = false)。 因此有一行经过b,这就是真相的原因,它不能通过a,因此例如e = trueh = true。 并且该行的检查通过表达式a|b 来测试b。但是,这意味着a,e,h 为真而所有其他设置为假,F 仍然为真,这与公式的目的相矛盾。

    3. 每个看起来像a&b 的子树都会检查一个唯一的行。所以最后一个字母应该出现在对应的析取词(a&b|...)&{c somewhere for sure here}的正上方,否则这个叶子没有用,可以安全地删除a或b。事实上,假设c 没有出现在上面,并且游戏是a&b&ctrue 而所有其他变量都是false。那么c应该在上面的表达式返回false,所以a&b总是没用的。所以通过删除a&b可以得到一个更短的表达式。

    4. 有8个独立的分支,所以至少有8个a&b类型的子树。由于afh 从不共享相同的行,因此我们不能使用 2 个连词的析取来重新组合它们,因此必须有 3 个外部变量。 8*2+3 使 19 个变量出现在最终公式中。 具有 19 个变量的树不能少于 18 个运算符,因此总大小必须至少为 19+18 = 37。

    您可以有上述公式的变体。

    QED。

    【讨论】:

      【解决方案2】:

      一种选择是手动绘制卡诺图。由于您有 9 变量,因此形成了一个 2^4 x 2^5 的网格,该网格相当大,而且从等式的外观来看,可能也不是很有趣。

      通过检查,卡诺图似乎不会为您提供任何有用的信息(卡诺图基本上将 ((!a)&b) | (a&b) 之类的表达式简化为 b),所以在这种简化的意义上,您的表达式已经是尽可能简单。但是,如果您想减少计算次数,您可以使用 AND 运算符对 OR 的分布来分解一些变量。

      【讨论】:

      • 他甚至无法将其中任何一个因素考虑在内;给定的 AND 对只能在一条获胜线上。
      • 我说的是提取单个变量,就像这样。 (a&((b&c) | (d&g) | (e&i))) | (c&((f&i) | (g&e))) | (e&((d&f) | (b&h))) | (g&h&i) 不过,我建议不要这样做,因为它的可读性要差得多。我不知道要使用什么语言,但编译器可能已经针对这些情况进行了优化。
      • 我可能记错了符号,但我很确定从理论的角度来看,这始终是鞅...
      【解决方案3】:

      思考这个问题的最佳方式是一个人的想法。没有人会对自己说“a and b and c, or if d and e and f”等等。他们会说“任意三个连续,水平、垂直或对角线。”

      另外,您可以只进行四次检查(三行和一条对角线),而不是进行八次检查(3 行、3 列和 2 条对角线),然后将棋盘旋转 90 度,然后再次进行相同的检查。

      这就是你最终的结果。这些函数都假设棋盘是一个 3×3 的布尔矩阵,其中 true 代表中奖符号,false 代表未中奖符号。

      def win?(board)
        winning_row_or_diagonal?(board) ||
          winning_row_or_diagonal?(rotate_90(board))
      end
      
      def winning_row_or_diagonal?(board)
        winning_row?(board) || winning_diagonal?(board)
      end
      
      def winning_row?(board)
        3.times.any? do |row_number|
          three_in_a_row?(board, row_number, 0, 1, 0)
        end
      end
      
      def winning_diagonal?(board)
        three_in_a_row?(board, 0, 0, 1, 1)
      end
      
      def three_in_a_row?(board, x, y, delta_x, delta_y)
        3.times.all? do |i|
          board[x + i * delta_x][y + i * deltay]
        end
      end
      
      def rotate_90(board)
        board.transpose.map(&:reverse)
      end
      

      矩阵旋转来自这里:https://stackoverflow.com/a/3571501/238886

      虽然这段代码有点冗长,但每个函数的意图都很明确。代码现在表达的是井字游戏规则,而不是长布尔表达式。

      【讨论】:

        【解决方案4】:

        当没有要提取的通用子术语时,您知道这是一个尽可能简单的方法(例如,如果您在两个不同的三重奏中有“a&b”)。

        您知道您的井字游戏解决方案必须已经尽可能简单,因为任何一对盒子最多只能属于一条获胜线(只有一条直线可以通过两个给定点),所以 (a & b)不能在您正在检查的任何其他胜利中重复使用。

        (此外,“简单”可能意味着很多事情;具体说明您的意思可能会帮助您回答自己的问题。)

        【讨论】:

          猜你喜欢
          • 2016-07-13
          • 2016-02-20
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多