I recently started using a new data set with my BST class and SegFaults started popping up... It's happening somewhere within the recursive remove methods' handling of deleting a node with two children (currently commented out so it will run). Does anyone see where the error is?
Additionally, I really feel like I made the remove function more complicated/verbose than it really is and would appreciate any suggestions to simplify it.
template<typename U>
bool bst<U>::remove(U toDel) {
bstNode<U>* cur = getPtr(toDel, root);
return remove(toDel, cur);
} // END delete(toDel)
template<typename U>
bool bst<U>::remove(U toDel, bstNode<U>*& cur) {
if(!cur) {
returnfalse;
} // END if
if(cur->data == toDel) {
bstNode<U>* parent = getParent(toDel, root, nullptr);
if(cur->left == nullptr && cur->right == nullptr) { // Zero children
cout << "Removed node (" << toDel << ") w/zero children.\n";
} elseif( (cur->left != nullptr) && (cur->right != nullptr) ) { // Two children *****************ERROR HERE********************
bstNode<U>* min = getMin(cur->right);
// if(parent->right == cur) { // Node is a right child
// parent->right = min;
// } else { // Node is a left child
// parent->left = min;
// } // END if/else
// min->right = cur->right;
// min->left = cur->left;
cout << "Removed node (" << toDel << ") w/two children.\n";
} else { // One child
if(toDel < parent->data) { // Node is a left child
if(cur->left != nullptr) { // Nodes' child is on left
parent->left = cur->left;
cur->left = nullptr;
} else { // Nodes' child is on right
parent->left = cur->right;
cur->right = nullptr;
} // END if/else
} else { // Node is a right child
if(cur->left != nullptr) { // Nodes' child is on left
parent->right = cur->left;
cur->left = nullptr;
} else { // Nodes' child is on right
parent->right = cur->right;
cur->right = nullptr;
} // END if/else
} // END if/else
cout << "Removed node (" << toDel << ") w/one child.\n";
} // END if/else
delete cur;
cur = nullptr;
nodeCount--;
returntrue;
} // END if
} // END remove(toDel, cur)
getMin:
1 2 3 4 5 6 7 8 9
template<typename U>
bstNode<U>* bst<U>::getMin(bstNode<U>* cur) {
if(!cur || !cur->left) { // data not found || min is reached
return cur;
} else { // min is not reached
return getMin(cur->left);
} // END if/else
} // END getMin(data, cur)
Where's the recursion? I don't see any recursive calls.
Does getParent() always return a non-null pointer? I don't see any checks. What does it return if the node is the root? Also, it's kind of strange that you pass toDel instead of cur.
Line 28 creates a cycle in the tree structure. By definition of getMin(), min is cur->left or an (grand-)child of cur->left, so line 28 causes this loop to never end:
1 2 3 4
// min->left = cur->left;
auto current = cur;
while (current)
current = current->left;
I have updated the remove function to 1) no longer be 'recursive' as all recursion is handled by the helper methods getPtr, getMin, and getParent. I also added a check for the case in which the item to delete is the root, because getParent does return nullptr if it is passed the root (full method below). However, I am still getting SegFaults... I also removed the line min->right = cur->right because as you pointed out it would create a loop.
OK, I definitely see that one but I don't see how it would cause a segFault as the remove() method ensures that when getParent is called it will return a valid ptr (unless I am not removing the nodes correctly, which is totally possible) since it handles the root case and not found case before calling getParent. (Right? I'm progressively getting more and more confused lol)
You removed the wrong line. You're still creating a cycle in the tree.
And once you remove the correct line, you'll still need to do something with cur->left. You can't just leak the node like you're doing now with cur->right.
template<typename U>
bst<U>::~bst() {
DESTROY(root);
} // END ~bst()
template<typename U>
void bst<U>::DESTROY(bstNode<U>* cur) {
if(!cur) {
if(!cur->left && !cur->right) {
delete cur;
cur = nullptr;
} else {
DESTROY(cur->left);
DESTROY(cur->right);
} // END if/else
} // END if
return;
} // END DESTROY(cur)
I see the leaked node (I'm gonna' try my hand at fixing that now that the segFault has quit). I think I see the cycle but only in the case the there is no left node to the right child of the node being deleted, in which cur->right would be pointing to min and setting min->right to itself would create the cycle. Is that the only case I need to worry about?
This is unrelated to your problems, but bst<U>::DESTROY() does nothing unless passed a null pointer, and in that case it tries to dereference it.
Oops. My bad. I thought your were passing cur to getMin(), but you're passing cur->right. You were right to remove that line, and the line that remains should not create a cycle, AFAICT.
O wow, I had gone through and tried to all replace all my 'pointer == something' or 'pointer != something' to just 'pointer' or '!pointer' and got that one mixed up. Not even gonna' ask how that wasn't throwing segFaults =P
Thank you both for all the help I really appreciate it!
OK, I definitely see that one but I don't see how it would cause a segFault as the remove() method ensures that when getParent is called it will return a valid ptr...
...Although I don't understand how I did swap around the getParent method to validate cur and the segFault stopped.
getParent calls itself and when it calls itself it makes no attempt to ensure it does so with a non-null pointer.
Line 5:This replaces the entire tree with the smallest node, leaking then rest of the tree.
Line 16. The if is unnecessary since getptr() returned the node that you want.
I think you'll find the code a lot easier to write if you getPtr(), getParent() and getMin() return references to the appropriate pointers rather than copies of them. It means you'll have to pass the root by reference.