[英]Delete a Node from C++ Binary Search Tree (class not struct)
我正在嘗試用C ++管理BST用於學術目的。
除了DeleteNode
函數之外,我沒有遇到任何問題,我也選擇用class
來實現這個數據結構而不是struct
。
問題是,我無法弄清楚如何正確地工作刪除功能,我的調試器經常說我有0xDDDDDDDDD
錯誤,有時我可以刪除節點,有時我的程序崩潰。
我認為這可能是指針的問題,但我無法弄清楚我做錯了什么。
這是我的刪除節點功能,我正在遇到嚴重問題:
編輯:無子刪除案件工作得很好 ,我生氣的是單子案件刪除。
//function that delete a selected node
void DeleteNode(TreeNode* root,int key) {
/*we got three case here:*/
//until we find the right node with value in the tree
if (root->getValue() != key && root != nullptr) {
if (root->getValue() > key) {
DeleteNode(root->Left, key);
}
else if (root->getValue() < key) {
DeleteNode(root->Right, key);
}
}
else { //when we found the right node, then operate
/* THIS WORKS PERFECTLY! */
//first case: our node got no right and left son
if (!root->Left && !root->Right) {
TreeNode* tmp = root->Father;
if (tmp->Left == root) { //if the son is a left son
tmp->Left = nullptr;
delete root;
}
else if (tmp->Right == root) { //if the son is a right son
tmp->Right = nullptr;
delete root;
}
}
//second case: our node got a left but no right son
/* THIS ONE DOESN'T WORK. */
else if (!root->Right) {
TreeNode *tmp = root;
root = root->Left; //new root is the left son of the root
root->Father = tmp->Father; //linking the father to the new son
tmp->Father->Left = root; //linking the son to the new father
delete tmp;
std::cout << "Erased!" << std::endl;
}
else if (!root->Left) {
TreeNode *tmp = root;
root = root->Right; //new root is the right son of the root
root->Father = tmp->Father; //linking the father to the new son
tmp->Father->Right = root; //linking the son to the new father
delete tmp;
std::cout << "Erased!" << std::endl;
}
}
}
我嘗試了很多組合,但每次結果都是一樣的:它在第一次出現InOrder
顯示功能時崩潰了。 (當它沒有時,該函數只刪除第一個節點,然后當我嘗試刪除一個新節點時崩潰。)
這是一個簡單的主要我正在嘗試刪除:
int main()
{
TreeNode root;
root.insertNode(&root,50);
root.insertNode(&root,30);
root.insertNode(&root,20);
root.insertNode(&root,40);
root.insertNode(&root,70);
root.insertNode(&root,60);
root.insertNode(&root,80);
for (int i = 0; i < 5; i++) {
int n;
cin >> n;
root.DeleteNode(&root, n);
cout << "In-Order: "; root.inOrder(&root);
cout << endl;
cout << "Pre-Order: "; root.preOrder(&root);
cout << endl;
cout << "Post-Order: "; root.postOrder(&root);
cout << endl;
}
}
這是我的完整BST代碼(除了我之前提交的刪除代碼,只是為了更加完整地理解我的代碼)
class TreeNode {
private:
int value;
TreeNode* Left;
TreeNode* Right;
TreeNode* Father;
public:
//constructor
TreeNode() {
this->Right = nullptr;
this->Left = nullptr;
this->Father = nullptr;
}
TreeNode(int value) {
this->value = value;
this->Right = nullptr;
this->Left = nullptr;
this->Father = nullptr;
}
//functions
int getValue() { return value; }
void setValue(int value) { this->value = value; }
//function to create a new node and insert a value into it
TreeNode* insertNode(TreeNode* root, int value) {
if (root->getValue() == NULL) {
root->setValue(value);
root->Father = nullptr;
}
else {
if (value > root->getValue()) {
if (root->Right) {
insertNode(root->Right, value);
}
else
root->Right = new TreeNode(value);
root->Right->Father = root;
}
else if (value < root->getValue()) {
if (root->Left) {
insertNode(root->Left, value);
}
else
root->Left = new TreeNode(value);
root->Left->Father = root;
}
}
return root;
}
//function to search a value into a BST
TreeNode* SearchNode(TreeNode* root, int key) {
if (root->getValue() == key) {
return root;
}
else if (root->getValue() < key) {
if (root->Right) {
SearchNode(root->Right, key);
}
else return nullptr;
}
else if (root->getValue() > key) {
if (root->Left) {
SearchNode(root->Left, key);
}
else return nullptr;
}
}
//function that return the height of the tree
int TreeHeigth(TreeNode* root) {
int heigth;
if (root == nullptr) {
return 0;
}
else {
return heigth = 1 + max(TreeHeigth(root->Left), TreeHeigth(root->Right));
}
}
//function that returns the number of the nodes
int CountTreeNode(TreeNode* root) {
if (root == nullptr) {
return 0;
}
else {
return CountTreeNode(root->Left) + CountTreeNode(root->Right) + 1;
}
}
//function that returns the minimum values into the tree
TreeNode* MinimumNode(TreeNode* root) {
if (root == nullptr) {
return nullptr;
}
while (root->Left != nullptr) {
root = root->Left;
}
return root;
}
//function that returns the maximum value into the tree
TreeNode* MaximumNode(TreeNode* root) {
if (root == nullptr) {
return nullptr;
}
while (root->Right != nullptr) {
root = root->Right;
}
return root;
}
//function that returns a successor of a given nodeb
TreeNode* SuccessorNode(TreeNode* node) {
//first case: our node got a rigth subtree:
if (node->Right != nullptr) {
return MinimumNode(node->Right);
}
//second case: our node doesnt got a right subtree: lets get
//upper in the tree until our node isn't a left child.
TreeNode* Ancestor = node->Father;
while (Ancestor != nullptr && node == Ancestor->Right) {
node = Ancestor;
Ancestor = Ancestor->Father;
}
}
//function tht returns a predecessor of a given node
TreeNode* PredecessorNode(TreeNode* node) {
//first case: (inverse to successor) our node got a left subtree:
if (node->Left != nullptr) {
return MaximumNode(node->Left);
}
TreeNode* Ancestor;
if (node->Father == nullptr)
return nullptr;
else
Ancestor = node->Father;
while (Ancestor != nullptr && node == Ancestor->Left) {
node = Ancestor;
Ancestor = Ancestor->Father;
}
return Ancestor;
}
//function that prints information about nodes
void InfoNode(TreeNode *root) {
root != nullptr ? std::cout << "Nodo corrente: " << root->getValue() << std::endl
: std::cout << "Nodo corrente: " << "NULL" << std::endl;
root->Father != nullptr? std::cout << "Padre: " << root->Father->getValue() << std::endl
: std::cout << "Padre: " << "NULL" << std::endl;
root->Left != nullptr ? std::cout << "Figlio SX: " << root->Left->getValue() << std::endl
: std::cout << "Figlio SX: " << "NULL" << std::endl;
root->Right!= nullptr ? std::cout << "Figlio DX: " << (root->Right)->getValue() << std::endl
: std::cout << "Figlio DX: " << "NULL" << std::endl;
}
//visits of a tree
void preOrder(TreeNode* root) {
if (root != nullptr) {
std::cout << root->getValue() << " ";
preOrder(root->Left);
preOrder(root->Right);
}
}
void inOrder(TreeNode* root) {
if (root != nullptr) {
inOrder(root->Left);
std::cout << root->getValue() << " ";
inOrder(root->Right);
}
}
void postOrder(TreeNode *root) {
if (root != nullptr) {
postOrder(root->Left);
postOrder(root->Right);
std::cout << root->getValue() << " ";
}
}
//max between 2 numbers
int max(int a, int b) {
return a > b ? a : b;
}
};
還有我正在努力研究的樹的代表:
50
/ \
30 70
/ \ / \
20 40 60 80
我做錯了什么?
看看這個條件: root->getValue() != key && root != nullptr
,這首先調用getValue
,然后檢查root
具有合法值。 交換它們( root != nullptr && root->getValue() != key
)。
最后我想你必須把最后一行更改為tmp->Father->Left = root;
修復崩潰問題。
TreeNode *tmp = root;
root = root->Right; //new root is the right son of the root
root->Father = tmp->Father; //linking the father to the new son
tmp->Father->Right = root; //linking the son to the new father
PS:也為另一方做這個交換......
注意:這是正確的,直到root
留給他父親的孩子,否則你的代碼是真的。 如果父親做tmp->Father->Left = root;
父親tmp->Father->Left = root;
你必須檢查root
是否留給孩子tmp->Father->Left = root;
否則tmp->Father->Right = root;
注意:正如您所提到的,您的代碼不會處理具有兩個childern的節點的刪除。
由於已經有答案給出了糾正特定錯誤的指示,我將嘗試着重於一個可以幫助您避免類似錯誤的建議:
嘗試將當前功能分為兩部分:
可以使用特定鍵搜索節點的節點,例如: Node* search(int key)
函數,該函數返回指向具有所需鍵或nullptr
的節點的指針,或者使用已有的鍵。
一個刪除(並重新連接)作為指針傳遞的節點並返回:next,previous等: Node* delete(Node* n)
。
然后調用search
,對nulltpr
測試,如果不同,則將返回的指針作為delete
的輸入參數傳遞。
通過這種方式,您可以輕松檢測出您遇到的問題:搜索或刪除。
PS:通常通過圖表(方框和箭頭)來確定重新布線錯誤。 決定你應該做什么,將其分成幾步並實施。
好吧,一旦知道DEBUG版本使用sentinel值,在代碼中找到問題變得更加微不足道。
0xDD用於死記憶。 那是已經刪除的內存。 因此,當調試器停止並且它告訴您有一個錯誤的指針並且數據包含大量0xDD時,您知道數據已被刪除。 此時,您應該檢查包含數據的類,以查看它們是否也被刪除,以便您知道哪些對象在嵌入另一個對象時被刪除。
請注意,如果某些操作使用刪除內存,有時您可能會在類的一部分中更改某些數據。 查看內存模式還有助於查找未初始化的內存和其他類似問題。
其他一些鏈接:
在像你這樣的情況下,如果你遵循編寫單元測試的良好實踐,那么找到問題甚至會更加微不足道。 事實上,如果你進行了適當的測試,那么你將測試所有可能的情況,這樣你就會知道哪些情況會失敗,這會幫助你找到你可能做錯的地方。
我想補充一下@Bonje Fir的答案。 當然這是一個正確的答案,但部分是:我會解釋原因。
他建議交換我寫的最后一段代碼:
情況:我們在正確的子樹中,我們想要刪除70(因為我們不再有葉子節點60):
50
/ \
30 70
/ \ \
20 40 80
現在,使用@Bonje Fir建議我們的代碼,我們會遇到一個問題:
TreeNode *tmp = root;
root = root->Right; //new root is the right son of the root
root->Father = tmp->Father; //linking the father to the new son
tmp->Father->Left (instead of Right) = root; //linking the son to the new father
因為代碼說的是,一旦你用他的兒子更新了新根,就將他之前的根(我們保存在tmp變量中)的父親與他的左兒子聯系起來。 然后我們會協助這樣的事情:
50
/ x
80 80
/ \
20 40
那是不一致的。
現在看一下另一邊,使用相同的代碼並且沒有葉節點20:
50
/ \
30 70
\ / \
40 60 80
代碼適合這里,因為我們在正確的子樹中。 所以一旦用40更新30( root = root -> right
),情況就是這樣:
50
x \
40 70
/ \
60 80
然后@Bonje Fir寫的代碼片段完全適合:
tmp->Father->Left = root
因為當然,我們將40分配給原始根的父親的左兒子。 (因為我們在左子樹中。)
50
/ \
40 70
/ \
60 80
所以我做了一些改變來糾正這個邏輯問題,並讓它在右邊和左邊的子樹中都能正常工作。
else if (!root->Left) {
TreeNode *tmp = root;
root = tmp->Right;
root->Father = tmp->Father; //linking the father to the new son
//we need also to connect the son with the father, but first
//we need to know in which subtree we're in.
if (root->Father->Right == tmp) //if we're in the right subtree
tmp->Father->Right = root;
else ////if we're in the left subtree
tmp->Father->Left = root;
delete tmp;
std::cout << "Erased!" << std::endl;
}
我利用了我沒有擦除我的根的事實,一旦分配了新的根,所以根的父親仍然指向舊的根。
(對於相反的情況也是如此。)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.