【问题标题】:Find possible boolean values to evaluate an expression to true查找可能的布尔值以将表达式评估为真
【发布时间】:2017-03-17 01:45:21
【问题描述】:

如果我有一个带有许多布尔变量的给定布尔表达式(AND 和 OR 操作),那么我想将此表达式评估为真,我如何找到所有可能的布尔值的集合以实现真表达?

例如,我有 4 个布尔变量 a、b、c、d 和一个表达式:

 (a ^ b) v (c ^ d)

我尝试过的最慢的方法是:

  • 我构建了一个表达式树来获取表达式中的所有变量,我得到了一个{a,b,c,d} 集。
  • 我找到了集合的所有子集:{a}, {b}, {c}, {d}, {a,b}, {a,c}, {a,d}, {b,c}, {b,d}, {c,d}, {a,b,c}, {a,b,d}, {a,c,d}, {b,c,d}, {a,b,c,d}
  • 对于每个子集,我将每个变量设置为真,然后计算表达式。如果表达式返回 true,我会保存带有值的子集。

编辑:我删除了NOT 运算符以使问题更简单。

【问题讨论】:

  • 我希望你知道这是一个 NP 完全问题。
  • @chris 你指的是哪个问题?
  • 您的问题中的那个。如果您知道产生真表达式的所有可能值集,则您知道是否存在解决方案,因此您可以轻松解决SAT problem。由于这是 NP 完全的(并且可能是最著名的),因此也是如此。
  • @chris 不完全:这表明这个问题是 NP-hard,但要成为 NP-complete,它也需要在 NP 中。除非我遗漏了什么,否则它不是:答案的长度可能是指数级的,即使你不需要找到它,在 NP 中仅仅产生它也是不可能的:)
  • @AlexeyRomanov,哦,说得好。我没有想太多关于走另一条路。

标签: logic expression logical-operators boolean-logic boolean-expression


【解决方案1】:

我想我看到了一种无需尝试所有排列即可计算它的方法,而且我的高级大纲(如下所述)并不是非常复杂。我将概述基本方法,您将有两个后续任务需要自己完成:

  • 将逻辑表达式(如“A && (B || C)”)解析为经典解析树,它表示表达式,树中的每个节点要么是变量,要么是布尔运算“&&”、“||”或“!” (NOT),两个孩子是它的操作数。这是一个经典的解析树。在 Google 中可以找到大量有关如何执行此操作的示例。

  • 将我的大纲翻译成实际的 C++ 代码。这也将取决于您,但我认为一旦您将大脑围绕整体方法,实施应该是相当明显的。

为了解决这个问题,我将使用两阶段的方法。

  1. 我将使用proof by induction 的一般方法,以便得出布尔表达式将评估为true 的所有变量的所有潜在值集的暂定列表。

  2. 在第二阶段,我将从所有潜在集合的列表中删除那些逻辑上不可能的集合。这可能听起来令人困惑,所以我将首先解释第二阶段。

让我们使用以下数据类型。首先,我将使用布尔表达式将评估为true 或false 的可能值的这种数据类型:

typedef std::set<std::pair<std::string, bool>> values_t;

这里,std::pair&lt;std::string, bool&gt; 代表变量,其名称为std::string,具有此bool 值。例如:

{"a", true}

表示变量“a”的值是true。由此可见,这个std::set代表了一组变量及其对应的值。

所有这些潜在的解决方案都将是:

typedef std::list<values_t> all_values_t;

这就是我们将如何表示所有变量值的所有组合的列表,这些组合产生true 或false 的结果。您可以使用std::vector 代替std::list,这并不重要。

现在请注意,values_t 可以同时拥有两者:

 {"a", true}

和

 {"a", false}

在集合中。这意味着为了使表达式计算为 true 或 false,“a”必须同时为真和假。

但这显然在逻辑上是不可能的。因此,在此解决方案的第 2 阶段,您将需要简单地检查 all_values_t 中的所有单个 values_t,并删除包含 true 和 false 的“不可能”values_t 用于某些变量.这样做的方法应该看起来很明显,我不会浪费时间来描述它,但是一旦第 1 阶段完成,第 2 阶段应该很简单。

对于第 1 阶段,我们的目标是提出一个大致如下声明的函数:

all_values_t phase1(expression_t expr, bool goal);

expr 是您的布尔表达式的解析表示,作为解析树(正如我在开头提到的,这部分由您决定)。 goal 是您希望解析表达式的计算方式:phase1() 返回所有可能的all_values_t,其中expr 的计算结果为true 或false,如“目标”所示。显然,您将调用phase1() 传递true 以获得“目标”作为您的答案,因为这就是您想要弄清楚的。但是phase1() 会递归调用自己,使用true 或false“目标”来发挥它的魔力。

在继续之前,现在阅读并理解描述归纳证明如何工作的各种资源非常重要。在您完全理解这个一般概念之前,请不要继续。

好的,现在您了解了这个概念。如果你这样做了,那么你现在必须同意我的观点,phase1() 已经完成。有用!归纳证明首先假设phase1() 做了它应该做的事情。 phase1() 将对自身进行递归调用,并且由于 phase1() 返回正确的结果,phase1() 可以简单地依靠自己来解决所有问题。看看这有多容易?

phase1() 手头确实有一个“简单”的任务:

  • 检查解析树的顶级节点是什么。它将是变量节点或表达式节点(见上文)。

  • 基于此返回适当的all_values_t。

就是这样。我们将同时考虑两种可能性。

  1. 顶级节点是一个变量。

所以,如果您的表达式只是一个变量,并且您希望表达式返回 goal,那么:

values_t v{ {name, goal} };

表达式计算为goal 只有一种可能的方法:显而易见:变量和goal 的值。

而且只有一种可能的解决方案。没有其他选择:

all_values_t res;

res.push_back(v);

return res;

现在,另一种可能性是表达式中的顶级节点是布尔运算之一:与、或、或非。

再一次,我们将分而治之,一次一个地解决每一个问题。

假设它是“不是”。那我们该怎么办?这应该很容易:

return phase1(child1, !goal);

只需递归调用phase1(),传递“not”表达式的子节点,goal 逻辑反转。因此,如果您的goal 为真,请使用phase1() 返回“非”子表达式的值为假,反之亦然。请记住,归纳证明假设 phase1() 像宣传的那样工作,因此您可以依靠它来获得子表达式的正确答案。

现在应该开始明白phase1() 的其余部分是如何工作的。只剩下两种可能:“与”和“或”逻辑运算。

对于“与”运算,我们将分别考虑“与”运算的“目标”应该是true还是false。

如果goal 为真,则必须使用phase1() 来得出all_values_t 以使两个子表达式都为真:

all_values_t left_result=phase1(child1, true);

all_values_t right_result=phase1(child2, true);

然后将两个结果组合在一起。现在,回想一下all_values_t 是所有可能 值的列表。 all_values_t(可以是空列表/向量)中的每个值都代表一种可能的解决方案。左子表达式和右子表达式都必须在逻辑上组合,但left_result 中的任何可能解决方案都可以与任何right_result 一起使用。左子表达式为真的任何潜在解决方案都可以(并且必须)与右子表达式为真的任何潜在解决方案一起使用。

因此,在这种情况下,需要返回的all_values_t 是通过在left_result 和right_result 之间执行cartesian product 获得的。即:取第一个值,left_result 中的第一个 values_t std::set,然后将第一个 right_result std::set 添加到此集合中,然后将第一个 left_result 与第二个 right_result 添加到此集合中,然后很快;然后是第二个left_result 和第一个right_result,然后是第二个right_result,依此类推。这些组合中的每一个都将push_back()ed 到all_values_t 中,该all_values_t 从此调用返回到phase1()。

但是你的goal 是让“and”表达式返回false,相反,你只需要做这个变体三次。第一次使用phase1(child2, false) 调用phase1(child1, false);然后phase1(child1, true) 和phase1(child2, false);最后是phase1(child1, false) 和phase1(child2, true)。 child1 或 child2 或两者都必须评估为 false。

这样就可以处理“与”操作了。

phase1() 处理的最后也是最后的可能性是逻辑或操作。现在,您应该可以自己弄清楚如何做,但我将简要总结一下:

如果goal 为假,您必须使用phase1(child2, false) 调用phase1(child1, false),然后将两个结果组合在一起,作为笛卡尔积。如果goal 为真,您将针对其他三种可能性进行三组递归调用,并将所有内容组合在一起。

你已经完成了。 phase1() 无事可做,我们通过归纳完成了证明。

好吧,我撒了一点谎。你还需要做一个小的“第三阶段”。回想一下,在“阶段 2”中,我们消除了所有不可能的解决方案。好吧,由于这一切,可能的解决方案的最终列表可能会在all_values_t 中多次出现相同的values_t,因此您只需对其进行重复数据删除即可。

附:作为第 1 阶段的一部分,也可以通过即时执行来避免离散的第 2 阶段。这种变化也将成为您的家庭作业。

【讨论】:

    猜你喜欢
    • 2018-05-29
    • 2013-12-15
    • 1970-01-01
    • 2020-01-26
    • 2013-10-23
    • 2017-11-24
    • 1970-01-01
    • 1970-01-01
    • 2017-10-10
    相关资源
    最近更新 更多