【问题标题】:How to structure this tree of nodes?如何构建这棵节点树?
【发布时间】:2010-07-11 14:40:18
【问题描述】:

我正在用 C++ 编写一个程序,该程序使用遗传技术来优化表达式树。

我正在尝试编写一个类Tree,它具有作为数据成员Node root。节点构造器生成一个随机的节点树,其中+,-,*,/ 为节点,整数为叶子。

我一直在研究这个,但我还不清楚最好的结构。因为我需要访问树中的任何节点才能对树进行变异或杂交,所以我需要保留节点的字典。一个数组就可以了,但似乎 vector 是推荐的容器。

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&lt;Node&gt;dict;,因为如果放到Tree的定义里,它不知道Node,这样说会报错。我还没找到typedef 的地方。

但我什至不确定向量是否是最好的容器。节点只需要按顺序编入索引。容器需要增长,因为可能有 200 到 500 个节点。

【问题讨论】:

  • 为什么不把 Node 的定义移到 Tree 的前面呢?节点指针向量也可能(或可能不会,很难说)为您提供更好的服务。
  • @Neil :这就是我最初的想法(并已删除),但 Node 也有一个 dict 成员......它按值传递,并存储一个副本。我不清楚为什么这很有用(特别是对于优化),也许彼得可以详细说明?
  • 每个节点都必须将自己添加到字典中作为其实例化。 dict.push_back(this) 我想如果 dict 是 Tree 的成员,我必须将它传递给 Node 构造函数,它会将它传递给每个后续的 Node 构造函数。
  • @Peter Make Tree 负责将节点添加到 dict - 节点本身不应该知道它们所包含的内容 - 对于包含类和容器类来说几乎总是如此。
  • @Peter 节点和树通常不应该做太多 - 你可能想考虑第三类,比如 Breeder,它知道树和节点并且可以完成更重的工作。在 C++ 中遇到问题时,通常的解决方案是引入一两个额外的类。

标签: c++ typedef


【解决方案1】:

我认为标准的二叉树应该可以......这是example of a (binary) expression tree node

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;

应该就是这样……

【讨论】:

    【解决方案2】:

    我不认为NodeTree 是最先编写的类。

    我会从Expression 开始。在您的情况下,您至少需要一个BinaryExpression,以及一个没有子节点(常量或变量)的表达式。每个二进制表达式应包含auto_ptr&lt;Expression&gt; lhsauto_ptr&lt;Expression&gt; rhs

    然后您可以轻松编写一个函数来枚举表达式树的成员。如果性能证明是相关的,您可以在树中缓存表达式列表,并在您更改表达式时手动使其无效。任何更高级的东西都可能更慢且更容易出错。

    我不明白为什么表达式需要知道它的父表达式。当您开始编辑表达式时,它只会让生活变得更加艰难。

    【讨论】:

    • 当来自两个随机选择的节点的子树被交换时,一个节点需要知道它的父节点才能进行杂交。
    • @Peter,当你做杂交时,你对节点的父节点做了什么?
    • @Lirik 当我第一次在 python 中找出树时,我在这篇文章中得到了 alex martelli 的一个很好的回复:stackoverflow.com/questions/1386493/… 有问题的节点将是两个子树之一的根。因此,父节点将更改为指向另一个子树,而另一个子树的父节点将更改为指向相关节点。伊吉达。
    • @Peter,好像有点复杂...更简单的解决方案是选择一个节点X,然后选择另一个节点Y,然后用X替换Y的左或右孩子。没有必要了解父级或维护返回父级的链接(这是不必要的复杂性,需要执行更多代码)。
    • 真是个好主意。选择子树节点的父节点,谢谢。
    【解决方案3】:

    您可以在节点上实现列表。然后,每个节点内部都会有两个额外的指针:

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

    树也会这样:

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

    然后,您只需跳转到sequentialFirstsequentialLast,然后迭代到sequentialNextsequentialPrevious,就可以在节点上双向移动。当然,Node 的构造函数和析构函数必须正确实现才能使这些指针保持最新。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-02-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多