Its been done to death, but the particular prompt (see Assignment 4) guiding me has strict+simple requirements that lead to a nice reduced environment to critique poor form. The project implements a heap-based binary tree structure with insert, find, and remove functions.
Both the binary tree nodes' IDs as well as data are int
. Children are called left
and right
.
The project is split into 3 files, user.c
, bintree.c
, and bintree.h
. The header file is #include
'd in both the C scripts. user.c
is where all the interaction is done - see bintree.h
for the available functions as well as the binary tree node struct's definition.
I will leave the original instructor comments in for clarity (the boilerplate can be obtained from the link above).
user.c
#include <stdio.h> #include "bintree.h" #include <stdlib.h> int main() { /* Insert your test code here. Try inserting nodes then searching for them. When we grade, we will overwrite your main function with our own sequence of insertions and deletions to test your implementation. If you change the argument or return types of the binary tree functions, our grading code won't work! */ return 0; }
bintree.h
#ifndef BINTREE_H
#define BINTREE_H
// Node structure should have an int node_id, an int data, and pointers to left and right child nodes
typedef struct node {
int node_id;
int data;
struct node *left;
struct node *right;
} node;
///*** DO NOT CHANGE ANY FUNCTION DEFINITIONS ***///
// Declare the tree modification functions below...
void insert_node(int node_id, int data);
int find_node_data(int node_id);
void remove_node(int node_id);
void delete_tree();
#endif
bintree.c
// bintree.c interfaces with the node struct defined in bintree.h
// to create and manipulate a heap-based binary tree. All functions
// meant for interfacing by the user are defined in the header file.
//
// There is only ONE root (ie one tree), so there is only
// one node of the tree with no parents.
//
// bintree.c assumes binary tree was built USING THE INSERT_NODE ALGORITHM!
// It is *theoretically* possible to construct binary trees obeying
// the (left,right)~(<,>)node_id equivalence (ie left-child has smaller node id than node_id,
// right-child has larger node id than node_id) that lose the 'useful' property
// of these trees: the extremely fast find_node_data algorithm,
// relying on all nodes to the right of the root having
// a larger node_id, and all nodes to the left having a smaller node_id.
// That is, a binary tree could theoretically locally preserve the
// equivalence property without it globally holding. Therefore, bintree.c
// cannot be handed a root pointer from another heap-based binary tree interface
// and be expected to work, unless the tree also globally preserves the equivalence.
#include <stddef.h> // NULL def
#include <stdlib.h> // malloc
#include <stdio.h> // printf/scanf
#include "bintree.h"
#define ERROR_VAL -999999 // junk data value for find_node_data
///*** DO NOT CHANGE ANY FUNCTION DEFINITIONS ***///
// Initialize tree
node *root = NULL;
// Insert a new node into the binary tree with node_id and data
void insert_node(int node_id, int data) {
// reserve memory for node
node *newNode = (node *)malloc(sizeof(node));
// initialize node values
newNode->node_id = node_id;
newNode->data = data;
newNode->left = NULL;
newNode->right = NULL;
// if tree empty, use as root
if (root == NULL) {
root = newNode;
return;
}
// if tree not empty, traverse tree
// find parent and attach child
node *tmp = root;
while (1) {
// go to right-child if higher id
if (node_id > tmp->node_id) {
// if no right-child, attach child
if (tmp->right == NULL) {
tmp->right = newNode;
return;
} else { tmp = tmp->right; }
// go to left-child if lower id
} else if (node_id < tmp->node_id) {
// if no left-child, attach child
if (tmp->left == NULL) {
tmp->left = newNode;
return;
} else { tmp = tmp->left; }
// if ids are equal, ask to replace data (keeps children)
} else if (node_id == tmp->node_id) {
char ans;
while(1) {
printf("node id occupied. replace data? (y)/(n): ");
scanf("%c", &ans);
if ( ans == 'y') {
tmp->data = data;
return;
} else if ( ans != 'n') {
printf("invalid answer.\n");
continue;
}
}
}
}
}
// Returns pointer to node's parent given node id
node *find_parent(int node_id) {
if (root == NULL) { printf("root is NULL\n"); return NULL; }
else if (root->node_id == node_id) { return NULL; } // root has no parent
node *tmp = root;
node *parent = NULL;
while (1) {
if (tmp == NULL) {
printf("no node found with node_id %d\n", node_id);
return NULL;
}
// if node_id higher, go right; if lower, go left; if match, return
else if ( (node_id > tmp->node_id) && (tmp->right != NULL) ) {
parent = tmp;
tmp = tmp->right;
} else if ( (node_id < tmp->node_id) && (tmp->left != NULL) ) {
parent = tmp;
tmp = tmp->left;
} else if (node_id == tmp->node_id) {
// check to ensure is actually parent before return
if ( (parent->left == tmp) || (parent->right == tmp) ) { return parent; }
} else {
// catch no matches
tmp = NULL;
}
}
}
// Returns pointer to node given node_id
node *find_node(int node_id) {
if (root == NULL) { return NULL; } // no nodes at all
else if (root->node_id == node_id) { return root; } // root has no parent
node *tmp = find_parent(node_id);
if (tmp == NULL) { return NULL; }
// parent has node w/ node_id as left- or right-child
else if ( (tmp->left != NULL) && ((tmp->left)->node_id == node_id) ) { return tmp->left; }
else if (tmp->right != NULL) { return tmp->right; }
// catch no matches
return NULL;
}
// Find the node with node_id, and return its data
int find_node_data(int node_id) {
node *tmp = find_node(node_id);
if (tmp == NULL) { return ERROR_VAL; }
return tmp->data;
}
/* OPTIONAL: Challenge yourself w/ deletion if you want
Find and remove a node in the binary tree with node_id.
Children nodes are fixed appropriately. */
void remove_node(int node_id) {
node *parent = find_parent(node_id);
node *tmp = find_node(node_id);
if (tmp == NULL) {
printf("remove_node failed, node DNE\n");
return;
}
// • 'left-child' and 'right-child' refer to the
// children of the node being deleted.
// • 'appropriate leg' refers to whichever (left or right)
// side of the parent the node being deleted is attached to.
//
// find the right-most descendent of the left-child (hence the highest node id less than node_id).
//
// - if no left-child, attach right-child to appropriate leg of tmp's parent.
//
// - otherwise, attach right-child to right-most descendent of left-child's right leg.
// attach left-child to appropriate leg of tmp's parent.
//
// free tmp and return
if (tmp->left == NULL) {
if (tmp->right != NULL) {
// catch root case
if (parent == NULL) {
root = tmp->right;
}
// otherwise attach right-child to appropriate leg of parent
else if (parent->left == tmp) {
parent->left = tmp->right;
} else {
parent->right = tmp->right;
}
}
} else {
// catch root case
if (parent == NULL) {
root = tmp->left;
}
// otherwise attach left-child to appropriate leg of parent
else if (parent->left == tmp) {
parent->left = tmp->left;
} else {
parent->right = tmp->left;
}
// and attach right-child to right-most descendent of left-child's right leg
node *next = tmp->left;
while (next->right != NULL) {
next=next->right;
}
next->right = tmp->right;
}
free(tmp);
return;
}
// For internal use by delete_tree,
// recursively frees nodes of tree
void delete_node(node *tmp) {
if (tmp->left != NULL) {
delete_node(tmp->left);
}
if (tmp->right != NULL) {
delete_node(tmp->right);
}
free(tmp);
}
// There is only one tree and it starts where 'root' points,
// delete_tree unallocates all memory for every node connected
// to root, including root.
void delete_tree() {
delete_node(root);
}
I would have liked to change find_node_data
to be cast to a pointer so that NULL
could be used to represent no result, but I wanted to stick to the grading scheme.
This is a rudimentary assignment, so any discussion/critique is appreciated! How are my comments? Is the code concise, clear, efficient, and consistent (are there shorter algorithms, are my control structures convoluted, are my logic checks sound, is it readable, correct usage of macros)? Within the bounds of the guidelines, are there any gaps in my implementation?
node
pointers 'left
' and 'right
'? or that the first comparison can establish whether the tree is a left or right BST, assuming preserved parity? \$\endgroup\$