Распределение И по ИЛИ в двоичном дереве (конъюнктивная нормальная форма)

Я пытаюсь преобразовать двоичное дерево, например

OR (Implementation of Operator - a specialisation of TreeNode... see below)
|-A (Implementation of TreeNode... see below)
|-OR
  |-B
  |-AND (Implementation of Operator - a specialisation of TreeNode... see below)
    |-C
    |-OR
      |-D
      |-E

в его эквивалентное представление Conjunctive Normal Form (CND). Я полагаю, что, поскольку я использую только логические операторы ИЛИ + И, единственный шаг, который я должен выполнить, - это распределение И по ИЛИ. Это приведет к созданию следующего дерева (все еще двоичного для моих целей) в CNF:

AND
|-OR
| |-A
| |-OR
|   |-B
|   |-OR
|     |-E
|     |-D
|-OR
  |-A
  |-OR
    |-B
    |-OR
      |-E
      |-C

У меня проблемы с созданием алгоритма, чтобы сделать это... пока у меня есть следующий скелет, который переписывает дерево снизу вверх (обратите внимание на рекурсивный вызов для восстановления):

public TreeNode reconstruct(TreeNode treeNode) {
  if(treeNode instanceof Operator) {
    TreeNode left = reconstruct(((Operator)treeNode).getLeft());
    TreeNode right = reconstruct(((Operator)treeNode).getRight());

    return distribute(treeNode, left, right);
  }
  else
    return node;
}

Используя классы:

 -----------
|  TreeNode | // Interface
 -----------
      ^
      |
 -----------
| Operator  | // Interface
 -----------
| getLeft() |
| getRight()|
| setLeft() |
| setRight()|
 -----------

Кто-нибудь может предложить реализацию дистрибутива, которая конвертируется в CNF?

// РЕДАКТИРОВАТЬ 1 (после ответа от nif)

private Node distribute(TreeNode node, TreeNode left, TreeNode right) {
  if (node instanceof Or) {
    if (left instanceof And) {
      // distribute right over left AND
      return 
        new And(
          new Or(((Operator)left).getLeft(), right),
          new Or(((Operator)left).getRight(), right)
        );
    } 
    else if (right instanceof And) {
      // distribute left over right AND
      return 
        new And(
          new Or(((Operator)right).getLeft(), left),
          new Or(((Operator)right).getRight(), left)
        );
    }
  }
  if(node instanceof Operator) {
    ((Operator)node).setLeft(left);
    ((Operator)node).setRight(right);
  }
  // default
  return node;
}

2 ответа

Решение

Если AND а также OR единственные операторы, которых вы используете, не должно быть сложно преобразовать ваше дерево в CNF. Все, что вам нужно сделать, это найти структуры в форме OR(AND(X,Y), Z) или же OR(Z, AND(X,Y)) и использовать закон распределения.

private static TreeNode distribute(TreeNode n, TreeNode left, TreeNode right) {
  if (n instanceof Or) {
    if (left instanceof And) {
      // distribute right over left AND
      return new And(new Or(left.getLeft(), right), 
                     new Or(left.getRight(), right));
    } else if (right instanceof And) {
      // distribute left over right AND
      return new And(new Or(right.getLeft(), left), 
                     new Or(right.getRight(), left));
    }
  }

  // no change
  return treeNode;
}

Этот алгоритм должен применяться ко всем узлам вашего дерева, пока дерево больше не изменится. Порядок, в котором вы применяете алгоритм к узлам, не имеет значения. Интуитивно, повторное применение алгоритма подтянет все AND узлы над OR узлы, пока дерево не находится в CNF.

TreeNode root = ....;
while (true) {
  TreeNode transformedRoot = reconstruct(root);
  if (root.equals(transformedRoot)) {
    break;
  }
  root = transformedRoot;
}
// root is now in CNF

Примечание: имейте в виду, что преобразование CNF может взорвать ваше дерево в геометрической прогрессии. Показанная реализация довольно примитивна и не использует никаких улучшений для сокращения времени вычислений.

Я рекомендую вам посмотреть, как перемещается дерево, в вашем коде выглядит поиск в глубину, поэтому вы начнете с самой глубокой ветви (оператор Deepest), которую вы должны разработать для своего метода. distribute ожидая этого порядка и применяя законы распределения для дочерних узлов в обратном направлении.

Очень общее описание того, что должен делать метод распространения:

Порядок того, какой тип закона распределения должен применяться, зависит от типа операции родителя и дочерних узлов. Каждый дочерний узел может быть оператором или значением, в зависимости от этой комбинации сделать распределение, требуемое законом.

Псевдокод того, что я пытаюсь вам сказать:

if parent node is OR type
    if child nodes are OPERATOR-VALUE combination
        if OPERATION is AND type
            apply correspondig distribution 
            return the new parent
        else
            apply correspondig distribution 
            return the new parent
    if child node are VALUE-VALUE combination
        return parent
if parent node is AND type
    if child nodes are OPERATOR-VALUE combination
        if OPERATION is AND type
            apply correspondig distribution 
            return the new parent
        else
            apply correspondig distribution 
            return the new parent
    if child nodes are VALUE-VALUE combination
        return parent;

Пример реализации:

public TreeNode distribute(TreeNode parent,TreeNode leftChild, TreeNode rightChild) {
    if( !(leftChild instanceof Operator) && !(rightChild instanceof Operator) ){
        /*There is nothing to do */
        return parent;
    }
    if( parent.getType() == 'OR'){
        /*
            Apply distributive laws and return the new branch
            for example:        
        */
        if ( (leftChild instanceof operator) &&  !(rightChild instanceof Operator) ){
            TreeNode operatorLeftChild  = leftChild.getLeftChild();
            TreeNode operatorRightChild = leftChild.getRightChild();
            if(leftChild.getType() == 'AND' )
            {
                /*
                Applying distributive laws:
                    rightChild OR (operatorLeftChild AND operatorRightChild) 
                        -> (rightChild OR operatorLeftChild) AND (rightChild OR operatorRightChild)
                */
                TreeNode newBranch = new Operator("AND");
                /*new Left child*/
                TreeNode newLeftChild= new Operator("OR");
                newLeftChild.setLeftChild(rightChild);
                newLeftChild.setRightChild(operatorLeftChild);
                /*new Richt Child */
                TreeNode newRightChild= new Operator("OR");
                newRightChild.setLeftChild(rightChild);
                newRightChild.setRightChild(operatorRightChild);
                /*Setting the new Branch*/
                newBranch.setLeftChild(newLeftChild);
                newBranch.setRightChild(newRightChild);
                return newBranch;
            }

        }
    }
    if( parent.getType() == 'AND'){
        /*
            Else-If and distributive laws stuff
        */
    }
    /*
        You can also implement this part wihtout the else-if code by implementing a true table
        but is more abstract and less human redeable
     */
}

Примечание. Предыдущий код не был протестирован, и я предполагаю, что многие вещи, которые я не знаю, как реализовано ваше дерево, возможно, вам потребуется обновить родительскую ссылку в дочерних узлах.

Другие вопросы по тегам