二进制树中的节点搜索溢出堆栈

时间:2017-03-28 09:06:34

标签: c++ algorithm data-structures binary-tree binary-search-tree

我使用以下方法遍历* 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;的节点,而不是完整的树遍历。

6 个答案:

答案 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);
}