2017-11-18 125 views
2

我試圖在C++中管理BST以達到我的學術目的。從C++二進制搜索樹中刪除一個節點(類不是結構體)

我並沒有問題,除了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 

我在哪裏做錯了?

+2

注意:類和結構之間沒有根本的區別。一個結構默認情況下所有成員都是'public',而一個類有'private'。 – Darhuuk

+0

我也嘗試設置所有的指針,父親,左,右兒子公共,但這根本沒有幫助。 – Asynchronousx

+0

另一個邊節點:這看起來像是從二叉樹中刪除節點的代碼,而不是二叉搜索樹(它需要在刪除內部節點後重新排序節點)。 – Darhuuk

回答

3

看這個條件: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留給他的父親的孩子,否則你的代碼是正確的。正是你必須檢查root是左邊的孩子,如果他的父親做tmp->Father->Left = root;其他tmp->Father->Right = root;

注:正如你提到你的代碼不處理節點的缺失有兩個子女。

+0

感謝您的注意,只是調整了。但仍然崩潰! – Asynchronousx

+2

@Asynchronousx將'this-> value = NULL'添加到您的TreeNode構造函數中。首先root擁有不是'NULL'的垃圾值。 –

+0

完成,謝謝你的附加說明。 – Asynchronousx

2

前面已經有一個答案給你方向糾正特定的錯誤,我會盡量把重點放在一個建議,這將有助於你避免類似的錯誤都在一起:

試試你的當前功能分離成兩塊:

  1. 一,搜索特定關鍵字的節點,例如:Node* search(int key)函數返回要麼指向與通緝鍵或nullptr節點,或使用你已經有了一個。

  2. 一個刪除(並重新連接)作爲指針傳遞的節點並返回:next,previous等:Node* delete(Node* n)

然後調用search,測試針對nulltpr,並且如果不同,通過返回的指針如在delete輸入參數。

通過這種方式,您可以輕鬆檢測到您在哪個階段出現問題:搜索或刪除。


P.S .:計算出重新佈線的錯誤通常是通過圖表(方框和箭頭)完成的。決定你應該做什麼,將其分成若干步驟並實施。

+1

我用visual studio調試我的程序,一步一步地檢查指針值和每一步的值本身。 看來,搜索代碼行效果很好,因爲它找到了我想要的節點,我得到麻煩的是刪除節點,當我的節點只有一個兒子。 – Asynchronousx

+0

@Asynchronousx確切地說,如果問題只與'delete'部分有關,那麼你和其他人都可能會閱讀你的代碼,這會更容易閱讀和最終檢測。 – Ziezi

+0

但問題僅與刪除有關,因爲其他一切都像魅力一樣。導致我頭痛的唯一部分是刪除單節點子情況。 (左側或右側) – Asynchronousx

2

那麼,一旦有人知道DEBUG版本使用定點值,在代碼中發現問題變得更加微不足道。

0xDD是死的記憶。這是已被刪除的內存。因此,當調試器停止並且它告訴您指針不正確且數據包含大量0xDD時,您知道數據已被刪除。此時,您應該檢查包含數據的類以查看它們是否被刪除,以便知道哪些對象被嵌入到另一箇中時會被刪除。

請注意,如果某些操作使用刪除內存,某些時候可能會有一些數據在類的一部分中進行了更改。查看內存模式還有助於查找未初始化的內存和其他類似問題。

其他一些環節:

在你們這樣的情況下,如果你遵循的編寫單元測試的很好的做法,那麼發現問題甚至會更加微不足道。事實上,如果您進行了適當的測試,那麼您將測試所有可能的案例,以便您知道哪些案例會失敗,並幫助您找到可能出錯的地方。

+0

非常感謝您提供有用的信息。 – Asynchronousx

1

我想補充一些@Bonje Fir的答案。 當然,這是一個正確的答案,但部分:我會解釋爲什麼。

他建議互換的最後一塊代碼,我寫道:

案例:我們在右子樹,我們想抹去70(因爲我們沒有了葉子節點60) :由於代碼是說,一旦你與他的兒子更新了新的根

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 

  50 
    / \ 
     30  70 
    /\  \ 
    20 40  80 

現在,與@Bonje冷杉建議我們的代碼,我們將有一個問題在這裏,鏈接th父親與他的左兒子之前的根(我們保存在一個tmp變量中)的父親。然後我們會協助這樣的事情:

  50 
    / x 
     80  80 
    /\  
    20 40  

而且這是不一致的。

現在採取的另一邊一看,用相同的代碼,無葉節點20:

 50 
/ \ 
    30  70 
    \ /\ 
    40 60 80 

代碼適合在這裏,因爲我們是在右子樹。 所以一旦更新30 40(root = root -> right)的情況會是這樣:

 50 
    x  \ 
    40  70 
     /\ 
     60 80 

那麼一塊代碼@Bonje杉木給我們來信,會伏貼:

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

我注意到這一事實,我沒有抹去我的根的優勢,一旦分配了新的,所以根的父親仍然指向舊的根。

(對於相反的情況相同的語音)。