2

I'm trying to manage a BST in C++ for my academic purpose.

I'm not having problems anywhere except for the DeleteNode function, also I chose to implement this data structure with a class and not with a struct.

The problem is, that I cant figure it out how to make properly work the delete function, often I got 0xDDDDDDDDD error my debugger say, sometimes I can delete the node, sometimes my program crash.

I think that's a possible problem of pointer, but I just can't figure it out where I'm doing wrong.

Here's my delete node function, the one I'm getting serious trouble about:

EDIT: The no-son delete case works perfectly, the one i'm getting mad about is the one-son-case delete.

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

            }
        }


        }

I tried a lot of combination, but the result are the same every time: it crashes on the first occurrence of the InOrder display function. (and when it does not, the function just delete the first nodes and then crash when i try to delete a new one.)

Here's a simple main where i'm trying to act the delete:

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

}

Here's my full BST code (except the delete one that i submitted before, just for being more complete in the understanding of my code)

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


    };

And there's the representation of the tree I'm trying to work on:

          50
       /     \
      30      70
     /  \    /  \
   20   40  60   80 

Where i'm doing it wrong?

Ziezi
  • 6,375
  • 3
  • 39
  • 49
Asynchronousx
  • 101
  • 1
  • 1
  • 8
  • 2
    Side note: there is no fundamental difference between a class and a struct. A struct simply has all members `public` by default, whereas a class has them `private`. – AVH Nov 18 '17 at 12:25
  • i also tried to set all the pointer to the father, left and right son public, but that ain't helped at all. – Asynchronousx Nov 18 '17 at 12:29
  • Another side node: this seems like code to delete a node from a binary tree, not a binary search tree (which needs nodes to be reordered after deletion of an internal node). – AVH Nov 18 '17 at 12:36
  • can you explain better? – Asynchronousx Nov 18 '17 at 12:48
  • The answer would be a little too long to fit here, but for example take a look at Wikipedia on the [Binary tree](https://en.wikipedia.org/wiki/Binary_tree#Deletion) and [Binary search tree](https://en.wikipedia.org/wiki/Binary_search_tree#Deletion) pages. Basically when deleting a parent node one of the child nodes might need to take its place to keep it a proper binary search tree. – AVH Nov 18 '17 at 12:51
  • Yes, and that's what i'm doing! When deleting a node (in the case of one son, left or right, i'm replacing the current root with the root = root->left in the case of no right son, or root = root->right in the case of no left son. That's why i cant understand where i'm doing it wrong! – Asynchronousx Nov 18 '17 at 12:53
  • That doesn't seem like it would always keep the tree properly balanced. The replacement node when the root has a right child should be the minimum child of the right child (this means you might have to recurse all the way down into the right child). E.g. if you delete "50" in your example, it should be replaced with "60", not with "70". There's a bunch of example code on the Wikipedia page I linked. – AVH Nov 18 '17 at 12:59

4 Answers4

3

Look at this condition: root->getValue() != key && root != nullptr, This first calls getValue and after that checks root has legal value. swap them(root != nullptr && root->getValue() != key).

Finally I think you must change last line to tmp->Father->Left = root; to fix crash problem.

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: Also do this exchange for other side...

Note: This is true until root is left child of his father otherwise your code is true. Precisely you must check if root is left child if his father do tmp->Father->Left = root; else tmp->Father->Right = root;

Note: As you mentioned your code does not handle deletion of a node with two childern.

Bonje Fir
  • 787
  • 8
  • 25
  • thanks for the note, just adjusted that. But still crashing! – Asynchronousx Nov 18 '17 at 12:33
  • 2
    @Asynchronousx add `this->value = NULL` to your TreeNode constructor. At first root has garbage value not `NULL`. – Bonje Fir Nov 18 '17 at 12:44
  • @Asynchronousx If I delete `50` at first your code does not erase it! – Bonje Fir Nov 18 '17 at 13:04
  • yea, because 50 is the root, and i didn't make any code for erasing that! – Asynchronousx Nov 18 '17 at 13:13
  • YOU. ARE. THE. MAN. I'm trying to figure it out from days this problems, it gave me so much headache!!! Thank you very much for helping me with this Bonje, you're amazing! Can you also explain why when i'm on the right/left side i must update the opposite side with the root? THANK YOU!! – Asynchronousx Nov 18 '17 at 13:37
  • @Asynchronousx I am so happy to hear that you are happy but I think your code has more issues that previous crash... Check if you remove `30` at first step. But good news is that **I think you must just rewrite delete node when it has children**. – Bonje Fir Nov 18 '17 at 13:52
  • For me it works like a charm! i cant delete 30 at first step (without deleting 20 first) because i must still write the case when a node got 2 children. I was waiting until i figurd out the problem that caused me so much issues with crashing and all. But it works perfectly! – Asynchronousx Nov 18 '17 at 13:59
  • @Asynchronousx Humm, you dont implement 2 children, Ok. Delete `60` and after that `70`. :))) – Bonje Fir Nov 18 '17 at 14:12
  • I would like to add something to your answer, that is surely correct, but partially. i'll edit this in seconds – Asynchronousx Nov 18 '17 at 17:17
2

As there is already an answer giving you directions to correct the specific errors, I will try to focus on a suggestion that will help you avoid similar error all together:

Try separating your current function into two:

  1. One that searches a node with specific key, for example: Node* search(int key) function that returns either a pointer to the node with wanted key or nullptr, or use the one that your already have.

  2. One that deletes (and re-wires) the node passed as pointer and returns: next, previous, etc: Node* delete(Node* n).

Then call search, test against nulltpr, and if different, pass the returned pointer as an input argument in delete.

In this way you could easily detect on which phase is you problem: searching or deleting.


P.S.: figuring out re-wiring bugs is usually done through diagrams (boxes and arrows). Decide what you should do, separate it into steps and implement it.

Ziezi
  • 6,375
  • 3
  • 39
  • 49
  • 1
    I debugged my program with visual studio, step by step, checking the pointers value and the value itself at every step. It seems that the search code lines works well, because it find my desired node, the one i'm gettin trouble with is the delete one, when my node got only one son. – Asynchronousx Nov 18 '17 at 12:43
  • @Asynchronousx Exactly, if the problem was related only to the `delete` part, it would have been much more easier to read and eventually detect, both by you and the rest of the people possibly reading your code. – Ziezi Nov 18 '17 at 12:50
  • But the problem is related only to the delete, because everything else works like a charm. The only part that is causing me a bunch of headache is the deleting the one-node-son case. (left or right either) – Asynchronousx Nov 18 '17 at 12:55
2

Well, once one know that DEBUG version use sentinel value, it become much more trivial to find problems in code.

0xDD is for dead memory. That is memory that has been already deleted. So when the debugger stop and it tells you that you have a bad pointer and the data contains a lot of 0xDD, you know the data has already been deleted. At that point, you should inspect class that contain the data to see if they are deleted too so you know which objects were deleted when the are embedded one inside another.

Be aware that sometime you might have some data that has been changed in part of the class if some operations use delete memory. Looking at memory pattern also help finding uninitialized memory and other similar problem.

Some other links:

In a case like yours, if you follow the good practice of writing unit tests, then it would even be more trivial to find the problem. In fact, if you do proper testing, then you will test all possible cases so you will know which cases fail and it would help you find where you might do something wrong.

Phil1970
  • 2,605
  • 2
  • 14
  • 15
1

I would like to add something to the answer of @Bonje Fir. Sure it is a correct answer, but partially: I'll explain why.

He suggested to swap the last piece of code i wrote:

Case: we're in the right subtree, and we would like to erase 70 (because we don't have anymore the leaf node 60):

          50
       /     \
      30      70
     /  \       \
   20   40      80 

now, with the code that @Bonje Fir suggested us, we would got a problem here:

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

Because the code is saying, that once you updated the new root with his son, link the father of the previous root (that we saved in a tmp variable) with his left son. Then we would assist to something like this:

          50
       /     x
      80      80
     /  \       
   20   40     

and that's inconsistent.

Now take a look on the other side, with the same code and without leaf node 20:

      50
   /      \
  30       70
    \     /  \
    40   60  80

the code fit here, because we're in the right subtree. so once update 30 with 40 (root = root -> right) the situation would be this:

      50
   x      \
  40       70
          /  \
         60  80

then the piece of code @Bonje Fir wrote us, would fit perfectly:

tmp->Father->Left = root

because for sure, we're assigning 40 to the left son of the father of the original root. (because we're in the left subtree.)

      50
   /      \
  40       70
          /  \
         60  80

So i made a little change to correct this logic problem, and make it work both in the right and left subtree.

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

i took advantage of the fact i didnt erase my root, once assigned the new one, so the father of the root still points to the old root.

(same speech for the opposite case.)

Asynchronousx
  • 101
  • 1
  • 1
  • 8