在C ++中检测相同表达式的好方法

时间:2015-09-24 13:33:42

标签: c++ algorithm

我正在编写一个解决这个益智游戏的程序:给出一些数字和一个目标数,然后使用n个数字和运算符+, - ,*,/和()来设定目标数。例如,给定2,3,5,7和目标编号10,解决方案为(2+3)*(7-5)=103*5-(7-2)=10,依此类推。

问题是,如果我天真地实现它,我会得到一堆相同的解决方案,例如(2+3)*(7-5)=10(3+2)*(7-5)=10,以及3*5-(7-2)=105*3-(7-2)=10和{{ 1}}和3*5-7+2=10等等。所以我想检测那些相同的解决方案并修剪它们。

我目前正在使用随机生成的双号来检测相同的解决方案。我正在做的基本上是将这些随机数替换为解决方案,并检查是否有任何一对计算到相同的数字。我必须在我的搜索的每个节点执行检测,所以它必须很快,我现在使用hashset。

现在问题是计算带来的错误。因为即使是相同的解决方案也不会计算到完全相同的值,因此当存储在hashset中时,我目前将计算值舍入为精度。然而,这似乎不能很好地工作,并且每次针对同一问题给出不同数量的解决方案。有时随机数很差,并修剪一些完全不同的解决方案。有时,计算值位于舍入函数的边缘,并输出两个(或更多)相同的解。有更好的方法吗?

编辑: 通过"相同"我的意思是两个或更多的解决方案(f(w,x,y,z,...)和g(w,x,y,z,...))无论原始数量是多少都计算到相同的数字(w, x,y,z ......)是。更多示例,4/3 * 1/2和1 * 4/3/2和(1/2)/(3/4)相同,但是4/3/1/2和4 /(3 * 1) / 2不是因为如果你将1改为其他数字,它们将不会产生相同的结果。

5 个答案:

答案 0 :(得分:8)

如果你"规范化"比较之前的表达式。一种方法是对操作是可交换的进行排序,因此3+2变为2+3,而2+3保持不变。当然,您还需要为括号内的组建立排序,例如3+(2*1) ...这会成为(1*2)+3还是3+(1*2)?只要它是一个总排序,那么排序并不一定重要。

答案 1 :(得分:2)

生成表达式的所有可能性。然后.. 创建表达式时,将它们放在已解析树的集合中(这也会消除括号)。然后"按下"任何除法和减法到叶节点,以便所有非叶节点都有*和+。应用分支的排序(例如常规字符串排序),然后比较树以查看它们是否相同。

答案 2 :(得分:0)

我喜欢使用双打的想法。问题在于四舍五入。为什么不使用一个随机的双输入值获得的值来使用容器SORTED。当您找到要插入该容器的位置时,您可以查看前一个和后一个项目。使用一组不同的随机双精度来重新计算每个随机双精度数以进行更稳健的比较。那么你可以有一个合理的截止值,足够接近等于"没有任意舍入。

如果一对表达式在主要的随机数和第二组中足够相近,则表达式是安全的,相同的"而新的一个被丢弃了。如果在主集合中足够接近相等而不是新集合,则您有一个罕见的问题,可能需要使用不同的随机数集重新键入整个容器。如果两者都不够接近,那么它们就不同了。

答案 3 :(得分:0)

由于你正在处理整数,我会专注于得到一个确切的结果。

声明:假设有一些f(a_1,...,a_n)= x,其中a_i和x是整数输入数,f(a_1,...,a_n)代表任何所需形式的功能。然后很明显f(a_i) - x = 0.我声称,我们可以构造一个不同的函数g,其中g(x,a_1,...,a_n)= 0表示完全相同的x而g只使用()s,+ , - 和*(没有分裂)。

我将在下面证明。因此,您可以仅在整数上构造g evaluate g(x,a_1,...,a_n)= 0。

示例: 假设我们有a_i = i for i = 1,...,4和f(a_i)= a_4 /(a_2 - (a_3 / 1))(其中包含到目前为止的除法)。这就是我想要简化的方式:

0 = a_4 / (a_2 - (a_3 / a_1) ) - x          | * (a_2 - (a_3 / a_1) )
0 = a_4 -       x * (a_2 - (a_3 / a_1) )    | * a_1
0 = a_4 * a_1 - x * (a_2 * a_1 - (a_3) )   

在这种形式中,您可以仅使用整数运算来验证某些给定整数x的相等性。

<强>证明: 有一些g(x,a_i):= f(a_i) - x等于f。考虑任何等价的g尽可能少的除法。假设至少有一个(否则我们已经完成)。假设在g内我们除以h(x,a_i)(任何函数,可能包含除法本身)。然后(g * h)(x,a_i):= g(x,a_i)* h(x,a_i)具有相同的根,如g所示(乘以根,即(x,a_i)其中g( a_i) - x = 0,保留所有根)。但另一方面,g * h由一个分区组成。矛盾(g与最小分割数量),这就是为什么g不包含任何除法。

我已经更新了示例以显示策略。

更新:这对合理输入数字(那些代表单个部门p / q)很有效。这应该对你有帮助。其他输入不能由人提供。

你在做什么来寻找/测试f?我猜想某种形式的动态编程在实践中会很快。

答案 4 :(得分:0)

对于你最近的一条评论所建议的较大的n,我认为你需要通过施工方法规范的更好的表现(或者可能&#34;几乎&#34;规范的建筑)而不是主要基于比较的方法。

想要构建令人难以置信的大量表达式,然后进行规范化和比较。

定义一个双重递归函数can(...)作为输入: 对规范表达式树的引用。 对该树的一个子表达式的引用。 要注入的输入数N. 一组禁止一些注射的标志。 要调用的叶子函数。

如果N为零,can只调用叶函数。如果N非零,则can以生成具有N个注入变量的规范树的每种可能方式修补子树,并为每个调用叶函数并恢复树,撤消补丁的每个部分,如同完成它,所以我们永远不需要大规模复制。

X是子树,K是表示变量N-1的叶子。第一个can会一次一个地替换子树,子树代表(X)+K(X)-K(X)*K(X)/KK/(X)中的一些但是标志和其他一些规则会导致其中一些被跳过。对于每个未跳过的,递归调用自身,整个树作为顶部和子,具有N-1和0标志。 接下来深入研究X的两个子节点,并使用N作为子树递归调用,并使用适当的标记。

外部只调用can,其中一个节点树代表原始N的变量N-1,并传递N-1。

在讨论中,向前命名输入更容易,因此A输入N-1,B输入N-2等。

当我们深入X并看到它是Y+ZY-Z时,我们不想在Y或Z中添加或减去K,因为这些是X+KX-K。所以我们传递一个禁止直接加或减的标志。

同样,当我们深入X并看到它是Y*ZY/Z时,我们不希望将Y或Z乘以或除以K,因为这对于乘法或除法来说是多余的X by K。

有些情况需要进一步澄清:

(A/C)/BA/(B*C)很容易非规范,因为我们更喜欢(A/B)/C,因此在将C分配到(A / B)时,我们禁止直接乘法或除法。

我认为在拒绝C/(A*B)所涵盖的C/(A/B)时允许(B/A)*C需要更多努力。

如果否定本质上是非规范的,那么更容易,因此第1级只是A并且不包括-A然后如果整个表达式产生负目标值,则我们否定整个表达式。否则,我们永远不会看到规范表达的否定:

给定X,我们可能会访问(X)+ K,(X)-K,(X)* K,(X)/ K和K /(X),我们可能会深入到X传递旗帜的部分它抑制了上述部分的一些情况:

如果X是+-抑制&#39; +&#39;或者&#39; - &#39;在其直接部分。如果X是*/压制*或其直接部分除以。

但如果X是/,我们在钻入X之前也会压制K/(X)