This course is designed to help learners master the core concepts of Data Structures and Algorithms (DSA) using the C programming language. Starting from the basics and advancing to complex topics like graphs, dynamic programming, and memory optimization, this course is ideal for students, job seekers, and aspiring developers. You’ll learn how to structure and manipulate data efficiently, solve real-world coding problems, and prepare for technical interviews at top companies. The content is structured step-by-step, combining theory with hands-on coding examples and practice problems to reinforce understanding. Whether you're preparing for university exams, campus placements, or competitive programming, this course provides a strong foundation in logic building, code efficiency, and problem-solving using C. Key Highlights: Covers all major DSA topics from beginner to advanced level 100+ coding examples with explanations Focus on time and space complexity optimization Designed for coding interviews, competitive exams, and CS fundamentals
Following our exploration of general binary trees and their traversals, we now arrive at a highly specialized and incredibly useful variant: the Binary Search Tree (BST). A BST is a binary tree with a special ordering property that makes operations like searching, insertion, and deletion highly efficient, typically achieving logarithmic time complexity on average. This property makes BSTs foundational for many data management and retrieval systems.
A Binary Search Tree is a node-based binary tree data structure that has the following properties:
This ordering property ensures that an Inorder traversal of a BST will always yield the nodes' keys in sorted (non-decreasing) order.
To search for a key in a BST, we start at the root and recursively move down the tree.
// Search operation in C for BST
struct Node* search(struct Node* root, int key) {
// Base Cases: root is null or key is present at root
if (root == NULL || root->data == key)
return root;
// Key is greater than root's data
if (root->data < key)
return search(root->right, key);
// Key is smaller than root's data
return search(root->left, key);
}
Time Complexity: The time complexity of searching is O(h), where h is the height of the tree. In the best case (balanced tree), h = O(log n). In the worst case (skewed tree), h = O(n).
To insert a new key, we follow the search path until we find a `NULL` pointer, which indicates the correct position for the new node.
// Insert operation in C for BST
struct Node* insert(struct Node* node, int key) {
// If the tree is empty, return a new node
if (node == NULL)
return createNode(key);
// Otherwise, recur down the tree
if (key < node->data)
node->left = insert(node->left, key);
else if (key > node->data)
node->right = insert(node->right, key);
// Else (key == node->data), handle duplicates as per requirement (often ignore or add to right)
return node;
}
Time Complexity: Similar to search, insertion takes O(h) time.
Deletion is the most complex operation, as there are three cases to consider:
Simply remove the node and set its parent's corresponding child pointer to `NULL`.
Replace the node with its child. The child takes the node's position, and the node is removed.
This is the most involved case. Find the inorder successor (smallest node in the right subtree) or the inorder predecessor (largest node in the left subtree) of the node to be deleted. Copy the value of the inorder successor (or predecessor) to the node, and then recursively delete the inorder successor (or predecessor) from its original position. The inorder successor will always have at most one right child (or no children), simplifying its deletion.
// Helper function to find the minimum value node in a BST
struct Node* minValueNode(struct Node* node) {
struct Node* current = node;
while (current && current->left != NULL)
current = current->left;
return current;
}
// Delete operation in C for BST
struct Node* deleteNode(struct Node* root, int key) {
// Base Case
if (root == NULL) return root;
// If the key to be deleted is smaller than the root's key,
// then it lies in left subtree
if (key < root->data)
root->left = deleteNode(root->left, key);
// If the key to be deleted is greater than the root's key,
// then it lies in right subtree
else if (key > root->data)
root->right = deleteNode(root->right, key);
// If key is same as root's key, then this is the node to be deleted
else {
// Node with only one child or no child
if (root->left == NULL) {
struct Node* temp = root->right;
free(root);
return temp;
} else if (root->right == NULL) {
struct Node* temp = root->left;
free(root);
return temp;
}
// Node with two children: Get the inorder successor (smallest in the right subtree)
struct Node* temp = minValueNode(root->right);
// Copy the inorder successor's content to this node
root->data = temp->data;
// Delete the inorder successor
root->right = deleteNode(root->right, temp->data);
}
return root;
}
Time Complexity: Like search and insert, deletion also takes O(h) time.
Advantages:
Disadvantages:
In this BST: 50 is the root. 30 < 50 (left child). 70 > 50 (right child). 20 < 30, 40 > 30, etc.
On a platform like UdaanPath, Binary Search Trees would be crucial for managing and quickly accessing ordered data:
Recognizing the vulnerability of basic BSTs to worst-case performance, the next logical step is to explore ways to maintain balance. In the upcoming chapter, we will dive into AVL Trees, the first self-balancing binary search tree, which automatically rebalances itself after insertions and deletions to guarantee O(log n) performance for all operations, even in the worst case.