左子右兄弟表达式树

Posted

技术标签:

【中文标题】左子右兄弟表达式树【英文标题】:Left-child, right-sibling expression tree 【发布时间】:2013-12-03 12:59:25 【问题描述】:

我完全被我的一项任务困住了,需要一些帮助。

我们正在实现一个树类,它存储和评估二进制表达式。这是一个这样的表达式的示例:(MAJ (AND 3 4) (OR 1 2 3) (NOT 5) 4 )。这是指这棵树:

树以 LCRS 表示形式提供给我们,我们正在构建一个类来对其执行各种操作,例如查找树中的最大数、查找特定运算符的编号或打印原始导致给定树的公式。我已经弄清楚了所有这些,通过数小时的头疼和用于追踪大型递归函数的成堆纸张。

但我不知道最后一个需要的函数是

bool evaluate(const vector<bool> &values, Tree_Node* p)

此函数使用 bool 向量提供的真值计算表达式(例如,如果 values[3] 和 values[4] 都为真,则 (AND 3 4) 为真)。我花了很多时间寻找答案,并认为我对表达式树有所了解,但找不到任何关于它在 LCRS 表示中如何工作的信息。

我只是无法弄清楚递归。我把它分成了所有可能的情况,但是当你考虑到不同的运营商时,会有很多情况。为了保持简洁,我会说我的主要问题是:

如何评估每个子树?例如,在 LCRS 树中,

 AND
 /
3
 \
  4

我看到我需要回到 4,意识到这是最简单的(基本)情况,然后开始递归回到 AND,但是当我回来时我不知道如何获得正确的值到 AND。我需要一个额外的参数还是什么?也许传递操作,或者使用某种后续的额外指针?

//一些澄清的东西

四个运算符是:

MAJ - 如果大多数输入为真,则返回真 AND - 逻辑与(如果两个输入都为真,则为真) OR - 逻辑或 NOT - 否定

(假设 SO 上的人理解 and,or,&not。)

Node 结构是一个典型的结构:

struct Tree_Node   
    std::string data;
    Tree_Node* left_child;
    Tree_Node* right_sibling;

除了分配的附加功能外,Tree 类也是标准的, 如有必要,我可以发布代码,但它具有您期望的树的操作,并且可以正确编译和测试,问题仅与此功能有关。

我在这个和我的其他家庭作业之间敲打我的桌子......哦,CS专业的生活。任何帮助,一如既往,非常感谢。

编辑:

非常感谢所有帮助过我的人。我在这里得到的所有答案确实帮助我弄清楚了事情。有时它有助于获得一个新的视角。另外, std::pair 对我来说是新的!有趣的是,因为我有一个自己的模板类,它做同样的事情......猜猜是时候让那个家伙退休了!

等我完成作业后,我会把我的功能和分析贴在这里,以备日后参考。

编辑: 正如所承诺的,完成的功能。 count_children 在功能上与 rici 制定的 count 函数相同,而 TruthValues 是一个 std::pair 整数,如 Counts。再次感谢那些帮助我解决这个问题的人。当给定一个字符串(例如switch_help("MAJORITY") == 2)时,函数 switch_help 会为 switch 语句返回适当的数字。

bool BooleanFormula::evaluate(const vector<bool> & values, Tree_Node* p)

    TruthValues truth_vals = TruthValues(0,0);
    int control = switch_help(p->data);

    switch (control)
        case 1: //p->data = "MAJORITY"
            truth_vals = count_children(values, p->left_child);
            return (truth_vals.first > truth_vals.second);
            //return true if there are more trues than falses

        case 2: //p->data = "AND"
            truth_vals = count_children(values, p->left_child);
            return (truth_vals.second == 0);
            //return true if there are no falses

        case 3: //p->data = "OR"
            truth_vals = count_children(values, p->left_child);
            return (truth_vals.first >= 1);
            //return true if there is at least one true

        case 4: //p->data = "NOT"
            return !(evaluate(values, p->left_child));
            //return the inverse of what is obtained by evaluating the subtree

        default: //p->data = some number
            //in this case, we're just at a node with an index in it
            return values[atoi((p->data).c_str())];
    


【问题讨论】:

提示:递归评估函数的结果应该是一个值列表,而不是(必然)单个值。 好吧,它最终必须返回一个布尔值,尽管我明白你在说什么。我一直在玩弄这个想法——如果我能得到一个向量或包含 3 和 4 的东西,我想这会很有帮助。如果我要添加某种列表作为函数的参数,并通过引用传递它,那么...我可以在到达底部时添加值并在备份的路上构建该列表?这可能行得通,但我会遇到多个递归调用查看同一个列表的问题吗? ***函数调用根上的递归 case,断言返回列表仅包含一个元素,并将其返回。其他一切都返回一个列表。它不会通过引用传入列表。您可以在之后进行一些优化,但首先要让它发挥作用。 从实现叶子案例开始,然后逐步向上。并遵循@Sneftel 的建议。 对不起,我还是有点糊涂。你是说我应该编写一个返回列表的辅助函数(或者对于不同的运算符可能不止一个)?那么,当原始函数具有完整的列表时,只有一个元素,可以返回该列表中的一个值吗? 【参考方案1】:

如果我理解结构,在每个子树上,您都有位于根的运算符,第一个操作数作为根的左子节点,随后的操作数作为右链接 第一个操作数,对吗?

那么评估的一般方案如下所示:

bool evaluate(const vector<bool> &values, Tree_Node* p) 
    bool result; 
    switch (p->op) 
        case BASE:
            return values[p->index];
        ...
        case AND:
            result = true;
            for (op = p->left; op != nullptr; op = op->right)
               result &= evaluate (values, op);
            return result;
        ...
    

(这里我假设叶子包含value 数组中的索引。

【讨论】:

【参考方案2】:

表达式树的递归求值涉及:

评估树的每个节点

这又需要:

对子项(操作数)列表应用操作。

现在,让我们尝试稍微系统化。考虑您拥有的操作——NOTANDORMAJ——并考虑如何通过操作数列表计算每个操作数的值。具体来说,我们需要了解有关列表的哪些信息?在所有情况下,以下两个数据就足够了(只有一种情况都是必要的):

列表中有多少值是TRUE

列表中有多少值是FALSE

(或者,第二个可能是“列表中有多少个值?”,这显然是等价的。)

那么ANDTRUE 当且仅当FALSE 孩子的数量是0; ORFALSE 当且仅当 TRUE 孩子的数量为 0; MAJTRUE,如果 TRUE 孩子的数量大于 FALSE 孩子的数量。 (NOT 至少有两个泛化到操作数列表,或者您可以将其限制为 TRUE 子代数为 0 且FALSE 子代数为 1 的情况。)

现在,假设我们已经将列表缩减为这两个值,nTRUEnFALSE,并且我们想要向列表中添加一个新元素(这将是递归步骤。)我们可以这样做吗?明显地。如果新元素是TRUE,我们增加nTRUE;如果新元素是FALSE,我们增加nFALSE。瞧!

在 C++ 中,表示一对值的标准方式是std::pair。使用它,我们可以如下表达我们的递归归约:

typedef std::pair<int, int> Counts;

Counts count(Tree_Node* node) 
  if (!node) return Counts0, 0;
  Counts rest = count(node->right_sibling);
  if (evaluate(node))
    return Countsrest.first + 1, rest.second;
  else
    return Countsrest.first, rest.second + 1;

假设evaluate 返回一个布尔值。但是如果评估返回一个Counts 对象,我们可以使这更加优雅,使用1,0 表示真,0,1 表示假。然后我们可以写:

typedef std::pair<int, int> Counts;
Counts operator+(const Counts& a, const Counts& b) 
  return a.first + b.first, a.second + b.second;


Counts count(Tree_Node* node) 
  if (!node)
    return Counts0, 0;
  else
    return evaluate(node) + count(node->right_sibling);

在这两种情况下,我都省略了evaluate 的定义,它需要根据运营商而有所不同。但我希望evaluate(node) 使用count(node-&gt;left_child) 的方式很明显。

【讨论】:

以上是关于左子右兄弟表达式树的主要内容,如果未能解决你的问题,请参考以下文章

Java实现后缀表达式建立表达式树

剑指offer树的子结构python

将一棵树转换为二叉树后,为什么根节点没有右子树

先序中序反推二叉树

二叉搜索树

爬虫----day04()