我正在尝试在二叉树中找到一个值并返回具有我正在寻找的值的节点。
我做了一个算法,当该值不在树的深层时,该算法运行良好,但当值处于较深位置时,我得到java.lang.StackOverflowError
。这是我的代码:
class Node {
// keep these fields
Node left, right;
int value;
public Node find(int v){
if(v > this.value && this.right != null)
return right.find(v);
if(v < this.value && this.left != null)
return left.find(v);
if(this.value == v)
return this;
return null;
}
}
任何人都可以建议我解决这个问题(我听说过尾部优化递归之类的东西),但我不确定它是用Java工作的。
答案 0 :(得分:11)
最简单的方法是将其转换为while
循环,它只保持“我们正在测试的当前节点”的状态。
在循环的每次迭代中,有三种可能性:
类似于:
public Node find(int v) {
Node current = this;
while (current != null) {
if (current.value == v) {
return current;
}
// This will drop out of the loop naturally if there's no appropriate subnode
current = v < current.value ? current.left : current.right;
}
return null;
}
或者代码更少,但可能不太可读:
public Node find(int v) {
Node current = this;
// Keep navigating down the tree until either we've run
// out of nodes to look at, or we've found the right value.
while (current != null && current.value != v) {
current = v < current.value ? current.left : current.right;
}
return current;
}
答案 1 :(得分:5)
您的代码重新编译为迭代的示例:
class Node {
// keep these fields
Node left, right;
int value;
public Node find(int v){
Node n = this;
while (n != null)
{
if (v > n.value)
n = n.right;
else if (v < n.value)
n = n.left;
else // v == n.value
return n;
}
return null;
}
}
编辑:如果不清楚的话,请注意其工作原理。由于您永远不需要记住有关如何到达当前节点的任何信息,因此我们只跟踪我们需要搜索的当前子树的根。在每一步,我们要么确定没有剩下要搜索的子树(第一个条件),可能有左边或右边的子树(中间两个条件),或者我们实际上在根处找到了值当前子树(最后一个条件)。我们一直在寻找,直到我们用完子树(条件),如果我们用完了,我们知道树中的值不是,我们返回null。
编辑:正如评论中所指出的,使用连续的if
是一个问题。我已经更新了代码,如果/ else,则使用if / else。
答案 2 :(得分:0)
树搜索用于避免迭代大型数组。
树方法的弱点是订购节点值时。在加载树时,每个节点都向左或向右移动,导致大量递归。话虽如此,堆栈溢出需要很多递归。
您可以散列值,这将倾向于平衡树,或者您可以增强树构建算法,以便在特定分支过长时平衡树。
话虽如此,您还应该查看树中有多少节点足以导致堆栈溢出。您的代码中可能存在未在此处显示的错误。
答案 3 :(得分:-2)
您可以使用Xss
JVM参数来增加分配给线程堆栈的内存。这将允许您拥有更大的方法调用堆栈。
- Xsssize
设置线程堆栈大小(以字节为单位)。附加字母k或K表示KB,m或M表示MB,g或G表示GB。该 默认值取决于虚拟内存。
以下示例将线程堆栈大小设置为1024 KB 不同的单位:
-Xss1m
-Xss1024k
-Xss1048576
参考:https://docs.oracle.com/javase/8/docs/technotes/tools/windows/java.html
否则你总是可以将递归转换为循环,这意味着你必须自己在堆栈中管理方法的调用堆栈(参数和返回值),这可能会变得混乱。
注意:对于搜索操作,Jon Skeet所提到的不需要堆栈。搜索不需要跟踪它的位置。然而,对于回溯,需要对父母的引用,并且我们必须确保我们始终以左子女开始。