Как структурировать это дерево узлов?

Я пишу программу на C++, которая использует генетические методы для оптимизации дерева выражений.

Я пытаюсь написать класс Tree, который имеет член данных Node root. Конструктор узла генерирует случайное дерево узлов с +,-,*,/ в качестве узлов и целыми числами в качестве листьев.

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

vector<Node> dict;

Таким образом, класс Tree будет содержать вектор dict со всеми узлами дерева (или указателями на них), корневой узел дерева и переменную для хранения меры пригодности для дерева.

class Tree
    {
    public:
        typedef vector<Node>dict;
        dict v;
        Node *root;
        float fitness;

        Tree(void);
        ~Tree();
    };

class Node
    {
    public:
        char *cargo;
        Node *parent;
        Node *left;
        Node *right;
        bool entry;
        dict v;
        Node(bool entry, int a_depth, dict v, Node *pparent = 0);
                };

Tree::Tree()
    {
     Node root(true,  tree_depth, v);
    };

Кажется, нет подходящего места для размещения typedef vector<Node>dict;, потому что, если оно входит в определение дерева, оно не знает об узле и выдает сообщение об ошибке. Я не смог найти место для typedef этого.

Но я даже не уверен, является ли вектор лучшим контейнером. Узлы просто нужно индексировать последовательно. Контейнеру нужно будет расти, так как может быть от 200 до 500 узлов.


person Peter Stewart    schedule 11.07.2010    source источник
comment
Почему бы не переместить определение узла до этого для дерева? также вы можете (или не можете, трудно сказать) лучше обслуживаться вектором указателей Node.   -  person    schedule 11.07.2010
comment
@Neil: Я изначально так думал (и удалил), но у Node также есть член dict ... Который он передает по значению и сохраняет копию. Я не понимаю, почему это полезно (особенно для оптимизации), возможно, Питер может уточнить?   -  person Stephen    schedule 11.07.2010
comment
Каждый узел должен добавить себя в dict в качестве своего экземпляра. dict.push_back(this) Я думал, что если бы dict был членом Tree, мне пришлось бы передать его конструктору Node, который передал бы его каждому последующему конструктору Node.   -  person Peter Stewart    schedule 11.07.2010
comment
@Peter Сделать дерево ответственным за добавление узлов в словарь - сами узлы не должны ничего знать о том, в чем они содержатся - это почти всегда верно для содержащихся и контейнерных классов.   -  person    schedule 11.07.2010
comment
@Neil Я бы хотел, чтобы Tree отвечало за добавление узлов в словарь. но Tree знает только о корневом узле. Такова природа деревьев, каждый узел знает своих детей, левых и правых, и родителя, если это необходимо для скрещивания. Я думал, что передача ссылки на dict будет очевидным решением». Но я на самом деле не был в восторге от этого, поэтому этот пост.   -  person Peter Stewart    schedule 11.07.2010
comment
@Peter Узлы и деревья, как правило, не должны много делать - вы можете рассмотреть третий класс, скажем, заводчик, который знает как о деревьях, так и о узлах и выполняет более тяжелую работу. Когда вы сталкиваетесь с проблемой в C++, обычное решение состоит в том, чтобы ввести один или два дополнительных класса.   -  person    schedule 11.07.2010


Ответы (3)


Я думаю, стандартное двоичное дерево должно подойти... вот пример (двоичного ) узел дерева выражений:

const int NUMBER = 0,    // Values representing two kinds of nodes.
          OPERATOR = 1;

struct ExpNode {  // A node in an expression tree.

    int kind;        // Which type of node is this?
                     //   (Value is NUMBER or OPERATOR.)
    double number;   // The value in a node of type NUMBER.
    char op;         // The operator in a node of type OPERATOR.
    ExpNode *left;   // Pointers to subtrees,
    ExpNode *right;  //     in a node of type OPERATOR.

    ExpNode( double val ) {
          // Constructor for making a node of type NUMBER.
       kind = NUMBER;
       number = val;
    }

    ExpNode( char op, ExpNode *left, ExpNode *right ) {
          // Constructor for making a node of type OPERATOR.
       kind = OPERATOR;
       this->op = op;
       this->left = left;
       this->right = right;
    }

}; // end ExpNode

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

  1. Подсчитайте количество узлов в дереве (это нужно делать только в конструкторе).
  2. Выберите случайный индекс от 0 до размера дерева.
  3. Посетите каждый узел и вычтите 1 из случайного индекса, пока не достигнете нуля.
  4. Вернуть узел, когда индекс равен 0.

В этом случае вам не нужно ничего знать о родительском узле. Таким образом, скрещивание/мутация должно выглядеть так:

select nodeX
select nodeY
    if( Rand(0,1) == 1 )
        nodeY->left = nodeX;
    else
        nodeY->right = nodeX;

И это должно быть так...

person Kiril    schedule 11.07.2010

Я не думаю, что Node или Tree — это первые классы, которые нужно написать.

Я бы начал с Expression. В вашем случае вам нужно как минимум BinaryExpression, а также выражение без подузлов (констант или переменных). Каждое двоичное выражение должно содержать auto_ptr<Expression> lhs и auto_ptr<Expression> rhs.

Затем вы можете легко написать функцию для перечисления членов дерева выражений. Если производительность окажется актуальной, вы можете кэшировать список выражений в дереве и сделать его недействительным вручную при изменении выражения. Все более продвинутое, вероятно, будет медленнее и более подвержено ошибкам.

Я не понимаю, почему выражение должно знать свое родительское выражение. Это только усложняет жизнь, когда вы начинаете редактировать выражения.

person Community    schedule 11.07.2010
comment
узел должен знать своего родителя для скрещивания, когда поддеревья из двух случайно выбранных узлов меняются местами. - person Peter Stewart; 11.07.2010
comment
@Peter, что ты делаешь с родителем узла, когда занимаешься скрещиванием? - person Kiril; 11.07.2010
comment
@Lirik Я получил отличный ответ от Алекса Мартелли в этом посте, когда я впервые разобрался с деревьями в питоне: заголовок stackoverflow.com/questions/1386493/ Рассматриваемый узел будет корнем одного из двух поддеревьев. Таким образом, родитель будет изменен, чтобы указать на другое поддерево, а родитель другого поддерева будет изменен, чтобы указать на рассматриваемый узел. йигида. - person Peter Stewart; 12.07.2010
comment
@Peter, это кажется немного сложным ... более простым решением было бы выбрать один узел X, затем выбрать другой узел Y, а затем заменить левый или правый дочерний элемент Y на X. Нет необходимости знать о родительском элементе или поддерживать ссылки вернуться к родителю (это ненужное усложнение и дополнительный код, который необходимо выполнить). - person Kiril; 12.07.2010
comment
Какая хорошая идея. Выбор родителей узлов поддерева, спасибо. - person Peter Stewart; 12.07.2010

Вы можете реализовать список по узлам. Тогда каждый узел будет иметь два дополнительных указателя внутри:

class Node{
...
Node* sequentialPrevious;
Node* sequentialNext;
...
}

И дерево тоже:

class Tree{
...
Node* sequentialFirst;
Node* sequentialLast;
...
}

Затем вы сможете перемещаться по узлам в двух направлениях, просто переходя к sequentialFirst или sequentialLast, а затем итеративно к sequentialNext или sequentialPrevious. Конечно, конструктор и деструктор Node должны быть правильно реализованы, чтобы поддерживать актуальность этих указателей.

person mbq    schedule 11.07.2010