我正在尝试创建一个井字游戏程序作为心理锻炼,我将董事会状态存储为像这样的布尔值:
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个变量。
并提出问题:
首先,我如何知道布尔条件是否已经尽可能简单?
和第二:简化上述布尔条件是什么?
答案 0 :(得分:5)
原始表达
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,也许您可以找到另一种解决方案。
通常,很难找到最小的公式。如果您对此感兴趣,请参阅this recent paper。但在我们的案例中,有一个简单的证明。
对于公式大小,我们将推测公式为最小,对于布尔运算{{1},我们将对变量a
,size(a)=1
进行推理},以及否定size(A&B) = size(A|B) = size(A) + 1 + size(B)
(因此我们可以假设我们免费获得Negation Normal Form)。
就该尺寸而言,我们的配方尺寸为37。
你不能做得更好的证据在于首先要说明要检查8行,并且总是有一对字母区分2个不同的行。由于我们可以将这8个检查重新组合在不少于3个剩余变量的组合中,因此最终公式中的变量数量应至少为size(!A) = size(A)
,我们可以从中推导出最小树大小。
详细证明
让我们假设给定的公式8*2+3 = 19
是最小并且采用NNF格式。
F
不能包含F
等否定变量。为此,请注意!a
应该单调,也就是说,如果它返回“true”(有一个获胜的行),则从F
更改其中一个变量到false
不应该改变那个结果。 According to Wikipedia,true
可以不加否定地书写。更好的是,我们可以证明我们可以删除否定。在this answer之后,我们可以转换回DNF格式,删除中间的否定变量或将其替换为F
。
true
不能包含子树,如两个变量F
的分离。
要使此公式有用且无法与a|b
或a
交换,则意味着存在相互矛盾的分配,例如
b
和F[a|b] = true
,因此F[a] = false
和a = false
因为单调性。此外,在这种情况下,将b = true
转为b
会生成整个公式false
,因为false
。
因此,false = F[a] = F[a|false] >= F[a|b](b = false)
之间有一行是事实的原因,它不能通过b
,因此例如a
和e = true
。
并且对该行的检查通过表达式h = true
进行测试a|b
。但是,这意味着当b
为真且所有其他设置为false时,a,e,h
仍然为真,这与公式的目的相矛盾。
每个看起来像F
的子树都会检查一个唯一的行。所以最后一个字母应该出现在相应的分离a&b
的正上方,或者这个叶子是无用的,可以安全地删除a或b。实际上,假设(a&b|...)&{c somewhere for sure here}
没有出现在上面,游戏是c
a&b&c
,而所有其他变量都是true
。然后c应该在上面的表达式返回false
,因此false
将始终无用。因此,删除a&b
。
有8个独立分支,因此至少有8个a&b
类型的子树。我们无法使用2个连词的析取重新组合它们,因为a&b
,a
和f
从不共享相同的行,因此必须有3个外部变量。 h
使最终公式中出现19个变量。
具有19个变量的树不能少于18个运算符,因此总大小必须至少为19 + 18 = 37。
您可以拥有上述公式的变体。
QED。
答案 1 :(得分:0)
一种选择是手动执行卡诺图。由于你有9
个变量,这就形成了一个2 ^ 4乘2 ^ 5的网格,这个网格相当大,而且通过等式的外观,可能也不是很有趣。
通过检查,它看起来不像卡诺图会给你任何有用的信息(卡诺图基本上将((!a)&b) | (a&b)
等表达式简化为b
),所以从简化的意义上说,表达式已经尽可能简单了。但是如果你想减少计算次数,你可以使用AND运算符在OR上的分布来分解一些变量。
答案 2 :(得分:0)
你知道,如果没有常见的子术语可以提取,那么这很简单(例如,如果你在两个不同的三重奏中有“a& b”)。
你知道你的tic tac toe解决方案必须已经尽可能简单,因为任何一对盒子最多只能属于一条获胜线(只有一条直线可以通过两个给定点),所以(a& b) )不能在你正在检查的任何其他胜利中重复使用。
(另外,“简单”可能意味着很多事情;指明你的意思可能会帮助你回答你自己的问题。)
答案 3 :(得分:0)
想到这一点的最佳方式是一个人如何看待它。没有人会对自己说“a和b和c,或者d和e和f,”等等。他们会说“连续三个,水平,垂直或对角线。”
此外,不是进行8次检查(3行,3列和2对角线),而是只进行4次检查(3行和1个对角线),然后将板旋转90度,然后再进行相同的检查。
这就是你最终的结果。这些函数都假设该板是一个三乘三的布尔矩阵,其中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
虽然这段代码相当冗长,但每个函数的意图都很明确。代码现在表达了tic-tac-toe的规则,而不是长的布尔表达式。