我使用以下方法遍历* 300 000级别的二叉树:
Node* find(int v){
if(value==v)
return this;
else if(right && value<v)
return right->find(v);
else if(left && value>v)
return left->find(v);
}
然而,由于堆栈溢出,我得到了分段错误。 关于如何在没有递归函数调用开销的情况下遍历深层树的任何想法?
* 通过&#34;遍历&#34;我的意思是&#34;搜索具有给定值&#34;的节点,而不是完整的树遍历。
答案 0 :(得分:27)
是!对于300 000级树避免递归。遍历您的树并使用循环找到值迭代。
二进制搜索树表示
25 // Level 1
20 36 // Level 2
10 22 30 40 // Level 3
.. .. .. .. .. .. ..
.. .. .. .. .. .. .. .. // Level n
进一步澄清问题。您的树的深度 n = 300.000级。因此,在最坏的情况下,二进制搜索树(BST)必须访问树节点的 ALL 。这是坏消息,因为最坏情况具有算法 O(n)时间复杂度。这样的树可以有:
2300.000个节点= 9.9701e + 90308个节点(大约)。
9.9701e + 90308个节点是要访问的指数级节点数。有了这些数字,调用堆栈溢出的原因就变得非常清楚了。
解决方案(迭代方式):
我假设您的节点class
/ struct
声明是经典的标准整数 BST 。然后你可以调整它,它会工作:
struct Node {
int data;
Node* right;
Node* left;
};
Node* find(int v) {
Node* temp = root; // temp Node* value copy to not mess up tree structure by changing the root
while (temp != nullptr) {
if (temp->data == v) {
return temp;
}
if (v > temp->data) {
temp = temp->right;
}
else {
temp = temp->left;
}
}
return nullptr;
}
采用迭代方法可以避免递归,从而避免了在程序调用堆栈中以递归方式查找树中的值的麻烦。
答案 1 :(得分:9)
一个简单的循环,你有一个Node *类型的变量,你设置为下一个节点,然后再循环...
不要忘记您搜索的值不存在的情况!
答案 2 :(得分:7)
您可以通过不使用调用堆栈来实现递归,而是使用用户定义的堆栈或类似的东西;这可以通过现有的stack模板完成。方法是有一个while
循环,迭代直到堆栈为空;由于现有的实现使用深度优先搜索,可以找到消除递归调用here。
答案 3 :(得分:6)
当您拥有的树是二进制搜索树时,您想要做的就是在其中搜索具有特定值的节点,那么事情很简单:不需要递归,你可以使用其他人指出的简单循环来做到这一点。
更常见的情况是,树不一定是二进制搜索树,并且想要执行完全遍历,最简单的方法是使用递归,但正如你已经了解的,如果树很深,那么递归将不起作用。
因此,为了避免递归,您必须在C ++堆上实现堆栈。您需要声明一个新的StackElement
类,它将包含原始递归函数所具有的每个局部变量的一个成员,以及原始递归函数接受的每个参数的一个成员。 (您可能能够使用更少的成员变量,在您使用代码后可以担心这一点。)
您可以将StackElement
的实例存储在堆栈集合中,或者您可以让它们中的每一个都包含指向其父项的指针,从而完全自己实现堆栈。
因此,它不是递归调用自身的函数,而是简单地包含一个循环。您的函数进入循环,当前 StackElement
正在初始化,其中包含有关树的根节点的信息。它的父指针将为null,这是表示堆栈将为空的另一种方式。
在您的函数的递归版本调用自身的每个地方,您的新函数将分配StackElement
的新实例,初始化它,并使用此新实例作为当前重复循环元素。
在你的函数的递归版本返回的每个地方,你的新函数将释放当前 StackElement
,弹出位于堆栈顶部的那个,使其成为新的当前元素,并重复循环。
当您找到所寻找的节点时,您只需从循环中断开。
或者,如果现有树的节点支持a)指向其&#34; parent&#34;的链接。节点和b)用户数据(您可以存储&#34;访问过的&#34;标志)然后您不需要实现自己的堆栈,您可以就地遍历树:在每次迭代中你的循环首先检查当前节点是否是你正在寻找的节点;如果没有,那么你通过孩子进行枚举,直到你找到一个尚未访问过的孩子,然后你去看看它;当你到达一个叶子,或者一个孩子都被访问过的节点时,你可以通过跟踪父母的链接来回溯。此外,如果您在遍历它时可以自由地销毁树,那么您甚至不需要&#34;用户数据&#34;的概念:一旦完成子节点,您就可以释放它并制作它无效。
答案 4 :(得分:3)
好吧,它可以以单个额外的局部变量和一些比较为代价进行尾递归:
Node* find(int v){
if(value==v)
return this;
else if(!right && value<v)
return NULL;
else if(!left && value>v)
return NULL;
else {
Node *tmp = NULL;
if(value<v)
tmp = right;
else if(value>v)
tmp = left;
return tmp->find(v);
}
}
答案 5 :(得分:0)
遍历二叉树是一个递归过程,您将继续行走,直到您发现您当前所在的节点无处可寻。
这是你需要一个合适的基础条件。看起来像:
if (treeNode == NULL)
return NULL;
通常,遍历树是以这种方式完成的(在C中):
void traverse(treeNode *pTree){
if (pTree==0)
return;
printf("%d\n",pTree->nodeData);
traverse(pTree->leftChild);
traverse(pTree->rightChild);
}