在逆波兰表示法表达式中查找数字输入和运算符的所有有效组合

计算科学 算法 组合学
2021-11-28 20:17:22

用逆波兰(后缀)表示法编写的算术表达式是数字和代数运算符的有序列表,它们按顺序计算,因为堆栈将处理它们以返回单个数字结果。如果表达式包含 n 个数字,则如果每个数字和每个运算符都用于计算结果,则它必须包含精确的 (n-1) 个代数运算符。此外,表达式中的前两个元素必须是数字,最后一个元素必须是运算符 - 因此序列中唯一可以数字运算符的元素是这两个极端之间的元素,即第 3、第 4、第 5等直到最后一个元素,但一个(即元素编号(2n - 2),因为序列中有(2n - 1)个元素。

我想评估这些中间元素的所有可能组合,其中有 (n-2) 个数字和 (n-2) 个运算符。我知道他们有多少:它将是 2(n-2) 选择 (n-2),生成所有这些排列是一项非常简单的任务 - 但一般来说,并非所有排列都会提供可解析的表达式。例如,如果n = 4,则总共有4C2 = 6个排列,前面有两个数字,最后有一个运算符:如果N代表一个数字,O代表一个运算符,它们是NNNNOOO,NNNONOO,NNNOONO,NNONNOO, NNONONO 和 NNOONNO。但是,这六个中的最后一个不会转换为有效的算术表达式,因为在执行第二个操作之后,堆栈已被清除(或者换句话说,在序列的前四个元素中,数字的数量不超过运营商的数量)。如果作为堆栈处理,这个特定示例实际上不会无法交付结果,

我可以轻松地生成中间元素的所有可能排列,然后对它们进行检查以查看哪些产生可解析的序列,但如果可能的话,我更喜欢生成可解析的序列。任何人都可以指出一个可以做到这一点的算法吗?

我毫不怀疑有一个,而且它可能是众所周知的 - 但我还没有找到一种足够简单的方法来制定问题,使我能够通过搜索引擎对其进行追踪。

非常感谢您的任何建议。

1个回答

我怀疑如果您从表达式的中缀表达式“树”开始并在您真的想要时将其转换为 RPN 格式,则仅生成有效表达式可能会更容易。

每个节点都可以是二元运算符O或一个数字N.O根据定义,节点不能是叶节点,因为它们恰好需要两个子节点,并且N节点必须是叶节点,因为它们不能有任何子节点。

因为我们知道O节点永远不是叶子和和N节点总是叶子,我们甚至不需要正确地标记它们。在这一点上,我们已经将问题转化为我们需要生成所有可能的完整二叉树的问题N叶子(或者,我们想要所有可能的完整二叉树O+N=2N1节点总数)。通过完整的二叉树,我的意思是任何人都不可能O节点只有 1 个孩子;每个节点要么恰好有 2 个子节点,要么没有。

有一种递归算法可用于为您生成这些树。这是一个 Python 实现示例。

def gen_full_bst(N):
    # N >= 1 is the number of leaf (number) nodes
    # the string 'N' denotes a leaf node, and a tuple denotes an operator node.
    if N == 1:
        yield 'N'
    
    # allow N-n leaf nodes to belong to the left child, and the remainder belongs to the right child
    for n in range(1, N):
        left_N = N - n
        right_N = N - left_N
        for left_child in gen_full_bst(left_N):
            for right_child in gen_full_bst(right_N):
                yield (left_child, right_child)

要将中缀树树转换为 RPN 格式,只需使用 BST 的后序遍历当一个节点被“访问”(不仅仅是遍历)时,将该节点添加到 RPN 堆栈的底部。

def bst_to_rpn(tree):
    if tree == 'N':
        return 'N'
    return f'{bst_to_rpn(tree[0])}{bst_to_rpn(tree[1])}O'

测试这个N=4

for tree in gen_full_bst(4):
    print(bst_to_rpn(tree))

生产

NNONONO
NNNOONO
NNONNOO
NNNONOO
NNNNOOO

作为旁注,我实现的方式gen_full_bst没有任何结果的中间缓存。例如,总共gen_full_bst(4)调用gen_full_bst(2)6 次。您可以实现一个查找表,其中存储预先计算的值,然后重新使用以加速计算,但会占用内存空间。从我电脑上的一些基本测试来看,非缓存版本似乎可以达到大约N=14.