C 给定一个布尔表达式,决定在何处放置括号,使其变为真
我正在考虑创建一个程序,在给定一个布尔表达式的情况下,例如,true和false xor true会在“操作”周围加上括号,并返回结果为true的新方程。我该怎么做,从哪里开始?动态规划中是否有一个小算法我应该搜索?这里的一个观察结果是,如果您最终得到一个全括号表达式,那么整个表达式中将有一些顶级运算符。基于该运算符,如果多个不同情况中的一种情况成立,则整个表达式的计算结果将为true。例如,如果顶级运算符是AND,则如果AND左侧和右侧的子表达式为true,则表达式为true。如果顶级操作是OR,则如果左或右子表达式的计算结果为true,则整个表达式的计算结果为true。如果顶级运算符为NOT,则如果NOT的子表达式为false,则整个表达式为true 您可以想象一个自上而下的递归算法,其工作原理如下:对于每个操作符,暂时假定它是顶级操作符,并递归地确定所讨论的子表达式是否可以具有适当的真值。如果是这样的话,这个公式就可以成立了。如果没有,它就不能 这里可能的子表达式/真值对的数量是O(n2),因为每个子表达式只是原始表达式的子数组,并且只有O(n2)。因此,将有许多重叠的子问题,因此您可以使用自顶向下的记忆或自下而上的wou动态规划计算结果 <>这似乎是个很有趣的问题,所以我实际上用C++来编码(不是,C,很不幸,因为这看起来很难)。C 给定一个布尔表达式,决定在何处放置括号,使其变为真,c,algorithm,boolean,C,Algorithm,Boolean,我正在考虑创建一个程序,在给定一个布尔表达式的情况下,例如,true和false xor true会在“操作”周围加上括号,并返回结果为true的新方程。我该怎么做,从哪里开始?动态规划中是否有一个小算法我应该搜索?这里的一个观察结果是,如果您最终得到一个全括号表达式,那么整个表达式中将有一些顶级运算符。基于该运算符,如果多个不同情况中的一种情况成立,则整个表达式的计算结果将为true。例如,如果顶级运算符是AND,则如果AND左侧和右侧的子表达式为true,则表达式为true。如果顶级操作是O
#包括
#包括
#包括
#包括
#包括
#包括
使用名称空间std;
/**
*枚举类型:符号
*
*表示可以出现在布尔表达式中的符号的类型。
*/
枚举符号{
是的,
假,,
以及,
或
不
暗示,
IFF,
异或
};
/**
*别名:子问题
*
*一个类型的名称,表示已记忆的文件中的一个子问题
*解决方案。每个子问题对应于一个子阵列(由一个起始点给出)
*和结束位置)和预期真值(真或假)
*/
使用子问题=元组;
名称空间标准{
模板结构哈希{
布尔运算符()(常量子问题&s)常量{
返回hash()(get(s))+31*hash()(get(s))+127*hash()(get(s));
}
};
}
/*内部执行*/
名称空间{
/**
*函数:canYield(常数向量和公式,
*大小开始,大小结束,
*无序映射和子问题,
*布尔(预期);
* --------------------------------------------------------------------------
*确定子表达式是否在公式的范围[开始,结束]中
*由公式给出的可对结果进行评估。记录其结果
*避免无关的计算。
*/
布尔产量(常数向量和公式,
大小开始,大小结束,
无序映射和子问题,
布尔(预计){
/*边缘案例检查:我们不应该有一个空范围*/
断言(结束>开始);
/*首先看看我们是否已经计算过了。如果已经计算过了,我们就完成了!
*如果没有,我们必须做一些工作。当这个If语句完成时,
*soln应该是有效的。
*/
const Subproblem thisSubproblem=make_tuple(开始、结束、预期);
if(!subproblem.count(thisSubproblem)){
/*基本情况:如果这是单个元素范围,则必须为
*无论是真是假,我们都应该能够读出答案
*直接的。
*/
如果(开始+1==结束){
/*这必须为真或假;否则公式格式不正确*/
断言(公式[start]==TRUE | |公式[start]==FALSE);
/*查看枚举常量是否与预期值匹配*/
bool isTrue=(公式[start]==TRUE);
子问题[thisSubproblem]=(isTrue==预期值);
}
/*否则,我们的射程会更长*/
否则{
/*如果此范围以NOT开头,一个选项是用括号括起来
*表达式的其余部分,试图得到与
*预料之中。
*/
如果(公式[开始]==不&&
canYield(公式、开始+1、结束、子问题、!预期)){
子问题[此子问题]=真;
}否则{
/*在该范围内迭代,尝试将所有二进制运算符置于
*如果可能的话,最高级别。如果其中任何一个有效,我们就完蛋了!
*/
对于(大小\u t i=开始;i<结束;i++){
/*为了处理和或,我们将使用de Morgan定律
*使我们能够将一系列案例结合在一起。
*/
if(公式[i]==和| |公式[i]==或){
/*这是观察家。如果我们想做出一个正确的判断
*或者一个或多个,我们需要让两个子公式匹配
*期望的真值。如果我们试图做出错误的判断
*或者一个或真,我们需要得到一个子公式来匹配
*期望真值。所以我们需要看到
*和/或的组合,以及我们是否希望为真/假。
*/
布尔奇偶性=((公式[i]==和)==预期值);
/*如果平价匹配,我们要么寻找AND/true
*或一个或/假。
*/
if(奇偶校验)&&
canYield(公式、开始、i、子问题、预期)&&
canYield(公式,i+1,结束,子问题,预期)){
子问题[此子问题]=真;
打破
}
/*如果双方不同意,我们要么寻找AND/false
*对。
*/
否则如果(!奇偶校验)&&
(canYield(公式、开始、i、子问题、预期)||
#include <string>
#include <vector>
#include <unordered_map>
#include <utility>
#include <cassert>
#include <iostream>
using namespace std;
/**
* Enumerated type: Symbol
*
* A type representing a symbol that can appear in a boolean expression.
*/
enum Symbol {
TRUE,
FALSE,
AND,
OR,
NOT,
IMPLIES,
IFF,
XOR
};
/**
* Alias: Subproblem
*
* A name for a type representing one of the subproblems in the memoized
* solution. Each subproblem corresponds to a subarray (given by a start
* and end position) and an expected truth value (either true or false.)
*/
using Subproblem = tuple<size_t, size_t, bool>;
namespace std {
template <> struct hash<Subproblem> {
bool operator()(const Subproblem& s) const {
return hash<size_t>()(get<0>(s)) + 31 * hash<size_t>()(get<1>(s)) + 127 * hash<bool>()(get<2>(s));
}
};
}
/* Internal implementation. */
namespace {
/**
* Function: canYield(const vector<Symbol>& formula,
* size_t start, size_t end,
* unordered_map<Subproblem, bool>& subproblems,
* bool expected);
* --------------------------------------------------------------------------
* Determines whether the subexpression in range [start, end) of the formula
* given by formula can be made to evaluate to result. Memoizes its results
* to avoid extraneous computations.
*/
bool canYield(const vector<Symbol>& formula,
size_t start, size_t end,
unordered_map<Subproblem, bool>& subproblems,
bool expected) {
/* Edge case check: we shouldn't have an empty range. */
assert (end > start);
/* Begin by seeing if we've already computed this. If so, we're done!
* If not, we have to do some work. By the time this if statement finishes,
* soln should be valid.
*/
const Subproblem thisSubproblem = make_tuple(start, end, expected);
if (!subproblems.count(thisSubproblem)) {
/* Base case: If this is a single-element range, it must be either
* to TRUE or FALSE, and we should be able to read off the answer
* directly.
*/
if (start + 1 == end) {
/* This has to be TRUE or FALSE; otherwise the formula is malformed. */
assert (formula[start] == TRUE || formula[start] == FALSE);
/* See whether the enumerated constant matches the expected value. */
bool isTrue = (formula[start] == TRUE);
subproblems[thisSubproblem] = (isTrue == expected);
}
/* Otherwise, we have a longer range. */
else {
/* If this range begins with a NOT, one option is to parenthesize
* the rest of the expression to try to get the opposite of what's
* expected.
*/
if (formula[start] == NOT &&
canYield(formula, start + 1, end, subproblems, !expected)) {
subproblems[thisSubproblem] = true;
} else {
/* Iterate over the range, trying to put all binary operators at
* the top level if possible. If any of them work, we're done!
*/
for (size_t i = start; i < end; i++) {
/* To handle AND and OR, we'll use the fact that de Morgan's laws
* allows us to unite a bunch of cases together.
*/
if (formula[i] == AND || formula[i] == OR) {
/* Here's the observatino. If we're trying to make an AND true
* or an OR false, we need to get both subformulas to match
* the expected truth value. If we're trying to make an AND false
* or an OR true, we need to get one subformula to match the
* expected truth value. So we need to see the parity of the
* combination of AND/OR and whether we want true/false.
*/
bool parity = ((formula[i] == AND) == expected);
/* If the parities match, we're either looking for an AND/true
* or an OR/false.
*/
if (parity &&
canYield(formula, start, i, subproblems, expected) &&
canYield(formula, i+1, end, subproblems, expected)) {
subproblems[thisSubproblem] = true;
break;
}
/* If the parities disagree, we're either looking for AND/false
* or OR/true.
*/
else if (!parity &&
(canYield(formula, start, i, subproblems, expected) ||
canYield(formula, i+1, end, subproblems, expected))) {
subproblems[thisSubproblem] = true;
break;
}
}
/* If we see an XOR or IFF, we can similarly use a de Morgan's
* trick.
*/
else if (formula[i] == XOR || formula[i] == IFF) {
/* If we're looking for an XOR/true or an IFF/false, then we
* need exactly one of the two subproblems to be true. If we're
* looking for an XOR/false or an IFF/true, then we need both
* of the subproblems to be true or both to be false. Let's
* start by determining which case we're in.
*/
bool parity = ((formula[i] == XOR) == expected);
/* Fire off the four subproblems we need to consider. */
bool fTrue = canYield(formula, start, i, subproblems, true);
bool fFalse = canYield(formula, start, i, subproblems, false);
bool sTrue = canYield(formula, i+1, end, subproblems, true);
bool sFalse = canYield(formula, i+1, end, subproblems, false);
/* If we have true parity, we're looking for XOR/true or IFF/
* false, so we want the first and second halves to disagree.
*/
if (parity && ((fTrue && sFalse) || (fFalse && sTrue))) {
subproblems[thisSubproblem] = true;
break;
}
/* Otherwise, we're looking for equal parity, so we need the
* first and second halves to agree.
*/
else if (!parity && ((fTrue && sTrue) || (fFalse && sFalse))) {
subproblems[thisSubproblem] = true;
break;
}
}
/* Implications are their own can of worms since they're so
* different than everything else.
*/
else if (formula[i] == IMPLIES) {
/* For 'true,' we check if the antecedent can be made false
* or the consequent made true.
*/
if (expected &&
(canYield(formula, start, i, subproblems, false) ||
canYield(formula, i+1, end, subproblems, true))) {
subproblems[thisSubproblem] = true;
break;
}
/* For 'false,' we see if the antecedent is true and the
* consequent is false.
*/
if (!expected &&
canYield(formula, start, i, subproblems, true) &&
canYield(formula, i+1, end, subproblems, false)) {
subproblems[thisSubproblem] = true;
break;
}
}
}
}
}
}
/* Return the solution we found. Notice that as a cute trick, if we didn't
* record a true value in the course of solving the problem, then this
* read will default to false - which is what we wanted!
*/
return subproblems[thisSubproblem];
}
}
/**
* Function: canParenthesizeToYieldTrue(const vector<Symbol>& formula);
* Usage: if (canParentheiszeToYieldTrue({FALSE, AND, NOT, FALSE})) // false
* if (canParenthesizeToYieldTrue({NOT, FALSE, AND, FALSE})) // true
*
* ----------------------------------------------------------------------------
* Given a boolean expression, returns whether it's possible to add parentheses
* into the expression in a way that causes that expression to evaluate to
* true. It is assumed that this formula is well-formed and nonempty.
*/
bool canParenthesizeToYieldTrue(const vector<Symbol>& formula) {
/* Table for memoizing whether each subproblem is solvable or not. */
unordered_map<Subproblem, bool> subproblems;
return canYield(formula, 0, formula.size(), subproblems, true);
}
/* Prints out an expression in a nice form. */
void printExpr(const vector<Symbol>& problem) {
for (Symbol s: problem) {
switch (s) {
case TRUE: cout << "true"; break;
case FALSE: cout << "false"; break;
case AND: cout << " /\\ "; break;
case OR: cout << " \\/ "; break;
case NOT: cout << "!"; break;
case XOR: cout << " + "; break;
case IFF: cout << " <-> "; break;
case IMPLIES: cout << " -> "; break;
default: assert(false);
}
}
}
/* Runs a test case to see if the program behaves as expected. */
void check(const vector<Symbol>& problem, bool expected) {
bool result = (canParenthesizeToYieldTrue(problem) == expected);
if (result) {
cout << " pass: ";
printExpr(problem);
cout << endl;
}
else {
cout << "! FAIL: ";
printExpr(problem);
string throwaway;
getline(cin, throwaway);
}
}
int main() {
/* Basic logic checks. */
check({TRUE}, true);
check({FALSE}, false);
check({NOT, TRUE}, false);
check({NOT, FALSE}, true);
check({TRUE, AND, TRUE}, true);
check({TRUE, AND, FALSE}, false);
check({FALSE, AND, TRUE}, false);
check({FALSE, AND, FALSE}, false);
check({TRUE, OR, TRUE}, true);
check({TRUE, OR, FALSE}, true);
check({FALSE, OR, TRUE}, true);
check({FALSE, OR, FALSE}, false);
check({TRUE, XOR, TRUE}, false);
check({TRUE, XOR, FALSE}, true);
check({FALSE, XOR, TRUE}, true);
check({FALSE, XOR, FALSE}, false);
check({TRUE, IFF, TRUE}, true);
check({TRUE, IFF, FALSE}, false);
check({FALSE, IFF, TRUE}, false);
check({FALSE, IFF, FALSE}, true);
check({TRUE, IMPLIES, TRUE}, true);
check({TRUE, IMPLIES, FALSE}, false);
check({FALSE, IMPLIES, TRUE}, true);
check({FALSE, IMPLIES, FALSE}, true);
/* Harder ones! */
check({TRUE, AND, NOT, TRUE}, false);
check({NOT, TRUE, AND, TRUE}, false);
check({FALSE, AND, NOT, FALSE}, false);
check({NOT, FALSE, AND, FALSE}, true);
check({TRUE, OR, NOT, TRUE}, true);
check({NOT, TRUE, OR, TRUE}, true);
check({FALSE, OR, NOT, TRUE}, false);
check({NOT, TRUE, OR, FALSE}, false);
check({NOT, FALSE, IMPLIES, TRUE}, true);
check({NOT, FALSE, IMPLIES, FALSE}, false);
check({NOT, FALSE, XOR, TRUE}, false);
check({NOT, FALSE, IFF, FALSE}, false);
/* Ridiculous */
check({NOT, NOT, FALSE, OR, FALSE, OR, FALSE}, false);
}