Deleting a node with 2 children from binary search tree [closed] - c

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 3 years ago.
Improve this question
I'm working on a C program that scans and stores students' records in the following format into a binary search tree. For the BST I've defined:
struct student
int id
char firstname[20]
char lastname[20]
float score
char zipcode[10]
struct node
struct student data
struct node* leftChild
struct node* rightChild
One of the functions of the program should be able to delete records from the tree. The program asks the user for the last name of the student that will be deleted.
The following is the method to traverse the BST to find the target last name to be deleted:
void traverse(node* root, student data)
if(root != NULL)
traverse(root->leftChild,data)
if(strcmp(root->data.lastname,data.lastname) == 0)
root = delete(root,data)
traverse(root->rightChild,data)
Sample student structure passed to traverse function is:
struct student John
int id = 1000
char firstname = John
char lastname = Adams
float score = 90.00
char zipcode = 92121
The delete function I'm using along with the findmin because I'm aware that I need to find the minimum in the right sub-tree of the root (target node) in order to replace the target node with it:
struct node* findmin(struct node* root)
while(root->leftChild != NULL)
root = root->leftChild
return root
struct node* delete(node* root, student data)
if(root == NULL) // check if empty tree
return root
// traverse towards left if ID is less
else if(data.iD < root->data.iD)
root->leftChild = delete(root->leftChild,data)
// towards right if greater than ID
else if(data.iD > root->data.iD)
root->rightChild = delete(root->rightChild,data)
/* else would mean target last name found */
else
/* if found node has no child */
if(root->leftChild == NULL && root->rightChild == NULL)
free(root)
root = NULL
// 1 child
else if(root->leftChild == NULL) // if left child is NULL
struct node* temp = root
root = root->rightChild
free(temp)
temp = NULL
else if(root->rightChild == NULL) // if right child is NULL
struct node* temp = root
root = root->leftChild
free(temp)
temp = NULL
// 2 children
else
struct node* temp = findmin(root->rightChild)
root->data = temp->data
root->rightChild = delete(root->rightChild,temp->data)
return root;
What I would expect to happen here is that student John will be passed into traverse along with the global root of BST and when printing the BST, student John will be deleted and no longer there. However, when I pass student containing a target last name, the name will still be existing in the tree. Furthermore, when I test my code and pass a record to delete, occasionally, the program will delete not the targeted node but the node with the greatest student ID (the node furthest right in the tree) and/or will greet me with a segmentation fault 11. Is there a flaw in the code I am not seeing? Let me know if you need me to provide any further information to help you help me.

There is a first problem with the traverse function. When you delete a node, you need to update the pointer to it. You don't keep track of it.
Here are the traverse and delete functions using the pointer to node pointer strategy. As you see the code becomes simpler.
In this code pRoot is a pointer to the root pointer. You can then update it's value. You can set it to NULL or to another node due to the deletion.
void traverse(node** pRoot, student data){
if(*pRoot != NULL) {
traverse(&((*pRoot)->leftChild),data);
if(strcmp((*pRoot)->data.lastname,data.lastname) == 0)
delete(pRoot,data);
traverse(&((*pRoot)->rightChild),data);
}
void delete(node* pRoot, student data)
if(*pRoot == NULL)
return;
node *temp = *pRoot;
if((*pRoot)->leftChild == NULL)
*pRoot = (*pRoot)->rightChild;
else if((*pRoot)->rightChild == NULL){
*pRoot = (*pRoot)->leftChild;
else {
// both children are not NULL :
// we replace *pRoot with the node with
// min ID detached from the rightChild.
// find node with min ID in right child
node **pNode = &((*pRoot)->rightChild);
while((*pNode)->leftChild != NULL)
pNode = &((*pNode)->leftChild);
// *pNode is the node with minID
// its left child is NULL,
// but its right child may not be NULL
// we detach the node with min ID
node *nNode = *pNode;
*pNode = nNode->rightChild;
//replace *pRoot with *nNode
nNode->rightChild = (*pRoot)->rightChild;
nNode->leftChild = (*pRoot)->leftChild;
*pRoot = nNode;
}
free(temp);
}
Combining the above traverse and delete functions into one function, you get
void delete(node** pRoot, student *data){
if(*pRoot == NULL)
return;
delete(&((*pRoot)->leftChild), data);
detele(&((*pRoot)->rightChild), data);
if(strcmp((*pRoot)->data.lastname,data->lastname) != 0)
return;
// we delete *pRoot
node *temp = *pRoot;
if((*pRoot)->leftChild == NULL)
*pRoot = (*pRoot)->rightChild;
else if((*pRoot)->rightChild == NULL){
*pRoot = (*pRoot)->leftChild;
else {
// both children are not NULL :
// we replace *pRoot with the node with
// min ID detached from the rightChild.
// find node with min ID in right child
node **pNode = &((*pRoot)->rightChild);
while((*pNode)->leftChild != NULL)
pNode = &((*pNode)->leftChild);
// *pNode is the node with minID
// its left child is NULL,
// but its right child may not be NULL
// we detach the node with min ID
node *nNode = *pNode;
*pNode = nNode->rightChild;
//replace *pRoot with *nNode
nNode->rightChild = (*pRoot)->rightChild;
nNode->leftChild = (*pRoot)->leftChild;
*pRoot = nNode;
}
free(temp);
}

Related

Changing root of tree and pruning

I have an n-ary tree structure like what's explained in the answer here:
typedef struct node {
int ID;
struct node *child; // points to (first) child
struct node *next; // points to next node at same level (sibling)
struct node *parent; // points to parent
} node;
Nodes are created using malloc.
I want to write a function that will change the root of the tree to another (specified) node, and deallocate the memory of all the nodes that - because of the change of root - are no longer part of the tree (i.e. if a node can't be tracked back via parents to the new root, it should be set to NULL and its memory should be deallocated).
For instance, if this is my tree:
1
/ \
2 3
/ \ \
4 5 6
/ \
7 8
and I want to change the root from 1 to 3, then after calling the prune_tree function, the root will be 3 and the memory of every node other than 3 and 6 will have been deallocated.
The closest I have come to solving my problem involves this function:
void prune_tree(node **root, node *new_root) {
if (*root == NULL || (*root)->parent == new_root)
return;
prune_tree(&((*root)->child), new_root);
prune_tree(&((*root)->next), new_root);
free(*root);
*root = NULL;
}
and after calling this function I set
root = new_root;
I got here mostly by trial and error; in fact I'm not even sure why this works most of the time I test it. It also adds what's probably an unnecessary step of having to set the root to the new root address after calling the function. I assume there's a way I can modify the root address in the function, or return the new root address.
I would think I don't need to worry about memory usage since the function is deallocating memory, but a time-efficient function is preferable. I'm not sure if that means I should steer away from recursion or not...
a recursive function would be perfect for this.
what you basically want to do (assuming there are no loops in the tree) is remove all the siblings of the new root, al their children and parents and al the parents of the root.
an algorithm could be:
typedef struct node {
int ID;
struct node *child;
struct node *sibling;
struct node *parent;
} node;
void remove_node(struct node* node, struct node* root)
{
if(node->parent != NULL)
remove_node(node->parent, new_root)
if(node->sibling != NULL)
remove_node(node->sibling, new_root)
if((node->child != NULL) && (node->child != root))
remove_node(node->sibling, new_root)
free(node)
}
void prune_tree(node **root, node *new_root) {
*root = new_root
remove_node(new_root->parent);
remove_node(new_root->sibling);
}
assumptions:
new_root is in the tree
no loops in the tree
I think I found a solution...
void prune_tree(node** root, node* new_root)
{
bool del = true;
if (*root == NULL)
return;
if (*root == new_root) {
del = false;
}
if (del){
prune_tree(&(*root)->child, new_root);
prune_tree(&(*root)->next, new_root);
free(*root);
*root = NULL;
}
else{
prune_tree(&(*root)->next, new_root);
}
}
Your approach does not work as you will be deallocating the new root itself. You only stop on nodes whose parent is the new root.
Here is a corrected version:
void prune_tree(node **root, node *new_root) {
if (*root == NULL)
return;
if (*root == new_root) {
(*root)->parent = NULL;
return;
}
prune_tree(&(*root)->child, new_root);
prune_tree(&(*root)->next, new_root);
free(*root);
*root = NULL;
}
You will still need to update the root of the tree to new_root after calling this function with its address:
prune_tree(&root, new_root);
root = new_root;

Binary Tree Extraction code

I have pre-written code that I'm trying to wrap my head around:
int maxExtract(node **tree)
{
node *prev = NULL;
node *curr = *tree;
int ret;
if(curr == NULL)
{
printf("Tree is empty!\n");
exit(-1);
}
while( curr->right != NULL )
{
prev = curr;
curr = curr->right;
}
ret = curr->data;
if( prev != NULL )
prev->right = curr->left;
else if( curr == *tree )
*tree = curr->left;
free(curr);
return ret;
}
I understand everything except the else if (curr == *tree) condition. I think it's saying that if the max node ends up being the root. However, wouldn't you need to change more connections than that after the else if, like to connect the right and left side of the tree to this new root after extracting the old one?
I think it's saying that if the max node ends up being the root.
That is exactly what it means and, if that's the case, then there can be nothing in the right side of that node because, if there were, the maximum would be somewhere in that (right) sub-tree, not at the root.
So let's consider the tree where the root is the maximum (and A1/A2 are arbitrary sub-trees, including empty ones):
MAX
/
x
/ \
A1 A2
To extract the maximum value, what you want to be left with is simply:
x
/ \
A1 A2
So the operation you want to perform is to make x the new root of the tree - this is done with the line in your code:
*tree = curr->left;
I'd probably write it slightly differently to explicitly handle the disparate cases rather than relying on things happening or not happening depending on various decisions in the middle of the code. By that, I mean:
int maxExtract (node **tree) {
// Handle empty tree as error.
if (*tree == NULL) {
printf ("Tree is empty!\n");
exit (-1);
}
// Handle root is max, i.e., has no right subtree.
if ((*tree)->right == NULL) {
node *nodeToDelete = *tree; // Save root for deletion.
int retVal = nodeToDelete->data; // Get data to return.
*tree = nodeToDelete->left; // Set new root.
free (nodeToDelete); // Delete old root.
return retVal; // Return old root value.
}
// Locate max and its previous.
node *prev = *tree;
node *curr = (*tree)->right;
while (curr->right != NULL) {
prev = curr;
curr = curr->right;
}
// Max has no right sub-tree but it MAY have a left one
// which needs to be transferred as-is to the right of prev.
int retVal = curr->data; // Get data to return.
node *nodeToDelete = curr; // Save root for deletion.
prev->right = curr->left; // Transfer left sub-tree (or null).
free (nodeToDelete); // Delete old max.
return retVal; // Return old max value.

C - passing sub linked list to functions

I'm working in this simple program with list but I'm having a bad time passing pointers.
Let's say I have a struct for students
typedef struct student{
char lastname[50];
int age;
int std_id;
struct student * next;
struct student * prev;
}stdn;
and I have another struct for classes
typedef struct class{
char class_id[3];
struct class * next;
struct class * prev;
struct student * stdn_list;
}clss;
so basically I have this list with classes, and each class contain a sublist with students.
so, here is the function creating the class list, and it works!
void create_class_list(clss ** root, clss * node){
clss * root_aux;
if(!(*root)){
(*root) = node;
}
else{
root_aux = (*root);
while(root_aux->next != NULL){
root_aux = root_aux->next;
}
node->prev = root_aux;
root_aux->next = node;
}
}
my problem is when I need to work with the sublist in each node of the class list.
Here is the function in charge of creating the sublist, and it works
void assign_student(clss ** root, stdn * node, char * class_id){
clss * root_aux;
stdn * stdn_aux;
root_aux = (*root);
while(root_aux != NULL){
if(strcmp(root_aux->class_id,class_id) == 0)
break;
root_aux = root_aux->next;
}
if(root_aux != NULL){
if(root_aux->stdn_list == NULL){
root_aux->stdn_list = node;
}
else{
stdn_aux = root_aux->stdn_list;
while(stdn_aux->next != NULL){
stdn_aux = stdn_aux->next;
}
node->prev = stdn_aux;
stdn_aux->next = node;
}
}
}
Basically this function looks for the specific class and add the student to that class.
My problem is when I want to delete a student, or sort the list using an algorithm like bubblesort, here's an example of a function to delete a student.
void delete_student(clss ** root, int stdn_id){
clss * root_aux;
stdn * stdn_aux;
stdn * temp;
int deleted=0;
root_aux = (*root);
while(root_aux != NULL){
stdn_aux = root_aux->stdn_list;
//try with root first//
if(stdn_aux->std_id == stdn_id){
temp = stdn_aux;
stdn_aux = stdn_aux->next;
stdn_aux->prev = NULL;
free(temp);
deleted = 1;
}
//if the student isn't the root
if(deleted == 0){
stdn_aux = stdn_aux->next;
while(stdn_aux != NULL){
if(stdn_aux->std_id == stdn_id){
temp = stdn_aux;
//link the prev element with the next element
stdn_aux->prev->next = stdn_aux->next;
//link the next element with the prev element
stdn_aux->next->prev = stdn_aux->prev;
stdn_aux = stdn_aux->next;
free(temp);
deleted = 1;
break;
}
stdn_aux = stdn_aux->next;
}
}
if(deleted == 1){
break;
}
root_aux = root_aux->next;
}
}
The function just looks like don't delete the element from the list and I'm not sure if is something with how I pass the pointer to the function, or how I create the list in first place.
When you delete a student node that is at the head of the Student List, you need to re-assign to root_aux->stdn_list as you are deleting the node it currently points to. That must be why it appers you are not deleting a student node.
root_aux->stdn_list = stdn_aux->next;
There are other issues with regard to processing that should be wrapped in if statements to prevent the program from core dumping:
Before you begin processing the student list, you need to first check that there is a student list. That is, check that the class variable pointing to the student list - root_aux->stdn_list - is not NULL.
Before doing the following statements, make sure that stdn_aux->next is not NULL, that is, there is stuff beyond the root node you are deleting.
stdn_aux = stdn_aux->next;
stdn_aux->prev = NULL;
Before making this assignment
stdn_aux->next->prev = stdn_aux->prev;
check that stdn_aux->next is not null because this is the last node in the student list.

Deleting a node from a Binary Tree Search

I think there are multiple errors in my code for deleting a node from a BST. I just can't figure out what! Here's my code. Thanks in advance!
void del(int val){
help = root;
f = help;
while (help){
if(help->data==val) break;
f = help;
if (val > help-> data) help = help->right;
else help = help->left;
} if(help->data != val) printf("\nElement not found!");
else{
printf("Element found!");
target = help;
if(val>f->data){
if(target->right && !target->left) {f->right = target->right; f = target->right;}
else {f->right = target->left; f = target->left;}
} else{
if(target->right && !target->left) {f->left = target->right; f = target->right;}
else {f->left = target->left; f = target->left;}
}
while(help ->right) help = help->right;
if(help->left) help = help->left;
f->right = help;
free(target);
}
}
One error that I spot is that if one were to delete the left node in the tree, then the last few lines might not work since they are not symmetric. So, let us say, the tree has root as 6 and left-child as 4 and right-child as 8. Now, you want to delete 4. So, after you have found the node in the first while clause, you would hit the else clause of "if (val > f->data){". At this point, f would point to 6, target and help would point to 4. Now, you are setting the left of f to right of target, so left of 6 would point to NULL and f itself would point to NULL. So, far so good! But, once you get into the while loop, since help has no right node, help would continue to point to 6. In the end, you make the right node of f (btw, f is already NULL at this point) to help and you would actually end up crashing!
while (help->right)
help = help->right;
if (help->left)
help = help->left;
f->right = help;
Another error is that you do not update the root pointer, incase you end up deleting the root node.
One simpler approach would be to divide this into three cases. I am providing code for all the three cases. Use a sample tree for each of these three cases and then debug/test it.
First, if the found node (which your file while loop is doing) has no child nodes, then delete it and set its parent to NULL and you are done. Here is a rough-first cut of the first case:
/* If the node has no children */
if (!help->left && !help->right) {
printf("Deleting a leaf node \n");
f = help->parent;
if (f) {
if (value > f->value)
f->right = NULL;
else
f->left = NULL;
}
/* If deleting the root itself */
if (f->value == root) {
root = NULL;
}
free(help);
return 0;
}
Second, if the found node has only child (left or right), then splice it out and the child of the found node becomes hte child of the parent node. Here is the second case:
/* If the node has only one child node */
if ((help->left && !help->right)
|| (!help->left && help->right)) {
printf("Deleting a node with only one child \n");
f = help->parent;
if (help->left) {
child_node = help->left;
} else {
child_node = help->right;
}
if (f) {
if (value > f->value) {
f->right = child_node;
} else {
f->left = child_node;
}
} else {
/* This must be the root */
root = child_node;
}
free(help);
return 0;
}
The third case is tricky -- here the found node has two child nodes. In this case, you need to find the successor of the node and then replace the value of the found node with the successor node and then delete the successor node. Here is the third case:
/* If the node has both children */
if (help->left && help->right) {
successor_found = find_successor(help, help->data);
printf("Deleting a node with both children \n");
if (successor_found) {
successor_value = successor_found->value;
del(successor_found->value);
help->value = successor_value;
}
return 0;
}
And, here is the code to find successor:
binary_node_t *node find_successor(binary_node_t *node, int value) {
binary_node_t *node_found;
if (!node) {return NULL; }
node_found = node;
old_data = node->data;
/* If the node has a right sub-tree, get the min from the right sub-tree */
if (node->right != NULL) {
node = node->right;
while (node) {
node_found = node;
node = node->left;
}
return node_found;
}
/* If no right sub-tree, get the min from one of its ancestors */
while (node && node->data <= old_data) {
node = node->parent;
}
return (node);
}
typedef struct xxx {
struct xxx *left;
struct xxx *right;
int data;
} ;
#define NULL (void*)0
#define FREE(p) (void)(p)
void treeDeleteNode1 (struct xxx **tree, int data)
{
struct xxx *del,*sub;
/* Find the place where node should be */
for ( ; del = *tree; tree = (data < del->data) ? &del->left : &del->right ) {
if (del->data == data) break;
}
/* not found: nothing to do */
if ( !*tree) return;
/* When we get here, `*tree` points to the pointer that points to the node_to_be_deleted
** If any of its subtrees is NULL, the other will become the new root
** ,replacing the deleted node..
*/
if ( !del->left) { *tree = del->right; FREE(del); return; }
if ( !del->right) { *tree = del->left; FREE(del); return; }
/* Both subtrees non-empty:
** Pick one (the left) subchain , save it, and set it to NULL */
sub = del->left;
del->left = NULL;
/* Find leftmost subtree of right subtree of 'tree' */
for (tree = &del->right; *tree; tree = &(*tree)->left) {;}
/* and put the remainder there */
*tree = sub;
FREE(del);
}
To be called like:
...
struct xxx *root;
...
treeDeleteNode1( &root, 42);
...

Problem with pointers in binary search tree deletion

I am trying to implement binary search tree operations and got stuck at deletion.
11
/ \
10 14
Using inorder traversal as representation of tree initially output is 10 11 14.
Deleting node 10, output expected is 11 14 but I am getting 0 11 14.
Deleting node 14, output expected is just 11 but I am getting 0 11 67837.
Please explain why I am getting wrong output. I am not looking for any code :).
typedef struct _node{
int data;
struct _node *left;
struct _node *right;
} Node;
Node* bstree_search(Node *root, int key)
{
if(root == NULL){
return root;
}
// Based on binary search relation, key can be found in either left,
// right, or root.
if(key > root->data)
return bstree_search(root->right, key);
else if(key < root->data)
return bstree_search(root->left, key);
else
return root;
}
void bstree_insert(Node **adroot, int value)
{
// since address of address(root is itself address) is passed we can change root.
if(*adroot == NULL){
*adroot = malloc(sizeof(**adroot));
(*adroot)->data = value;
(*adroot)->right = (*adroot)->left = NULL;
return;
}
if(value > (*adroot)->data)
bstree_insert(&(*adroot)->right, value);
else
bstree_insert(&(*adroot)->left, value);
}
void bstree_inorder_walk(Node *root)
{
if(root != NULL){
bstree_inorder_walk(root->left);
printf("%d ",root->data);
bstree_inorder_walk(root->right);
}
}
void bstree_delete(Node **adnode)
{
//Node with no children or only one child
Node *node, *temp;
node = temp = *adnode;
if((*adnode)->right == NULL || (*adnode)->left == NULL){
if((*adnode)->right == NULL){
*adnode = (*adnode)->left;
}else{
*adnode = (*adnode)->right;
}
}else{ // Node with two children
}
free(temp);
}
int main()
{
Node *root = NULL;
Node *needle = NULL;
int i,elems[] = {11,10,14};
for(i = 0; i < 3; ++i)
bstree_insert(&root,elems[i]);
bstree_inorder_walk(root);
printf("\n");
needle = bstree_search(root, 10);
bstree_delete(&needle);
bstree_inorder_walk(root);
printf("\n");
needle = bstree_search(root, 14);
bstree_delete(&needle);
bstree_inorder_walk(root);
printf("\n");
}
Please explain why I am getting wrong
output.
Your delete function must also change the parent of the deleted Node. For example, when you delete the node holding 10, you must set the root Node's left child to NULL. Since you don't do this, when you later traverse the tree, you print out data that has already been freed.
I did not look at any code other than delete, so I can't make any guarantees about it working once this change is made.
You're getting wrong output because your deletion code is buggy (okay, maybe that's stating the obvious).
To delete from a binary search tree, you first find the node to be deleted. If it's a leaf node, you set the pointer to it in its parent node to NULL, and free the node. If it's not a leaf node, you take one of two leaf nodes (either the left-most child in the right sub-tree, or the right-most child in the left sub-tree) and insert that in place of the node you need to delete, set the pointer to that node in its previous parent to NULL, and delete the node you've now "spliced out" of the tree.
A couple of things really quick,
first when you allocate the node, you really should be doing the malloc on the sizeof the type (ie Node).
Second, if you have 2 children it looks like you are not really deleting the node and rebuilding the search tree by promoting one of the children.
Other people have already got you other obvious errors.

Resources