![](/img/trans.png)
[英]Binary Search Tree recursive insertion causing stack overflows, iterative insertion not working
[英]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 *類型的變量,你設置為下一個節點,然后再循環...
不要忘記您搜索的值不存在的情況!
當您擁有的樹是二進制搜索樹時 ,您想要做的就是在其中搜索具有特定值的節點,那么事情很簡單:不需要遞歸,您可以使用簡單的循環來完成其他人指出。
在更一般的情況下,有一個樹不一定是二進制搜索樹,並且想要執行它的完全遍歷 ,最簡單的方法是使用遞歸,但正如你已經理解的,如果樹很深,那么遞歸不管用。
因此,為了避免遞歸,您必須在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.