【发布时间】:2013-11-28 04:24:58
【问题描述】:
感谢您抽出宝贵时间阅读本文(对于文字墙感到抱歉)。
背景
是的,这是一个学校项目。不,我不是要你为我做这件事。我自己完成了所有工作,我只是被 Java 本身难住了,问题不大。
该项目是关于基因编程的基础知识。我随机生成树(代表基本数学表达式),根据来自目标函数的数据(x,y 值)评估它们的适应度,然后通过消除一些非常遥远的树来操纵集合,稍微改变一些,然后执行对两个最合适的表达式进行“交叉”操作。交叉是问题。我创建了一个有效的算法,但它永久地改变了两棵原始树,我想保留它们,以防交叉生成两棵远离的树。
为了解决这个问题,我研究了复制树形结构,结果证明这很难实现。我研究了几种不同的方法,首先是序列化。在尝试并阅读了一些examples 之后,我自己尝试了一下,但失败了。我意识到我对 Java 的掌握不够好,无法使用该技术。所以我决定在 Node 类上使用递归复制构造函数,并在 GPTree 类上使用一个简单的复制构造函数(见下文)。
我无法解释出了什么问题,如果你看不到它尤其困难,但请耐心等待。在 Eclipse 中调试了一段时间后,问题似乎始于 GPTree.crossover 方法。具体如下:
Node t1Node = getRandomNode(t1.root);
Node t1NodeParent = t1Node.parent;
Node t2Node = getRandomNode(t2.root);
Node t2NodeParent = t2Node.parent;
例如,当我查看前两个节点(t1Node 和 t1NodeParent)的调试器信息时,t1Node 的 id 将是(所有数字由示例组成)18 及其父节点的 id将是 22。然后,在分配给 t1NodeParent 之后,t1NodeParent 的 id 将是 22(就像它应该是的那样),但它的左孩子或右孩子的 id 都不会是 18(就像孩子的) !因此,这些赋值下面的if 子句会变得混乱,然后树也会变得混乱。有时(不是每次,奇怪的是)原始树也会被更改。
我认为这与我尝试复制树木的方式有关,因为这是唯一改变的事情。但是,就我的一生而言,我无法告诉你它为什么会这样。
所以,感谢您阅读这个冗长的问题。 . .我希望你能帮忙。 :/
这是我的严重精简代码,证明了我的问题:
GPEnv.java (gist)
import java.util.ArrayList;
public class GPEnv {
private final int MAX_HEIGHT = 4;
private ArrayList<GPTree> trees;
public GPEnv(int numTrees) {
trees = new ArrayList<GPTree>();
for (int i = 0; i < numTrees; i++) {
trees.add(new GPTree(MAX_HEIGHT));
}
}
public void crossover() {
System.out.println(trees.get(0));
System.out.println(trees.get(1));
System.out.println();
/*
This commented version works, but it permanently alters the original trees.
GPTree[] res = GPTree.crossover(
trees.get(0),
trees.get(1)
);
*/
GPTree[] res = GPTree.crossover(
trees.get(0).copy(),
trees.get(1).copy()
);
System.out.println(res[0]);
System.out.println(res[1]);
System.out.println();
System.out.println(trees.get(0));
System.out.println(trees.get(1));
}
public static void main(String[] args) {
GPEnv env = new GPEnv(2);
env.crossover();
}
}
Expression.java (gist)
public abstract class Expression {
protected static enum Token {
PLUS("+"),
MINUS("-"),
TIMES("*"),
DIVIDE("/"),
NUMBER(""),
VARIABLE("x");
private final String symbol;
Token(String symbol) {
this.symbol = symbol;
}
public String toString() {
return this.symbol;
}
}
protected static class Node {
protected Token token;
protected Node parent, left, right;
protected double value;
public Node() {
this.parent = null;
this.left = this.right = null;
}
public Node(int number) {
this();
this.token = Token.NUMBER;
this.value = number;
}
public Node(Token token) {
this();
this.token = token;
this.value = Double.NaN;
}
private Node(Token token, double number, Node parent, Node left, Node right) {
switch (token) {
case PLUS:
this.token = Token.PLUS;
this.value = Double.NaN;
break;
case MINUS:
this.token = Token.MINUS;
this.value = Double.NaN;
break;
case TIMES:
this.token = Token.TIMES;
this.value = Double.NaN;
break;
case DIVIDE:
this.token = Token.DIVIDE;
this.value = Double.NaN;
break;
case NUMBER:
this.token = Token.NUMBER;
this.value = Double.parseDouble(number + "");
break;
case VARIABLE:
this.token = Token.VARIABLE;
this.value = Double.NaN;
break;
}
this.parent = parent;
this.left = left;
this.right = right;
}
public void setParent(Node rent) {
this.parent = rent;
}
public boolean isOperator() {
switch (this.token) {
case PLUS:
case MINUS:
case TIMES: // intentional fall-throughs
case DIVIDE:
return true;
default:
return false;
}
}
public String toString() {
if (this.token == Token.NUMBER) {
return this.value + "";
}
return this.token.toString();
}
public Node copy() {
Node left = null;
Node right = null;
if (this.left != null) {
left = this.left.copy();
}
if (this.right != null) {
right = this.right.copy();
}
return new Node(token, value, parent, left, right);
}
}
protected Node root;
private void postOrderTraverse(Node node, StringBuilder sb) {
if (node != null) {
postOrderTraverse(node.left, sb);
postOrderTraverse(node.right, sb);
sb.append(node + " ");
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
postOrderTraverse(this.root, sb);
return sb.toString();
}
}
GPTree.java (gist)
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Random;
public class GPTree extends Expression {
private static final int MIN_HEIGHT = 2;
private static final int MAX_MUTATED = 4;
private static final Random rand = new Random();
private int maxHeight;
public GPTree(int maxHeight) {
super();
this.maxHeight = maxHeight;
generateRandomTree();
}
private GPTree(Node root, int maxHeight) {
this.root = root;
this.maxHeight = maxHeight;
}
private static Node getRandomNode(Node node) {
if (node.left == null && node.right == null) {
return node;
} else if (rand.nextInt(10) > 6) {
return node;
}
if (rand.nextInt(2) == 0) {
return getRandomNode(node.left);
} else {
return getRandomNode(node.right);
}
}
private void generateRandomTree(Node node, int depth) {
if (depth == maxHeight) {
node.left = new Node(rand.nextInt(10));
node.left.setParent(node);
node.right = new Node(rand.nextInt(10));
node.right.setParent(node);
return;
}
if (rand.nextInt(2) == 0) {
node.left = new Node(Token.values()[rand.nextInt(4)]);
generateRandomTree(node.left, depth + 1);
} else {
// give numbers an increased chance of occuring (60%)
if (rand.nextInt(10) > 3) {
node.left = new Node(rand.nextInt(10));
} else {
node.left = new Node(Token.VARIABLE);
}
}
if (rand.nextInt(2) == 0) {
node.right = new Node(Token.values()[rand.nextInt(4)]);
generateRandomTree(node.right, depth + 1);
} else {
// give numbers an increased chance of occuring (60%)
if (rand.nextInt(10) > 3) {
node.right = new Node(rand.nextInt(10));
} else {
node.right = new Node(Token.VARIABLE);
}
}
if (depth < MIN_HEIGHT && (!node.left.isOperator() && !node.right.isOperator())) {
if (rand.nextInt(2) == 0) {
node.left = new Node(Token.values()[rand.nextInt(4)]);
generateRandomTree(node.left, depth + 1);
} else {
node.right = new Node(Token.values()[rand.nextInt(4)]);
generateRandomTree(node.right, depth + 1);
}
}
node.left.setParent(node);
node.right.setParent(node);
}
public void generateRandomTree() {
if (this.root == null) {
this.root = new Node(Token.values()[rand.nextInt(4)]);
}
generateRandomTree(this.root, 1);
}
public static GPTree[] crossover(GPTree t1, GPTree t2) {
GPTree[] result = new GPTree[2];
Node t1Node = getRandomNode(t1.root);
Node t1NodeParent = t1Node.parent;
Node t2Node = getRandomNode(t2.root);
Node t2NodeParent = t2Node.parent;
t2Node.parent = t1NodeParent;
if (t1NodeParent == null) {
t1.root = t2Node;
} else {
if (t1NodeParent.left == t1Node) {
t1NodeParent.left = t2Node;
} else {
t1NodeParent.right = t2Node;
}
}
t1Node.parent = t2NodeParent;
if (t2NodeParent == null) {
t2.root = t1Node;
} else {
if (t2NodeParent.left == t2Node) {
t2NodeParent.left = t1Node;
} else {
t2NodeParent.right = t1Node;
}
}
result[0] = t1;
result[1] = t2;
return result;
}
public GPTree copy() {
return new GPTree(root.copy(), maxHeight);
}
}
【问题讨论】:
-
您是否需要从头开始实现数据结构?我很想用DOM 来表示树。
-
技术上没有,但我已经在这方面做了很多工作,而且在到期之前我没有那个。我害怕不得不尝试学习一种新方法,然后实施它。 . .
标签: java tree copy binary-tree deep-copy