簡體   English   中英

二進制樹中的節點搜索溢出堆棧

[英]Node search in Binary Tree overflows stack

我使用以下方法遍歷* 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);
}

但是由於堆棧溢出,我得到了分段錯誤。 關於如何在沒有遞歸函數調用開銷的情況下遍歷深層樹的任何想法?

*“遍歷”我的意思是“搜索具有給定值的節點”,而不是完整的樹遍歷。

是! 對於300 000級樹, 避免遞歸 遍歷您的樹並使用循環迭代地查找值。

二進制搜索樹表示

           25             // Level 1
        20    36          // Level 2
      10 22  30 40        // Level 3
  .. .. .. .. .. .. .. 
.. .. .. .. .. .. .. ..   // Level n

只是為了進一步澄清問題。 您的樹的深度為n = 300.000級 因此,在最壞的情況下, 二進制搜索樹 (BST)將不得不訪問所有樹的節點。 這是壞消息,因為最壞的情況具有算法O(n)時間復雜度 這樣的樹可以有:

2300.000個節點= 9.9701e + 90308個節點(大約)。


9.9701e + 90308節點是要訪問的指數大量節點。 有了這些數字,調用堆棧溢出的原因就變得非常清楚了。


解決方案(迭代方式):

我假設你的Node 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;
}

采用這種迭代方法可以避免遞歸 ,從而避免了在程序調用堆棧中以遞歸方式查找樹中的值的麻煩。

一個簡單的循環,你有一個Node *類型的變量,你設置為下一個節點,然后再循環...
不要忘記您搜索的值不存在的情況!

您可以通過不使用調用堆棧來實現遞歸,而是使用用戶定義的堆棧或類似的東西; 這可以通過現有的堆棧模板完成。 方法是有一個while循環,迭代直到堆棧為空; 由於現有的實現使用深度優先搜索,因此可以在此處找到消除遞歸調用。

當您擁有的樹是二進制搜索樹時 ,您想要做的就是在其中搜索具有特定值的節點,那么事情很簡單:不需要遞歸,您可以使用簡單的循環來完成其他人指出。

在更一般的情況下,有一個樹不一定是二進制搜索樹,並且想要執行它的完全遍歷 ,最簡單的方法是使用遞歸,但正如你已經理解的,如果樹很深,那么遞歸不管用。

因此,為了避免遞歸,您必須在C ++堆上實現堆棧。 您需要聲明一個新的StackElement類,它將包含原始遞歸函數所具有的每個局部變量的一個成員,以及原始遞歸函數接受的每個參數的一個成員。 (您可能能夠使用更少的成員變量,您可以在使用代碼后擔心這一點。)

您可以將StackElement實例存儲在堆棧集合中,或者您可以簡單地讓每個實例包含指向其父級的指針,從而自己完全實現堆棧。

因此,它不是遞歸調用自身的函數,而是簡單地包含一個循環。 您的函數進入循環,並使用有關樹的根節點的信息初始化當前的 StackElement 它的父指針將為null,這是另一種表示堆棧為空的方式。

在函數的遞歸版本調用自身的每個地方,新函數將分配一個新的StackElement實例,初始化它,並使用這個新實例作為當前元素重復循環。

在函數的遞歸版本返回的每個地方,你的新函數將釋放當前的 StackElement ,彈出位於堆棧頂部的那個,使其成為新的當前元素,並重復循環。

當您找到所需的節點時,您只需從循環中斷開。

或者,如果現有樹的節點支持a)到其“父”節點的鏈接和b)用戶數據(您可以存儲“已訪問”標志),那么您不需要實現自己的堆棧,您可以只是在原地遍歷樹:在循環的每次迭代中,首先檢查當前節點是否是您正在尋找的節點; 如果沒有,那么你通過孩子進行枚舉,直到你找到一個尚未訪問過的孩子,然后你去看看它; 當你到達一個葉子,或者一個孩子都被訪問過的節點時,你可以通過跟蹤父母的鏈接來回溯。 此外,如果您在遍歷它時可以自由地銷毀樹,那么您甚至不需要“用戶數據”的概念:一旦完成了子節點,就可以釋放它並使其為空。

好吧,它可以以單個額外的局部變量和一些比較為代價進行尾遞歸:

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

遍歷二叉樹是一個遞歸過程,在此過程中,您將繼續行走,直到您發現當前所在的節點無處可尋。

這是你需要一個合適的基礎條件。 看起來像:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM