【问题标题】:how to convert a propositional logical tree into conjunction normal form (CNF) tree如何将命题逻辑树转换为合取范式 (CNF) 树
【发布时间】:2013-04-18 19:22:01
【问题描述】:

我有一个类似字符串的字符串

     s="(=> P (OR (AND A (NOT B)) (AND B (NOT A))))";

并转换它输出这个字符串的CNF,比如

( 或 ( 非 P ) ( 或 A B ) ) ( 或 ( 非 P ) ( 或 ( 非 B ) ( 非 A ) ) )

我是否需要创建一个结构 TreeNode 来保留该值?

     struct TreeNode {
            string val;         // The data in this node.
            TreeNode *left;   // Pointer to the left subtree.
            TreeNode *right;  // Pointer to the right subtree.
            //TreeNode *farther;//should I use farther or not in convert to CNF?
      };

如何使它成为合取范式的CNF? 请给出一些算法细节。 从我的角度来看,也许使用递归函数更好地解决这个问题,但我仍然想不出如何使用递归。或者您有其他解决此问题的建议?

【问题讨论】:

  • 请做好自己的工作。当你遇到困难时,问一个具体的问题。但这里有一个提示:“递归下降解析器”。另一个提示:程序需要做的第一件事是什么?
  • 我只是想了解一些算法并从中学习一些东西,以便我可以做我自己的工作,我不希望有人为我发布源代码。我确实很难找到有用的信息,但仍然无法弄清楚,这就是我在这里提问的原因,我错了吗?
  • 在你写这篇文章的时候,你可能已经用谷歌搜索了“递归下降解析器”并且已经走了很长的路。
  • 没有问题,如果您觉得有帮助,请接受答案!
  • 丽贝卡,我已经发布了一个答案,应该可以轻松引导您完成一个完整的程序。

标签: c++ c recursion conjunctive-normal-form prefix-tree


【解决方案1】:

假设您将函数命名为 CNF,获取一棵树并在 CNF 中返回该树。

  1. 首先,将等效的 p<=>q 替换为 AND(p=>q,q=>p),然后将含义 p=>q 替换为 OR(q,NOT(p))

  2. 转换为否定范式:将所有NOT 操作向下移动,以便NOT 操作仅绑定到原子(AB)。

  3. 那么,CNF(AND(x,y)) 的结果很简单:AND(CNF(x),CNF(y)),因为 ANDs 在树的高处就像 CNF。

  4. CNF(OR(AND(x,y),z)) 的结果有点复杂。这里我们将使用析取大于合取的分布规则,即OR(AND(x,y),z) 等价于AND(OR(x,z),OR(y,z))。实际上,CNF(OR(AND(x,y),z)) 将是 AND(OR(CNF(x),CNF(z)),OR(CNF(y),CNF(z))

你已经完成了。

【讨论】:

  • 所以你的算法是递归还是不是?底部的暗示(=>)呢,X=>Y CNF:不是x和Y,如果字符串很长,事情会很复杂,工作量很大
  • @Rebecca Adrian 谈到了影响。是的,它是递归的……任何树遍历都是。例如,你看到“AND(CNF(x),CNF(y))”了吗?这是 CNF 的递归调用。对于一个有经验的程序员来说,这并不是所有的工作。如果你不是其中之一,也许你应该从一个更简单的问题开始。
  • @Rebecca 在某些情况下,算法的输出将在输入大小方面呈指数增长,例如对于OR(AND(x_1,y_1),AND(x_2,y_2),...AND(x_n,y_n)),结果将是OR(z_1, z_2, ..., z_n)形式的所有子句与每个Z_iX_iY_i
  • @AdrianPanasiuk 我正在使用您提到的相同方法。但是有可能在一次遍历树中应用所有分配律吗?我的问题是 a&b&c|d 之类的东西转换为 ((a|d)&((b&c)|d)),所以我必须进行两次遍历才能获得所需的:((a|d)&((b| d)&(c|d)))。这是我做错了什么还是意料之中的?
  • @Wyvern666 尝试将 ANDOR 视为 n 元而不是二进制 - 所以 a&b&c|d 变为 OR(AND(a,b,c),d) 而不是 OR(AND(a,AND(b,c)),d)
【解决方案2】:

简单的递归下降解析器解决方案:

TreeNode* ParseExpression(const char*& p): 如果 p 指向的字符串不是以 '(' 开头,则返回 ParseAtom(p),否则将 p 移过 '(',调用 ParseOperation(p),然后将 p 移过 ') ' 并返回 ParseOperation 返回的值。

TreeNode* ParseAtom(const char*& p):跳过原子(连续的非空格序列)。返回一个以原子为值且左右为 NULL 的 TreeNode。

TreeNode* ParseOperation(const char*& p): p 指向的字符串应该以运算符开头。将 p 移过运算符,然后确定运算符需要多少个操作数。如果有,则调用 ParseExpression(p) 一次;如果两个,调用 ParseExpression(p) 两次。然后返回一个以运算符为值的 TreeNode,并将一两次 ParseExpression 调用的结果作为左和右(对于只有一个操作数的运算符,右应为 NULL)。

设置一个指向原始字符串的指针;在该指针上调用 ParseExpression;返回值是您的树,指针将指向字符串中的第一个表达式。

这解决了您的一个问题:如何将字符串变成树。 Adrian Panasiuk 解决了另一个问题,即如何将树转换为正常形式。由于您将进行额外的树转换,因此节点中的“值”应该称为“op”或类似的名称来代表“运算符”(这是 C++ 中的保留字),它应该是一个枚举,而不是字符串。

【讨论】:

  • 所以实际上,不需要在树中转换字符串,这会使事情变得复杂,对吧?是否有可能我可以使用 substr() 和 find() 和 strcpy() 之类的东西来使其成为 CNF?
  • @Rebecca 当然需要将字符串转换为树,我只是告诉你怎么做。
  • 为什么有人会在发布 8 年后不加解释地否决它?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多