deletion of binary tree using new method - c

Actually I was stuck while implementing the deletion of a tree . I deleted the leaf nodes using free() and now the parent would become leaf nodes , and delete those nodes too using recursion . But the problem is leaf node is not getting deleted actually , its still there . And also the method i followed in deletion of tree is as follows
void deleteTree(struct node *root)
{
if(root->left == NULL && root->right == NULL)
{
free(root);
}
else
{
if(root->left != NULL)
deleteTree(root->left);
if(root->right != NULL)
deleteTree(root->right);
}
}
This method deleted only the leaf nodes , and the corresponding parent nodes were not deleted . After debugging in XCode I found out that the leaf nodes were not deleted , They were still there.
So, why this happening ?

void deleteTree(struct node *root)
{
if (root) {
deleteTree(root->left);
deleteTree(root->right);
free(root);
}
}

Your deleteTree() is not good for delete nodes in tree. What you are doing is preorder traverse in binary tree recursively. The node you delete/free from tree will be refereed again when function returns, and hence you are accessing a free node that will cause Undefined behavior at run-time.
Do not access freed memory:
According to the C Standard, the behavior of a program that uses the value of a pointer that refers to space deallocated by a call to the free() or realloc() function is undefined.
You should choose Post-order to free tree's node. because in post-order traversal you don't access a node that is processed (deleted/free). And your deleteTree() function should be something like as follows:
void deleteTree(struct node *root){
if(root == NULL) return;
deleteTree(root->left);
deleteTree(root->right);
free(root); // node processed and return
}

Since you do not free the parent nodes on any level, all levels except for leaves remain. What you need to do is add free(root) after the if statement, at the end of the else statement.

Related

How to correctly deallocate structure from memory

I have a dynamic data structure which looks like this:
struct tree_node {
int y;
int x;
struct tree_node *left;
struct tree_node *right;
struct tree_node *parent;
};
The structure is a node of a binary tree, with the addition that each node also points to its parent. Now, using the classical method of adding nodes to the binary tree using malloc() I can populate the binary tree easily. However, I am having trouble deallocating the binary tree from memory.
Usually, to remove nodes from the binary tree you perform a post-order traversal and then free each node like this:
void deleteTree(struct tree_node* node)
{
if (node == NULL) return;
deleteTree(node->left);
deleteTree(node->right);
printf("Deleting node with values [%d][%d]\n", node->y , node-> x);
free(node -> left);
free(node -> right);
free(node -> parent);
free(node);
printf("\nNode deleted");
}
However, when I run the above function it does not deallocate the binary tree from memory. When I run the function, it deallocates one leaf and then when it tries to delete the next node it gets stuck in an endless loop and my computer either crashes or the program exits with a non-descriptive error.
The output in the terminal is the following:
Deleting node with values [11][4]
Node deleted
Deleting node with values [7739840][0]
So the terminal shows that it deletes the first leaf, and then it tries to fetch the values from the next node but it cannot (which is why it displays 7739840). Then it gets stuck in an endless loop since it does not print "Node deleted".
How can I correctly deallocate the memory? Does it have to do with the way my node is built?
The correct way of deallocating all the nodes from your tree structure looks like this:
void deleteTree(struct tree_node* node)
{
if (node == NULL) return;
deleteTree(node->left);
deleteTree(node->right);
free(node);
}
You're deallocating nodes multiple times.
When you delete a given node, after deleteTree(node->left) is called the left node has already been freed, so it doesn't need to be freed again. So remove free(node->left).
The current node has also been freed because free(node->parent) was called in that function, so any further reads of node access freed memory. So remove free(node->parent).
And similarly to the call to deleteTree(node->left), calling deleteTree(node->right) already frees the right node, so remove the call to free(node->right).
So now you're left with:
void deleteTree(struct tree_node* node)
{
if (node == NULL) return;
deleteTree(node->left);
deleteTree(node->right);
printf("Deleting node with values [%d][%d]\n", node->y , node-> x);
free(node);
printf("\nNode deleted");
}
In short, each node is responsible for cleaning itself up.
This is happening because you are freeing the same node multiple times.
To delete a binary search tree you have to do something like this.
void bst_destroy(BSTNode node) {
if (node == NULL) {
return;
}
bst_destroy(node->left);
bst_destroy(node->right);
free(node);
}
Basically to delete a binary search tree you have to delete the leaves of the tree one by one so you don't end up deleting parent nodes you might have to visit again (which you will have to do if you are using recursion).

Delete function binary search tree BST with parent passing the tree pointer as argument

I am having a very difficult time understanding what my book is wanting me to learn. I am currently re-vising some algorithms and data structure material I studied quite some years ago. One chapter is about Binary Search Tree and their application, etc. The book talks about a BST (binary search tree) as a tree with nodes that has the following qualities: int data, node left, node right, node parent. I understand this and I have implemented a search function.
Now to the issue: How do I create a delete function when the arguments I have are N* delete(Tree* tree, N* delete_node);?
There are lots of recursive functions but they rely on the fact that we recursively pass a pointer of a node and a key value. This is not what the exercise in the book is looking for. An example of a method that I have tried and works is: source code for C Program for Insertion and Deletion in Binary Search Tree. Two other great sources that fails to meet these criteria are codezclub and geeksforgeeks. They solve the "delete" function but their function have other parameters in their function. My book specifies
A modifying operation that, given a pointer delete_node to an
element in the BST, tree, deletes delete_node from the BST tree and returns its pointer.
Function declaration: N* delete(Tree* tree, N* delete_node);
Anybody got any ideas? Also minimally reproducible examples are given in the sources above with the caviat that the structs are missing "parent" node. Below are my definitions for the tree and the node:
typedef struct N {
int data;
struct N* left;
struct N* right;
struct N* parent;
} N;
typedef struct Tree {
N* root;
} Tree;
You can use CLRS as a reference for understanding Binary search tree data structures and operations on it. Let's implement delete procedure first and then explain necessary parts.
We need 2 procedures, SUCCESSOR and transplant other than delete itself to make it complete. required procedures are implement before delete.
In order to move subtrees around within the binary search tree, we define a
subroutine transplant, which replaces one subtree as a child of its parent with
another subtree. When transplant replaces the subtree rooted at node u with
the subtree rooted at node v, node u’s parent becomes node v’s parent, and u’s
parent ends up having v as its appropriate child.
void transplant(Tree** root, Tree* u, Tree* v) {
if(u == NULL)
return;
else if(u->parent == NULL)
*root = v;
else if(u->parent->left == u)
u->parent->left = v;
else
u->parent->right = v;
if(v != NULL)
v->parent = u->parent;
}
We need to implement Tree* SUCCESSOR(Tree* T) procedure which returns the node where node->key is the next larger element greater than T->key, if it exists. otherwise, returns NULL. We need SUCCESSOR when delete_node has 2 children. It'll be replaced with the SUCCESSOR(delete_node).
Tree* SUCCESSOR(Tree* T) {
if(T == NULL)
return T; // Since T==NULL, it's equivalent to return NULL
else if(T->left != NULL) {
while(T->left != NULL)
T=T->left;
return T;
}
else {
while(T->parent != NULL && T->parent->right == T)
T = T->parent;
return T->parent;
}
}
We can all needed to implement delete procedure.
Tree* delete(Tree* root, Tree* delete_node) {
if(delete_node == NULL)
return root;
if(delete_node->left== NULL)
transplant(&root, delete_node, delete_node->right);
else if(delete_node->right == NULL)
transplant(&root, delete_node, delete_node->left);
else {
Tree* succ = SUCCESSOR(delete_node);
if(delete_node->right != succ) {
transplant(&root, succ , succ ->right);
succ->right = delete_node->right;
succ->right->parent = succ;
}
transplant(&root, delete_node, succ);
succ->left = delete_node->left;
succ->left->parent = succ;
}
return root;
}
Here's how delete procedure works:
The procedure for deleting a given node delete_node from a binary search tree root takes as arguments pointers to root and delete_node. It organizes its cases a bit differently from the three mentioned above.
If delete_node has no left child (first if statement do this part), then we replace delete_node by its right child, which may or may not be NULL. When delete_node's right child is NULL, this case deals with the situation in which delete_node has no children. When delete_node's right child is non-NULL, this case handles the situation in which delete_node has just one child, which is its right child.
If delete_node has just one child, which is its left child (else if part handles this case), then we replace delete_node by its left child.
Otherwise, delete_node has both a left and a right child. We find delete_node’s successor succ, which lies in delete_node’s right subtree and has no left child (If it has a left, child, then it's not the smallest number greater than delete_node->key. We get contradiction). We want to splice succ out of its current location and have it replace delete_node in the tree.
If succ is delete_node's right child, then we replace delete_node by succ.
Otherwise, succ lies within delete_node’s right subtree but is not delete_node's right child. In this case, we first replace succ by its own right child, and then we replace delete_node by succ.
Since both quotes are taken fromCLRS, I encourage you to review it in case of having problem in understanding why it works.
Of course you can do deletion without recursive function.
typedef struct N {
int data;
struct N* left;
struct N* right;
struct N* parent;
} N;
typedef struct Tree {
N* root;
} Tree;
N* delete(Tree* tree, N* delete_node);
Lets start from this definition. Well, have to say the delete function is uncommon, given the fact that in most cases we are using:
void delete(Tree* tree, int element);
Because we don't know whether the element exists or not (it need to be determined by contains() or find() function when we execute the deletion), and we don't need any return value most likely.
Anyway lets put it your way.
Deleting from BST is complicated and you should proceed with three basic situation:
The node you want to delete is leaf, i.e. it has no child;
The node has one child, either left or right;
The node have both right and left child.
These situations are different in processing, and for simplicity we will omit some implementation details and safety checks, but don't forget them in production code.
In situation 1, you simply free the memory, and change the pointer in its parent to NULL:
if (delete_node->parent->data < delete_node->data)
delete_node->parent->right = NULL; // WARNING: corner case
else
delete_node->parent->left= NULL; // WARNING: corner case
free(delete_node);
Situation 2 is pretty like Situation 1; instead of NULL, we need to change the pointer in parent node to its only child, then we free the node. The logic is similar and we will skip the code.
Situation 3 is a little more complicated. Generally we have two ways and both of them works; either we replace the node's data with largest (in BST we can say "rightest") data of its left subtree, or we replace the node's data with smallest/leftest data of its right subtree. After the replacing, we remove that node instead. Let's examine the second approach, i.e. replace the node's data with smallest data of its right subtree. The first approach has very similar logic.
For this approach, we need this function implemented:
N* findMin(N* root_of_subtree);
It will take root_of_subtree as input (search will start from this node), and returns the pointer to the desired, smallest node.
Assume we have found the desired smallest node in the right subtree of delete_node:
N* smallestNodeOfRightSubtree = findMin(delete_node->right);
delete_node->data = smallestNodeOfRightSubtree->data;
smallestNodeOfRightSubtree->parent->left = smallestNodeOfRightSubtree->right; // WARNING, corner case
free(smallestNodeOfRightSubtree);
We directly changed parent->left here, since in general case (corner case 2 explains what is not general), that node has two charateristics:
It has no left child.
It must be on the left subtree of its parents.
But, it is not always general, we still have some corner cases need to cover:
What if delete_node is root node? It has no parent, and code in Situation 1 will be undefined behavior.
What if the smallest node of the right subtree of delete_node is delete_node->right, i.e. the right subtree only have 1 node. In this case, we cannot change smallestNodeOfRightSubtree->parent->left; instead, we should change parent->right.
THAT'S IT. Now you know the logics, and some corner cases. GL & HF.

Traversal in tree/linked list structure

It is kind of like a hybrid tree/linked list structure. Here's how I defined the structure
struct node {
nodeP sibling;
nodeP child;
nodeP parent;
char name[100];
};
a node has a child, that is connected to a linked list. Other elements on the linked list may have their own child that is connected to the linked list or by itself
Now to my question, how would I traverse this structure to search and print the path to a specific node.
Any suggestions would be appreciated.
Thanks
Update
Here is printPath function:
//node -> root of the tree
//current = this is the node that i need to print path to
void printPath(nodeP node, nodeP current){
if (node != NULL){
if ((strcmp(node->name, current->name) == 0))
{
printf("%s/", node->name);
return; //it returns to the previous recursive call and then continues until the node is null
}
printPath(root->childDir, currentDir);
printPath(root->sibling, currentDir);
printf(" %s/", node->name);
}
}
My problem is getting out of the recursive call, once if finish printing path to the current node.
The node structure presented in the question is the same as for a regular binary tree which is using names child and sibling instead of the traditional left and right.
If you want to print a path to the desired node, you should print the root values of each subtree containing that node. So here is the pseudocode:
function printPath(root, searchName)
if root is null
return false
if root.name is searchName or
printPath(root.child, searchName) or
printPath(root.sibling, searchName) then
print root.name
return true
else
return false
Here, if root is null, it is not containing the desired node, so we return false for this path printing nothing. But if it's name is the one that we want, or one of it's subtrees is containing what we want, we print the name and returning true. This way you will get the path printed in the order from the leaf to root.

Binary Trees - difference between insert and mirror?

What manipulations are we doing in insert/delete that we aren't in mirror?
By mirror I'm referring to switching the left and right child of every single node in the tree.
The reason I'm asking is that when it comes to an insert, if you don't save the root returned by the function, it leads to errors. The same is not true of mirror. I assumed mirror would have the same requirement (returning the root node), but that is not the case. Why is that?
Here's my code if it's relevant (the insert is implemented for a BST):
Insert:
Node *insert(Node *root,int val)
{
Node *newNode=NULL;
if(root==NULL)
{
newNode=(Node*) malloc(sizeof(Node));
newNode->value=val;
newNode->left=NULL;
newNode->right=NULL;
return newNode;
}
if(root->value>val)
{
root->left=insert(root->left,val);
}
else
{
root->right=insert(root->right,val);
}
return root;
}
Mirror:
void mirror(Node *root)
{
Node *temp_left;
if(root==NULL)
return;
temp_left=root->left;
root->left=root->right;
root->right=temp_left;
mirror(root->left);
mirror(root->right);
}
Notice the line:
root->left=insert(root->left,val);
Here you are assigning the result of insert() to root->left. You need insert() to return a node* pointer here, or else we would have no idea where malloc placed the new node in memory, and so you wouldn't be able to add the node to the tree.
On the other hand, mirror only traverses the tree recursively and swaps some already-existing pointers. You need never assign the result mirror() to a variable.

Two questions on Binary Search Trees

I have two questions about binary search trees - one about the code I am writing and the other more about theory. First of all, the code that I wrote below works fine except for when I try to display the case where the BST is actually empty; it gives me a segmentation fault when I would like it to print out an error message. I feel like I may have gotten my pointers mixed up at some point and so that is giving me the error. Here is my code:
#include <stdio.h>
#include <stdlib.h>
struct Node {
char *word;
struct Node *left;
struct Node *right;
};
/* Function that creates a new node as a leaf; that is, its */
/* left and right children are NULL. */
/* Require: node -> word != NULL */
struct Node * createNode (char *word) {
struct Node *item = malloc(sizeof(struct Node));
item -> word = word;
item -> left = NULL;
item -> right = NULL;
return item;
}
/* Recursive function that inserts a node into proper position by */
/* searching through tree. */
struct Node * insertNode (struct Node *root, char *word) {
// If tree is empty, node becomes root
if(root == NULL)
return createNode(word);
else {
if(strcmp(word, root -> word) < 0) {
root -> left = insertNode(root -> left, word);
return root;
} else if(strcmp(word, root -> word) > 0) {
root -> right = insertNode(root -> right, word);
return root;
} else if(strcmp(word, root -> word) == 0)
printf("Word is already present in the tree.");
}
}
/* Function to display Binary Search Tree via inorder traversal. */
/* -- prints entire left subtree, then root, then right subtree */
void display (struct Node *root) {
if(root -> word == NULL)
printf("Tree is empty.");
if(root -> left != NULL)
display(root -> left);
printf("%s\n", root -> word);
if(root -> right != NULL)
display(root -> right);
}
void main () {
struct Node root;
struct Node *rootP = &root;
root = createNode("
}
The second question involves populating the binary tree. I want to use a small dictionary which will of course be in alphabetical order. If I feed those words into the binary tree starting with, say, "aardvark," won't the tree end up incredibly skewed as all subsequent words will come after the first alphabetically and thus always be right children? I'm afraid that I will end up with a tree that is incredibly off balance! Is there some method I can use to shuffle the tree around as I am populating it?
Thank you for taking the time to read this!
In your display function, you first need to test whether root == null before testing whether root -> word == null. That should fix the seg fault.
Regarding the theory question: the answer is that yes, the tree will end up incredibly skewed. That's what balanced binary trees are all about.
if(root -> word == NULL)
printf("Tree is empty.");
Your problem lies here. What happens if root itself is null? Double check that pointer before de-referencing it.
Yes, if you insert items in sorted order (or relatively sorted), you'll get a skewed tree. Look into algorithms on rotations for nodes in balanced binary trees.
Regarding your second question, others have already mentioned looking into balancing your binary tree. However, as an alternative, if your input is already known to be sorted, then it is more appropriate instead to use a linear array with a binary search to find items of interest, rather than a binary tree. A binary search of a sorted array has the same time complexity as a search in a balanced binary tree.

Resources