An interactive BST that lets you insert, delete, and search nodes while rendering the tree sideways in your terminal with Unicode branch connectors and ANSI colour highlights.
Written in pure C with no external dependencies.
- Insert, delete, and search with live visual feedback
- Sideways tree rendering: right subtree at top, root in middle, left subtree at bottom
- Unicode box-drawing branch connectors (
┌────,└────,│) - ANSI colour coding: root in cyan, found/highlighted node in green
- Inorder traversal printed below the tree on every redraw
- Duplicate insertion silently ignored
- Two-child delete uses the in-order successor strategy
- Node counter displayed after every operation
The program maintains a binary search tree in memory. After every command, it redraws the entire tree sideways using a recursive printer that visits right subtree first (top of screen), then the node itself, then the left subtree (bottom of screen). Unicode box-drawing characters connect parent-child lines, and ANSI escape codes apply colour to the root and any highlighted search result.
Every element in the tree is a Node holding an integer value and two child pointers:
typedefstructNode {
intval;
structNode*left, *right;
} Node;A global root pointer starts as NULL. A global highlight integer tracks the most recently searched value so the printer can colour it green:
staticNode*root=NULL;
staticintn_nodes=0;
staticinthighlight=-999999;mknode wraps calloc — zero-initialised memory means both child pointers begin as NULL automatically:
staticNode*mknode(intv) {
Node*n=calloc(1, sizeof*n);
if (!n) { perror("calloc"); exit(1); }
n->val=v;
returnn;
}free_tree does a post-order traversal to deallocate every node:
staticvoidfree_tree(Node*n) {
if (!n) return;
free_tree(n->left);
free_tree(n->right);
free(n);
}Classic recursive descent. If the slot is empty, place a new node; otherwise go left for smaller values and right for larger ones. Duplicates are ignored — the inserted flag tells the caller whether anything changed:
staticNode*bst_insert(Node*n, intv, int*inserted) {
if (!n) { *inserted=1; n_nodes++; returnmknode(v); }
if (v<n->val) n->left=bst_insert(n->left, v, inserted);
elseif (v>n->val) n->right=bst_insert(n->right, v, inserted);
returnn;
}Usage: root = bst_insert(root, 42, &flag);
Deletion handles three structural cases:
- Leaf (no children): free the node, return NULL
- One child: free the node, return the surviving child
- Two children: replace the node's value with its in-order successor (leftmost node in the right subtree), then recursively delete the successor from the right subtree
staticNode*min_right(Node*n) { while (n->left) n=n->left; returnn; }
staticNode*bst_delete(Node*n, intv, int*deleted) {
if (!n) returnNULL;
if (v<n->val) n->left=bst_delete(n->left, v, deleted);
elseif (v>n->val) n->right=bst_delete(n->right, v, deleted);
else {
*deleted=1; n_nodes--;
if (!n->left) { Node*r=n->right; free(n); returnr; }
if (!n->right) { Node*l=n->left; free(n); returnl; }
Node*s=min_right(n->right);
n->val=s->val;
intdummy=0;
n->right=bst_delete(n->right, s->val, &dummy);
n_nodes++; /* compensate: inner delete decremented again */
}
returnn;
}Recursive boolean descent — returns 1 if the value exists in the tree, 0 otherwise:
staticintbst_search(Node*n, intv) {
if (!n) return0;
if (v==n->val) return1;
returnv<n->val ? bst_search(n->left, v) : bst_search(n->right, v);
}On a successful search, the global highlight is set so the next redraw colours that node green.
The printer uses a reverse pre-order traversal (right → root → left) to render the tree sideways. Since the terminal scrolls top-to-bottom, visiting the right subtree first places it at the top, the root in the middle, and the left subtree at the bottom:
┌──── 90 ← right child (printed first)
│
┌──── 50 ← root
│
│ ┌──── 30
│ │
└──── 20
│
└──── 10 ← left child (printed last)
The prefix string accumulates vertical guide lines (│ ) for each ancestor level:
staticvoidprint_tree(constNode*n,
char*prefix, size_tpfx_cap,
intis_right_child, intdepth)
{
if (!n) return;
/* recurse right (upper half of screen) */
{
char*p2=malloc(pfx_cap);
memcpy(p2, prefix, strlen(prefix) +1);
strncat(p2, is_right_child ? "│ " : " ", pfx_cap-strlen(p2) -1);
print_tree(n->right, p2, pfx_cap, 1, depth+1);
free(p2);
}
/* print this node */constchar*branch= (depth==0) ? "" :
(is_right_child) ? "┌──── " : "└──── ";
constchar*col= (n->val==highlight) ? GREEN :
(depth==0) ? CYAN : "";
printf("%s"DIM"%s"RST"%s%d"RST"\n", prefix, branch, col, n->val);
/* recurse left (lower half of screen) */
{
char*p2=malloc(pfx_cap);
memcpy(p2, prefix, strlen(prefix) +1);
strncat(p2, is_right_child ? " " : "│ ", pfx_cap-strlen(p2) -1);
print_tree(n->left, p2, pfx_cap, 0, depth+1);
free(p2);
}
}Key insight: ┌──── marks right children (they appear above the parent) and └──── marks left children (they appear below). The dimmed │ characters form continuous vertical lines connecting ancestors.
ANSI escape codes are plain string literals prepended to output. RST resets all attributes back to terminal defaults:
#defineRST "\033[0m"
#defineBOLD "\033[1m"
#defineDIM "\033[2m"
#defineCYAN "\033[1;36m"
#defineGREEN "\033[1;32m"
#defineYELLOW "\033[1;33m"
#defineRED "\033[1;31m"To colour a number green: printf("%s%d%s", GREEN, val, RST);
Inorder (left → root → right) visits nodes in sorted order. This is printed as a flat list below the tree diagram after every redraw:
staticvoidinorder(constNode*n, int*first) {
if (!n) return;
inorder(n->left, first);
if (!*first) printf(DIM" → "RST);
printf("%s%d"RST,
(n->val==highlight) ? GREEN : YELLOW, n->val);
*first=0;
inorder(n->right, first);
}The first flag suppresses the arrow before the initial element. Highlighted values appear in green; all others in yellow.
The main loop reads a line, parses the command word, dispatches to the appropriate handler, then calls redraw() to refresh the tree display:
while (1) {
printf(CYAN"bst> "RST);
fflush(stdout);
if (!fgets(line, sizeofline, stdin)) break;
trim(line);
if (strncmp(line, "insert", 6) ==0) {
/* parse value, call bst_insert, print result */
} elseif (strncmp(line, "delete", 6) ==0) {
/* parse value, call bst_delete, print result */
} elseif (strncmp(line, "search", 6) ==0) {
/* parse value, call bst_search, set highlight */
} elseif (strcmp(line, "inorder") ==0) {
/* print sorted traversal without redrawing tree */
} elseif (strcmp(line, "clear") ==0) {
/* free all nodes, reset root to NULL */
} elseif (strcmp(line, "quit") ==0||strcmp(line, "q") ==0) {
/* free tree, exit */
}
highlight=-999999; /* clear search highlight */redraw();
}Every command path (except inorder and quit) falls through to redraw(), which prints the tree diagram, the inorder list, and the node count.
redraw() ties everything together — it prints the tree via print_tree, then the inorder list via inorder, then the node count:
staticvoidredraw(void) {
printf("\n");
if (!root) {
printf(DIM" (tree is empty — try: insert 50)\n"RST);
} else {
charprefix[1024] =" ";
print_tree(root, prefix, sizeofprefix, 0, 0);
}
printf("\n"BOLD" Inorder: "RST);
if (root) {
intfirst=1;
inorder(root, &first);
} else {
printf(DIM"(none)"RST);
}
printf(" "DIM"[%d node%s]"RST"\n\n",
n_nodes, n_nodes==1 ? "" : "s");
}gcc bintree.c -o bintree
No flags or external libraries needed.
./bintree
| Command | Action |
|---|---|
insert N | Insert integer N into the tree |
delete N | Delete integer N from the tree |
search N | Highlight N in green on the next redraw |
inorder | Print the sorted traversal (no tree redraw) |
clear | Remove every node from the tree |
quit / q | Free all memory and exit |
Press Ctrl+D to quit (EOF).
Edit the colour macros at the top of bintree.c to change the theme:
CYAN— root node colourGREEN— highlighted / searched node colourYELLOW— inorder traversal textRED— error messagesDIM— branch lines and metadata
The highlight sentinel -999999 (reset value for highlight) can be changed if your tree contains values near that range.
- Binary search tree property: left < root < right
- Recursive BST insertion with duplicate rejection
- Recursive BST deletion with three structural cases
- In-order successor selection for two-child deletion
- Recursive tree traversal (inorder, preorder, postorder)
- Sideways terminal rendering with dynamic prefix strings
- Unicode box-drawing characters for tree branches
- ANSI escape-code colouring and text formatting
- Manual heap memory management with
calloc/free - REPL loop with string parsing and command dispatch
Standard C libraries only:
stdio.h—printf,fgets,sscanf,fflush,perrorstdlib.h—calloc,free,exitstring.h—strlen,strcmp,strncmp,memcpy,memmove,strncatctype.h—isspace
No external libraries or graphics engines required.