Reason of child node assignments after deletion from Binary Search Tree? - c++

I have a point which i haven't understood while learning BST Deletion mechanism. Could you explain me why there is an assignment (p->rchild =, p->lchild =) each time the Delete(Node* p, int key) is called? Actually, I thought that the Delete(Node* p, int key) method just keeps returning without any mutation so the tree doesn't change.
And while i was looking for an explanation, i stumbled into this sentence :
We have to make assignments after deletion else we will end up having
duplicate nodes.
If you agree with this statement, could you please explain it to me?
Node* BST::Delete(Node *p, int key) {
Node* q;
if (p == nullptr){
return nullptr;
}
if (p->lchild == nullptr && p->rchild == nullptr){
if (p == root){
root = nullptr;
}
delete p;
return nullptr;
}
if (key < p->data){
p->lchild = Delete(p->lchild, key);
} else if (key > p->data){
p->rchild = Delete(p->rchild, key);
} else {
if (Height(p->lchild) > Height(p->rchild)){
q = InPre(p->lchild);
p->data = q->data;
p->lchild = Delete(p->lchild, q->data);
} else {
q = InSucc(p->rchild);
p->data = q->data;
p->rchild = Delete(p->rchild, q->data);
}
}
return p;
}

why there is an assignment (p->rchild =, p->lchild =) each time the Delete(Node* p, int key) is called?
If the data is found, then the goal is to have a tree that has one node less. The algorithm will uses a value-swapping mechanism to ensure that the node that will actually be removed, is always a leaf node. The deletion of a leaf node consists of two actions:
The removal from memory;
The update of its parent so that the child pointer that references this deleted node is set to a null pointer.
As the algorithm recurses right to that leaf node, it cannot set the null pointer to its parent, as there is at this stage no reference available to the parent node. For that the caller should do something, since the caller does have a reference to the parent.
So when the recursive traversal arrives at the leaf node, it needs to convey to the caller that this node should be detached. It does so by returning a null pointer, and the agreement is that the caller (whose current node p is the parent) should assign the returned pointer to relevant child pointer. That way the deleted node is really detached from the rest of the tree.
Actually, I thought that the Delete(Node* p, int key) method just keeps returning without any mutation so the tree doesn't change.
Surely the tree must change somehow to have a node deleted from it. The change happens in the assignment to p->lchild or p->rchild
I stumbled into this sentence :
We have to make assignments after deletion else we will end up having duplicate nodes.
This is true. Let's take an example tree:
7
/ \
3 8
/ \ \
1 5 9
/ / \
0 4 6
Now let's see what happens if we call Delete(root, 3). p points to the node with value 7. We go to the left with a recursive call:
p->lchild = Delete(p->lchild, key);
In the recursive execution context, we get a new p which points to the node with value 3. This is the value we're looking for, so we get into the outer else block. As the heights of the subtrees below that node are equal, we get into the inner else block. There we assign:
q = InSucc(p->rchild);
This q will reference the node with value 4. And now a duplication happens. We copy the data from q to p. That comes down to deleting the value 3 from the tree:
p->data = q->data;
But now we have twice the value 4 in the tree.
7
/ \
4* 8
/ \ \
1 5 9
/ / \
0 4* 6
So the algorithm descends to the (right) child, and now seeks to delete the node with value 4 in that subtree:
p->rchild = Delete(p->rchild, q->data);
In this new recursive call we get a new p again, which now refers to the node with value 5. We move left -- this assignment will play an important role later:
p->lchild = Delete(p->lchild, key);
This final recursive call has a new p that refers to the node with value 4 -- the one we were looking for.
This time we end up in the if block that has the delete, because this node is a leaf node. The node is freed and a null pointer is returned to the caller. From here on we start backtracking up the tree.
So one level up, at the node with value 5, we get the return value from the recursive call (which is a null pointer) and assign it:
p->lchild = Delete(p->lchild, key);
This important assignment will detach the duplicate node (with value 4) from the tree. You can see that if this assignment would not have been made, there would still be a reference to that node with a duplicate value -- even though it is pointing to freed memory.
The tree is now in its final shape:
7
/ \
4 8
/ \ \
1 5 9
/ \
0 6
Backtracking will still continue, going back to the root. Also there assignments are made to some child pointers, but these will not change the tree, as in all these cases we had returned return p;, which was the original value of the caller's child pointer.
Bug
As mentioned in the comments, the code has a bug. When deleting a leaf node, it does not verify that this node has actually the value to delete. And so in case you call this method with a value that does not occur in the tree, you'll end up deleting a leaf node with another value. In the example tree above: if you were to call Delete(root, 10), the node with value 9 will be deleted.
To correct this bug, move the following if block:
if (p->lchild == nullptr && p->rchild == nullptr){
... inside the outer else block, as the first statement there.

Related

Nodes in binary tree are null

void MultiMap::insert(string key, unsigned int value)
{
if(head == nullptr)
head = new Node(key, value);
else
{
Node* tempNode = head;
while(tempNode != nullptr)
{
if(key <= tempNode->m_key)
tempNode = tempNode->m_left;
else if(key > tempNode->m_key)
tempNode = tempNode->m_right;
}
/*line 1*/tempNode = new Node(key, value);
//*line 2*/head->m_left = new Node(key, value);
}
}
For an assignment, I have to make a binary tree class, "MultiMap" with nodes that contain a string and an int.
The above is code to insert a new node into the tree. The nodes are sorted by their strings. If the string of the node I am trying to insert is > the current node, the program should try to insert it on the right branch of the tree, and if it is <=, the program should try to insert it on the left branch of the tree.
I tested it by trying to insert two nodes: (Joe, 5) and (Bill, 1) in that order, so if the program works properly, "bill" should be on the left branch of "joe".
Line 2 is commented out.
If I use line 1, the program compiles and "inserts" the second node, but when I try to look for it with other code, it only finds a nullptr. If I replace line 1 with line 2, the program works as expected.
"tempNode" is what I'm using to trace through the tree to find the appropriate place to insert a new node. "head" is a pointer to the first node in the tree. "m_left" and "m_right" are pointers to nodes, representing the left and right branches of a node, respectively.
I don't know why the two lines don't do the same thing even though at that point, it seems like tempNode and head->m_left are pointing to the same location in memory: the left branch of the first node.
Pointers are variables that hold addresses. There is nothing magic about them. Line 1 does this:
tempNode = new Node(key, value);
This doesn't insert anything into your tree. In fact, it just leaks memory.
What tempNode pointed to prior to this statement is irrelevant. More importantly, how tempNode held that prior value is already lost because you're already descended down the tree one level. Two pointers holding the same address just means the address is reachable with two pointers. Assigning a new address to a pointer has no effect on the previously addressed entity (if there was any).
Your task should be finding the pointer that should be filled in with the address of a newly allocated object. You found it (sort of). Unfortunately you also lost it as soon as you walked into it with your step "down" the tree for the final null-detection. As soon as this:
while (tempNode != nullptr)
becomes false and breaks, you're already one node too far. There are a number of ways to handle this. Some people like using a "parent" pointer, but that just means you have to special-case an empty map condition. Consider this instead:
void MultiMap::insert(string key, unsigned int value)
{
// pp will always point to the pointer we're testing
// i.e. a pointer to pointer.
Node **pp = &head;
while (*pp) // while whatever pp points to is a non-null-pointer
{
if (key < (*pp)->m_key)
pp = &(*pp)->m_left; // put address of left-pointer into pp
else if ((*pp)->m_key < key)
pp = &(*pp)->m_right; // put address of right pointer into pp
else break; // strict weak order match
}
if (*pp)
{
// found matching key. NOTE: unclear if you wanted to just update or not
}
else
{
// allocate new node.
*pp = new Node(key,value);
}
}
And you'll notice other than initializing our pointer-to-pointer with the address of the head node pointer, head is never referenced again.
Finally, notice there is no special-case head-node test. If the map is empty and the head pointer is NULL, this will automatically create a new node and make it the root.
What is going on here:
Node* tempNode = head;
while(tempNode != nullptr)
{
if(key <= tempNode->m_key)
tempNode = tempNode->m_left;
else if(key > tempNode->m_key)
tempNode = tempNode->m_right;
}
OK, now tempNode == nullptr, and it does not point to any node of the tree. As it is the variable on the stack, the next line:
/*line 1*/tempNode = new Node(key, value);
just initializes this local pointer and does not affect the tree itself. (Really here will be a memory leak.)
In your second line you initialize the node in the tree:
head->m_left = new Node(key, value);
But only for head->m_left.
So you can write:
if (key <= tempNode->m_key) {
if (tempNode->m_left == nullptr) {
tempNode->m_left = new Node(key, value);
break; // traverse tree loop
} else {
tempNode = tempNode->m_left;
}
}

Is it possible to make efficient pointer-based binary heap implementations?

Is it even possible to implement a binary heap using pointers rather than an array? I have searched around the internet (including SO) and no answer can be found.
The main problem here is that, how do you keep track of the last pointer? When you insert X into the heap, you place X at the last pointer and then bubble it up. Now, where does the last pointer point to?
And also, what happens when you want to remove the root? You exchange the root with the last element, and then bubble the new root down. Now, how do you know what's the new "last element" that you need when you remove root again?
Solution 1: Maintain a pointer to the last node
In this approach a pointer to the last node is maintained, and parent pointers are required.
When inserting, starting at the last node navigate to the node below which a new last node will be inserted. Insert the new node and remember it as the last node. Move it up the heap as needed.
When removing, starting at the last node navigate to the second-to-last node. Remove the original last node and remember the the new last node just found. Move the original last node into the place of the deleted node and then move it up or down the heap as needed.
It is possible to navigate to the mentioned nodes in O(log(n)) time and O(1) space. Here is a description of the algorithms but the code is available below:
For insert: If the last node is a left child, proceed with inserting the new node as the right child of the parent. Otherwise... Start at the last node. Move up as long as the current node is a right child. If the root was not reached, move to the sibling node at the right (which necessarily exists). Then (whether or not the root was reached), move down to the left as long as possible. Proceed by inserting the new node as the left child of the current node.
For remove: If the last node is the root, proceed by removing the root. Otherwise... Start at the last node. Move up as long as the current node is a left child. If the root was not reached, move to the sibling left node (which necessarily exists). Then (whether or not the root was reached), move down to the right as long as possible. We have arrived at the second-to-last node.
However, there are some things to be careful about:
When removing, there are two special cases: when the last node is being removed (unlink the node and change the last node pointer), and when the second-to-last node is being removed (not really special but the possibility must be considered when replacing the deleted node with the last node).
When moving nodes up or down the heap, if the move affects the last node, the last-node pointer must be corrected.
Long ago I have made an implementation of this. In case it helps someone, here is the code. Algorithmically it should be correct (has also been subjected to stress testing with verification), but there is no warranty of course.
Solution 2: Reach the last node from the root
This solution requires maintaining the node count (but not parent pointers or the last node). The last (or second-to-last) node is found by navigating from the root towards it.
Assume the nodes are numbered starting from 1, as per the typical notation for binary heaps. Pick any valid node number and represent it in binary. Ignore the first (most significant) 1 bit. The remaining bits define the path from the root to that node; zero means left and one means right.
For example, to reach node 11 (=1011b), start at the root then go left (0), right (1), right (1).
This algorithm can be used in insert to find where to place the new node (follow the path for node node_count+1), and in remove to find the second-to-last-node (follow the path for node node_count-1).
This approach is used in libuv for timer management; see their implementation of the binary heap.
Usefulness of Pointer-based Binary Heaps
Many answers here and even literature say that an array-based implementation of a binary heap is strictly superior. However I contest that because there are situations where the use of an array is undesirable, typically because the upper size of the array is not known in advance and on-demand reallocations of an array are not deemed acceptable, for example due to latency or possibility of allocation failure.
The fact that libuv (a widely used event loop library) uses a binary heap with pointers only further speaks for this.
It is worth noting that the Linux kernel uses (pointer-based) red-black trees as a priority queue in a few cases, for example for CPU scheduling and timer management (for the same purpose as in libuv). I find it likely that changing these to use a pointer-based binary heap will improve performance.
Hybrid Approach
It is possible to combine Solution 1 and Solution 2 into a hybrid approach which dynamically picks either of the algorithms (for finding the last or second-to-last node), the one with a lower cost, measured in the number of edges that need to be traversed. Assume we want to navigate to node number N, and highest_bit(X) means the 0-based index of the highest-order bit in N (0 means the LSB).
The cost of navigating from the root (Solution 2) is highest_bit(N).
The cost of navigating from the previous node which is on the same level (Solution 1) is: 2 * (1 + highest_bit((N-1) xor N)).
Note that in the case of a level change the second equation will yield a wrong (too large) result, but in that case traversal from the root is more efficient anyway (for which the estimate is correct) and will be chosen, so there is no need for special handling.
Some CPUs have an instruction for highest_bit allowing very efficient implementation of these estimates. An alternative approach is to maintain the highest bit as a bit mask and do these calculation with bit masks instead of bit indices. Consider for example that 1 followed by N zeroes squared is equal to 1 followed by 2N zeroes).
In my testing it has turned out that Solution 1 is on average faster than Solution 2, and the hybrid approach appeared to have about the same average performance as Solution 2. Therefore the hybrid approach is only useful if one needs to minimize the worst-case time, which is (twice) better in Solution 2; since Solution 1 will in the worst case traverse the entire height of the tree up and then down.
Code for Solution 1
Note that the traversal code in insert is slightly different from the algorithm described above but still correct.
struct Node {
Node *parent;
Node *link[2];
};
struct Heap {
Node *root;
Node *last;
};
void init (Heap *h)
{
h->root = NULL;
h->last = NULL;
}
void insert (Heap *h, Node *node)
{
// If the heap is empty, insert root node.
if (h->root == NULL) {
h->root = node;
h->last = node;
node->parent = NULL;
node->link[0] = NULL;
node->link[1] = NULL;
return;
}
// We will be finding the node to insert below.
// Start with the current last node and move up as long as the
// parent exists and the current node is its right child.
Node *cur = h->last;
while (cur->parent != NULL && cur == cur->parent->link[1]) {
cur = cur->parent;
}
if (cur->parent != NULL) {
if (cur->parent->link[1] != NULL) {
// The parent has a right child. Attach the new node to
// the leftmost node of the parent's right subtree.
cur = cur->parent->link[1];
while (cur->link[0] != NULL) {
cur = cur->link[0];
}
} else {
// The parent has no right child. This can only happen when
// the last node is a right child. The new node can become
// the right child.
cur = cur->parent;
}
} else {
// We have reached the root. The new node will be at a new level,
// the left child of the current leftmost node.
while (cur->link[0] != NULL) {
cur = cur->link[0];
}
}
// This is the node below which we will insert. It has either no
// children or only a left child.
assert(cur->link[1] == NULL);
// Insert the new node, which becomes the new last node.
h->last = node;
cur->link[cur->link[0] != NULL] = node;
node->parent = cur;
node->link[0] = NULL;
node->link[1] = NULL;
// Restore the heap property.
while (node->parent != NULL && value(node->parent) > value(node)) {
move_one_up(h, node);
}
}
void remove (Heap *h, Node *node)
{
// If this is the only node left, remove it.
if (node->parent == NULL && node->link[0] == NULL && node->link[1] == NULL) {
h->root = NULL;
h->last = NULL;
return;
}
// Locate the node before the last node.
Node *cur = h->last;
while (cur->parent != NULL && cur == cur->parent->link[0]) {
cur = cur->parent;
}
if (cur->parent != NULL) {
assert(cur->parent->link[0] != NULL);
cur = cur->parent->link[0];
}
while (cur->link[1] != NULL) {
cur = cur->link[1];
}
// Disconnect the last node.
assert(h->last->parent != NULL);
h->last->parent->link[h->last == h->last->parent->link[1]] = NULL;
if (node == h->last) {
// Deleting last, set new last.
h->last = cur;
} else {
// Not deleting last, move last to node's place.
Node *srcnode = h->last;
replace_node(h, node, srcnode);
// Set new last unless node=cur; in this case it stays the same.
if (node != cur) {
h->last = cur;
}
// Restore the heap property.
if (srcnode->parent != NULL && value(srcnode) < value(srcnode->parent)) {
do {
move_one_up(h, srcnode);
} while (srcnode->parent != NULL && value(srcnode) < value(srcnode->parent));
} else {
while (srcnode->link[0] != NULL || srcnode->link[1] != NULL) {
bool side = srcnode->link[1] != NULL && value(srcnode->link[0]) >= value(srcnode->link[1]);
if (value(srcnode) > value(srcnode->link[side])) {
move_one_up(h, srcnode->link[side]);
} else {
break;
}
}
}
}
}
Two other functions are used: move_one_up moves a node one step up in the heap, and replace_node replaces moves an existing node (srcnode) into the place held by the node being deleted. Both work only by adjusting the links to and from the other nodes, there is no actual moving of data involved. These functions should not be hard to implement, and the mentioned link includes my implementations.
The pointer based implementation of the binary heap is incredibly difficult when compared to the array based implementation. But it is fun to code it. The basic idea is that of a binary tree. But the biggest challenge you will have is to keep it left-filled. You will have difficulty in finding the exact location as to where you must insert a node.
For that, you must know binary traversal. What we do is. Suppose our heap size is 6. We will take the number + 1, and convert it to bits. The binary representation of 7 is, "111". Now, remember to always omit the first bit. So, now we are left with "11". Read from left-to-right. The bit is '1', so, go to the right child of the root node. Then the string left is "1", the first bit is '1'. As you have only 1 bit left, this single bit tells you where to insert the new node. As it is '1' the new node must be the right child of the current node. So, the raw working of the process is that, convert the size of the heap into bits. Omit the first bit. According to the leftmost bit, go to the right child of the current node if it is '1', and to the left child of the current node if it is '0'.
After inserting the new node, you will bubble it up the heap. This tells you that you will be needing the parent pointer. So, you go once down the tree and once up the tree. So, your insertion operation will take O(log N).
As for the deletion, it is still a challenge to find the last node. I hope you are familiar with deletion in a heap where we swap it with the last node and do a heapify. But for that you need the last node, for that too, we use the same technique as we did for finding the location to insert the new node, but with a little twist. If you want to find the location of the last node, you must use the binary representation of the value HeapSize itself, not HeapSize + 1. This will take you to the last node. So, the deletion will also cost you O(log N).
I'm having trouble in posting the source code here, but you can refer to my blog for the source code. In the code, there is Heap Sort too. It is very simple. We just keep deleting the root node. Refer to my blog for explanation with figures. But I guess this explanation would do.
I hope my answer has helped you. If it did, let me know...! ☺
For those saying this is a useless exercise, there are a couple of (admittedly rare) use cases where a pointer-based solution is better. If the max size of the heap is unknown, then an array implementation will need to stop-and-copy into fresh storage when the array fills. In a system (e.g. embedded) where there are fixed response time constraints and/or where free memory exists, but not a big enough contiguous block, this may be not be acceptable. The pointer tree lets you allocate incrementally in small, fixed-size chunks, so it doesn't have these problems.
To answer the OP's question, parent pointers and/or elaborate tracking aren't necessary to determine where to insert the next node or find the current last one. You only need the bits in the binary rep of the heap's size to determine the left and right child pointers to follow.
Edit Just saw Vamsi Sangam#'s explanation of this algorithm. Nonetheless, here's a demo in code:
#include <stdio.h>
#include <stdlib.h>
typedef struct node_s {
struct node_s *lft, *rgt;
int data;
} NODE;
typedef struct heap_s {
NODE *root;
size_t size;
} HEAP;
// Add a new node at the last position of a complete binary tree.
void add(HEAP *heap, NODE *node) {
size_t mask = 0;
size_t size = ++heap->size;
// Initialize the mask to the high-order 1 of the size.
for (size_t x = size; x; x &= x - 1) mask = x;
NODE **pp = &heap->root;
// Advance pp right or left depending on size bits.
while (mask >>= 1) pp = (size & mask) ? &(*pp)->rgt : &(*pp)->lft;
*pp = node;
}
void print(NODE *p, int indent) {
if (!p) return;
for (int i = 0; i < indent; i++) printf(" ");
printf("%d\n", p->data);
print(p->lft, indent + 1);
print(p->rgt, indent + 1);
}
int main(void) {
HEAP h[1] = { NULL, 0 };
for (int i = 0; i < 16; i++) {
NODE *p = malloc(sizeof *p);
p->lft = p->rgt = NULL;
p->data = i;
add(h, p);
}
print(h->root, 0);
}
As you'd hope, it prints:
0
1
3
7
15
8
4
9
10
2
5
11
12
6
13
14
Sift-down can use the same kind of iteration. It's also possible to implement the sift-up without parent pointers using either recursion or an explicit stack to "save" the nodes in the path from root to the node to be sifted.
A binary heap is a complete binary tree obeying the heap property. That's all. The fact that it can be stored using an array, is just nice and convenient. But sure, you can implement it using a linked structure. It's a fun exercise! As such, it is mostly useful as an exercise or in more advanced datastructures( meldable, addressable priority queues for example ), as it is quite a bit more involved than doing the array version. For example, think about siftup/siftdown procedures, and all the edge cutting/sewing you'll need to get right. Anyways, it's not too hard, and once again, good fun!
There are a number of comments pointing out that by a strict definition it is possible to implement a binary heap as a tree and still call it a binary heap.
Here is the problem -- there is never a reason to do so since using an array is better in every way.
If you do searches to try to find information on how to work with a heap using pointers you are not going to find any -- no one bothers since there is no reason to implement a binary heap in this way.
If you do searches on trees you will find lots of helpful materials. This was the point of my original answer. There is nothing that stops people from doing it this way but there is never a reason to do so.
You say -- I have to do so, I've got an legacy system and I have pointers to nodes I need to put them in a heap.
Make an array of those pointers and work with them in this array as you would a standard array based heap, when you need the contents dereference them. This will work better than any other way of implementing your system.
I can think of no other reason to implement a heap using pointers.
Original Answer:
If you implement it with pointers then it is a tree. A heap is a heap because of how you can calculate the location of the children as a location in the array (2 * node index +1 and 2 * node index + 2).
So no, you can't implement it with pointers, if you do you've implemented a tree.
Implementing trees is well documented if you search you will find your answers.
I have searched around the internet (including SO) and no answer can be found.
Funny, because I found an answer on SO within moments of googling it. (Same Google search led me here.)
Basically:
The node should have pointers to its parent, left child, and right child.
You need to keep pointers to:
the root of the tree (root) (duh)
the last node inserted (lastNode)
the leftmost node of the lowest level (leftmostNode)
the rightmost node of the next-to-lowest level (rightmostNode)
Now, let the node to be inserted be nodeToInsert. Insertion algorithm in pseudocode:
void insertNode(Data data) {
Node* parentNode, nodeToInsert = new Node(data);
if(root == NULL) { // empty tree
parent = NULL;
root = nodeToInsert;
leftmostNode = root;
rightmostNode = NULL;
} else if(lastNode.parent == rightmostNode && lastNode.isRightChild()) {
// level full
parentNode = leftmostNode;
leftmostNode = nodeToInsert;
parentNode->leftChild = nodeToInsert;
rightmostNode = lastNode;
} else if (lastNode.isLeftChild()) {
parentNode = lastNode->parent;
parentNode->rightChild = nodeToInsert;
} else if(lastNode.isRightChild()) {
parentNode = lastNode->parent->parent->rightChild;
parentNode->leftChild = nodeToInsert;
}
nodeToInsert->parent = parentNode;
lastNode = nodeToInsert;
heapifyUp(nodeToInsert);
}
Pseudocode for deletion:
Data deleteNode() {
Data result = root->data;
if(root == NULL) throw new EmptyHeapException();
if(lastNode == root) { // the root is the only node
free(root);
root = NULL;
} else {
Node* newRoot = lastNode;
if(lastNode == leftmostNode) {
newRoot->parent->leftChild = NULL;
lastNode = rightmostNode;
rightmostNode = rightmostNode->parent;
} else if(lastNode.isRightChild()) {
newRoot->parent->rightChild = NULL;
lastNode = newRoot->parent->leftChild;
} else if(lastNode.isLeftChild()) {
newRoot->parent->leftChild = NULL;
lastNode = newRoot->parent->parent->leftChild->rightChild;
}
newRoot->leftChild = root->leftChild;
newRoot->rightChild = root->rightChild;
newRoot->parent = NULL;
free(root);
root = newRoot;
heapifyDown(root);
}
return result;
}
heapifyUp() and heapifyDown() shouldn’t be too hard, though of course you’ll have to make sure those functions don’t make leftmostNode, rightmostNode, or lastNode point at the wrong place.
TL;DR Just use a goddamn array.

Removing from a Binary Search Tree

I am trying to write a remove from a binary tree function. I'm kinda lost so I'm trying to handle it case by case, starting with if the value I'm trying to remove is in the root of the BST. To test my function, I am first calling a printcontents() function that prints all the contents of the tree, then I'm calling remove(8) [8 being the value in my root at the moment), and then calling printcontents() again. The way I'm doing it is by trying to replace the root with the "right-most" value in the left side of the tree. When I call printcontents the second time, it prints the new root value correctly, but when it continues printing the contents and reaches the point where that value used to be, it has a random long number "-572......"(although i don't think the number matters) and then my program crashes. I see my root's value is being replaced, but what happens afterwards??
Here's my remove function:
void BinarySearchTree::remove(int value) {
Node* tmp = head;
Node* tmp2 = head;
if (head->data == value && head->left != NULL) {
tmp=tmp->left;
while (tmp->right != NULL) {
tmp=tmp->right;
}
while (tmp2->right->right != NULL) {
tmp2=tmp2->right;
}
if (tmp->left == NULL) {
head->data = tmp->data;
tmp2->right = NULL;
delete tmp;
}
if (tmp->left != NULL) {
head->data = tmp->data;
tmp2->right = tmp->left;
delete tmp;
}
}
It's obviously incomplete, but I'm testing it to only handle the case in which the root is removed and replaced by the right-most value in the left side of the tree (assuming there is a left side, which there is), and I feel like logically it should be working, so perhaps it is when I "delete tmp" that things go wrong. I don't know whether posting my whole program will be necessary, but if so, let me know!
May I suggest that instead of writing out for root, why don't you treat the case as it is dealt with in CLRS : That is two distinct cases.
1. When node to be deleted is a leaf
2. When node to be deleted is non-leaf(in that case replace it with inorder successor/predecessor).
The root deletion obviously falls under the second case. This is just a suggestion.

Raw Binary Tree Deletewithcopying Issue (Improved Explaination)

I've created Raw BinaryTree. In this tree, insertion is not like BST, its like this::
If tree is empty then add value & make it root. (suppose 30)
If tree is not empty then input father value (30) & add new value (20) to its left subtree.
If left subtree is not empty then, insert value (20) to right subtree.
For next insertion, again take father value to determine where value is to be added.
& so on..
Its working fine except when I try to delete a node with two children. Method Im using to delete is deleteWithCopy.
As my instructor has told, deletewithcopy is:
1. Find father of node (temp) which is to be deleted.
2. If temp (node to be deleted) is the right child of 'father' then find temp's immediate Successor
3. If temp (node to be deleted) is the left child of 'father' then find temp's immediate Predecessor
4. Swap value of temp with its predecessor/succesor
5. Delete temp (which is now leaf of tree).
NOW How to find Successor & Predecessor.
Successor = logical successor of a node is its right-most child in left subtree
Predecessor = logical predecessor of a node is its left-most child in right subtree
According to algorithm I have created the function, but after deleting, when I traverse (or print) the tree, it shows run time error,
Unhandled exception at 0x008B5853 in binarytree.exe: 0xC0000005: Access violation reading location 0xFEEEFEEE.
which is error for "0xFEEEFEEE is used to mark freed memory in Visual C++."
I have dry run-ed this thing again & again, there is nothing out of bounds in memory that Im trying to acces, I have fixed every loose end, but still :(
Here is the function:
void BinaryTree<mytype>::deletewithTwoChild(BTNode<mytype> *temp)
{
BTNode<mytype> *father = findfather(temp, root); //found address of father of temp node & stored it in pointer
BTNode<mytype> *leaf = temp; //created a copy of temp node
/////CASE 1 (for predecessor)
if(temp==root || father->left==temp) //if temp is left child of its father then
{
leaf = leaf->left; //move leaf 1 time left
while(leaf->right!=0 ) //until leaf reaches the right most node of left subtree
{
leaf = leaf->right; //move leaf 1 time to right
}
//swapping values
mytype var = leaf->key_value; //created a template variable to store leaf's key
leaf->key_value = temp->key_value; //assigning temp's key to leaf
temp->key_value = var; //assigning leaf's key to temp
if(leaf->right!=0) //if leaf has right child then call deletewithOneChild function
{
deletewithOneChild(leaf); //call to respective function
}
else if(leaf->left==0 && leaf->right==0) //if leaf has no children then
{
deleteWithNoChild(leaf); //call to respective function
}
}
/////CASE 2 (for successor)
else if(father->right==temp) //if temp is right child of its father, then
{
leaf = leaf->right; //move leaf 1 time right
while(leaf->left!=0) //until leaf reaches the last node of tree which has no child
{
leaf = leaf->left; //move leaf 1 time to left
}
//swapping values
mytype var = leaf->key_value; //created a template variable to store leaf's key
leaf->key_value = temp->key_value; //assigning temp's key to leaf
temp->key_value = var; //assigning leaf's key to temp
if(leaf->right!=0) //if leaf has right child then call deletewithOneChild function
{
deletewithOneChild(leaf); //call to respective function
}
else if(leaf->left==0 && leaf->right==0) //if leaf has no children then
{
deleteWithNoChild(leaf); //call to respective function
}
}
}
Data Set I m using:
30
/ \
20 80
/ / \
10 40 120
\ / \
60 100 140
/ \ / \
50 70 130 150
Im trying to delete node 80, 60, 120, 140 when the run time error pops up. Plz help :(( Also I need guidence how to handle tree iff 30 is deleted.
As I know, the definition of successor and predecessor are different.
Successor : the minimum node of right subtree, that is, the left-most node of right subtree.
Predecessor : the maximum node of left subtree, that is, the right-most node of left subtree.
Back to Issue 1. I have noticed the if-else condition after swapping values in case 1. Since, in case 1, you are finding the right most node of the subtree, leaf->right always be null and leaf->left may not be null. As a result, none of the delete function be called in case after swapping values. This will cause the issue of wrong BST or even worse, the crash of the program. Therefore, the if-else condition in case would be:
// leaf->right always be null, only need to verify leaf->left.
if (leaf->left != 0)
{
deleteWithOneNode(leaf);
}
else
{
deleteWithNoChild(leaf);
}
Issue 2. As I know, the remove of one node in BST has no rule to choose predecessor or successor and the parent(father) node of the removed node is used only when swapping the whole node rather then swapping the value of the node only. Therefore, my delete function would be:
void BinaryTree<myType>::delete(BTNode<myType>* node, BTNode<myType>* parent)
{
if (node->right) // find successor first
{
BTNode* ptrParent = node;
BTNode* ptr = node->right;
while (ptr->left)
{
ptrParnet = ptr;
ptr = ptr->left;
}
// Since the ptr will be delete, we only assign the value of ptr to the value node
node->key_value = ptr->key_value;
if (node == ptrParent)
{
ptrParnet->right = ptr->right;
}
else
{
ptrParent->left = ptr->right;
}
delete ptr;
}
else if (node->left) // find predecessor
{
BTNode* ptrParent = node;
BTNode* ptr = node->left;
while (ptr->right)
{
ptrParent = ptr;
ptr = ptr->right;
}
// Since the ptr will be delete, we only assign the value of ptr to the value node
node->key_value = ptr->key_value;
if (node == ptrParent)
{
ptrParent->left = ptr->left;
}
else
{
ptrParent->right = ptr->left;
}
delete ptr;
}
else
{
if (node->key_value > parent->key_value)
{
parent->right = NULL;
}
else
{
parent->left = NULL;
}
delete node;
}
}
By using the function, the tree after removing 30 would be
40
/ \
20 80
/ / \
10 60 120
/ \ / \
50 70 100 140
/ \
130 150

Binary Search Tree node removal

I've been trying to implement a delete function for a Binary Search Tree but haven't been able to get it to work in all cases.
This is my latest attempt:
Node* RBT::BST_remove(int c)
{
Node* t = get_node(c);
Node* temp = t;
if(t->get_left() == empty)
*t = *t->get_left();
else if(t->get_right() == empty)
*t = *t->get_right();
else if((t->get_left() != empty) && (t->get_right() != empty))
{
Node* node = new Node(t->get_data(), t->get_parent(), t->get_colour(), t->get_left(), t->get_right());
*t = *node;
}
return temp;
}
Node* RBT::get_node(int c)
{
Node* pos = root;
while(pos != empty)
{
if(c < pos->get_data())
pos = pos->get_left();
else if(c == pos->get_data())
return pos;
else
pos = pos->get_right();
}
return NULL;
}
t is a node and empty is just a node with nothing in it.
I'm just trying to swap the values but I'm getting a runtime error. Any ideas?
edit: I'm returning temp to delete it afterwards.
Thanks
First, your last else if conditional clause is redundant. Swap it with an else clause.
Secondly, I think it would make things easier for you if you'd take as parameter a pointer to the node to remove. You can write a find() function which would find a node given its key. I'm assuming of course that you can change the function signature. If you can take as parameter the node to remove you can focus on removing the node rather than add logic for finding the node. Otherwise, still write that find() function and use that for getting the pointer to the relevant node.
When you remove a node in a binary search tree you must maintain the ordering so the tree doesn't lose its integrity. Recall that there is a specific ordering in the tree that supports the fast retrieval of elements. So, enumerate the possible cases:
The node to delete has no children. That's easy: just release its resources and you're done.
The node has a single child node. That's fairly simple too. Release the node and replace it with its child, so the child holds the removed node's place in the tree.
The node has two children. Let's call the node D. Find the right-most child of D's left subtree. Let's call this node R. Assign the value of R to D, and delete R (as described in this algorithm). Notice that R can have zero or one children.
The third scenario, illustrated:
.
.
.
/
D
/ \
/\ .
/ \
/ \
+------+
\
R
/
?