Phase 7 — Data Structures & Algorithms in C
Phase 7 is where C moves from “I know the language” to “I can use the language to solve real problems.”
We’ll divide it logically into:
Foundations of DSA
Linear Data Structures
Hash Tables
Trees & Heaps
Graphs
Searching & Sorting
Algorithmic Thinking & Complexity
Module 1 — DSA Foundations
Chapter 1 — What Is a Data Structure?
Question
The following program stores three numbers in an array and accesses the second element.
What is the output?
#include <stdio.h>
int main(void)
{
// Store related values together.
int numbers[] = {10, 20, 30};
// Access the second element.
printf("%d\n", numbers[1]);
return 0;
}Answer
20Step-by-step explanation
numbersis an array.It contains three integers:
index: 0 1 2
value: 10 20 30C arrays use zero-based indexing.
Therefore
numbers[1]means the second element.The result is
20.
How to read the important code
When you see:
int numbers[] = {10, 20, 30};read it naturally as:
“Create an integer array containing 10, 20 and 30.”
A data structure is simply a way of organizing data so that operations on that data can be performed efficiently.
Examples:
Array
Linked List
Stack
Queue
Hash Table
Tree
Heap
GraphKey takeaway
Data structure = a way of organizing and storing data.
Module 2 — Dynamic Arrays
Chapter 2 — Dynamic Arrays
Question
Predict the output.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
// Allocate space for three integers.
int *numbers = malloc(3 * sizeof(int));
// Store values dynamically.
numbers[0] = 10;
numbers[1] = 20;
numbers[2] = 30;
// Read the final element.
printf("%d\n", numbers[2]);
// Release the allocated memory.
free(numbers);
return 0;
}Answer
30Step-by-step explanation
malloc() creates memory dynamically.
malloc(3 * sizeof(int))means:
“Give me enough heap memory to store three integers.”
Because numbers is a pointer, we can use array syntax:
numbers[0]
numbers[1]
numbers[2]This is one of the most important C ideas:
An allocated block of memory can be used like an array through a pointer.
A dynamic array usually needs:
pointer → allocated memory
size → number of elements currently stored
capacity → number of elements the allocation can holdWhen capacity is exhausted, we can use realloc() to grow it.
Key takeaway
Dynamic arrays combine pointers, heap memory and array-style access.
Module 3 — Linked Lists
Chapter 3 — Singly Linked List
Question
Predict the output.
#include <stdio.h>
#include <stdlib.h>
struct Node
{
// Store the actual value.
int data;
// Point to the next node.
struct Node *next;
};
int main(void)
{
// Create two nodes dynamically.
struct Node *first = malloc(sizeof(struct Node));
struct Node *second = malloc(sizeof(struct Node));
// Connect the nodes.
first->data = 10;
first->next = second;
second->data = 20;
second->next = NULL;
// Traverse the list.
printf("%d %d\n", first->data, first->next->data);
// Free both nodes.
free(second);
free(first);
return 0;
}Answer
10 20Step-by-step explanation
Each node contains:
+------+------+
| data | next |
+------+------+The structure is:
first
|
v
+----+------+ +----+------+
| 10 | next | --> | 20 | NULL |
+----+------+ +----+------+This line:
first->next->datameans:
Go to
first.Follow its
nextpointer.Reach the second node.
Read its
data.
-> is used when accessing a structure through a pointer.
Key takeaway
A linked list is a chain of nodes connected through pointers.
Chapter 4 — Doubly & Circular Linked Lists
Question
What does this print?
#include <stdio.h>
struct Node
{
int data;
struct Node *next;
struct Node *prev;
};
int main(void)
{
struct Node a = {10, NULL, NULL};
struct Node b = {20, NULL, NULL};
// Connect a forward to b.
a.next = &b;
// Connect b backward to a.
b.prev = &a;
printf("%d %d\n", a.next->data, b.prev->data);
return 0;
}Answer
20 10Step-by-step explanation
A singly linked list has:
data + nextA doubly linked list has:
data + next + prevSo we can travel:
10 → 20
10 ← 20A circular linked list connects the final node back to the first:
10 → 20 → 30
↑ ↓
└─────────┘Key takeaway
Doubly linked lists allow movement in both directions; circular lists connect the end back to the beginning.
Module 4 — Stack
Chapter 5 — Stack
A stack follows:
LIFO — Last In, First Out
Question
Predict the output.
#include <stdio.h>
#define SIZE 3
int main(void)
{
int stack[SIZE];
int top = -1;
// Push three values.
stack[++top] = 10;
stack[++top] = 20;
stack[++top] = 30;
// Pop the most recently inserted value.
printf("%d\n", stack[top--]);
// Pop again.
printf("%d\n", stack[top--]);
return 0;
}Answer
30
20Step-by-step explanation
After pushing:
30 ← top
20
10The last value inserted is 30.
Therefore:
stack[top--]returns 30.
Then top decreases.
Next value:
20This is LIFO.
Real-world examples
Stacks are used for:
function calls
undo operations
expression evaluation
parentheses matching
DFS
backtracking
Key takeaway
Stack = LIFO.
Module 5 — Queue
Chapter 6 — Queue
A queue follows:
FIFO — First In, First Out
Question
Predict the output.
#include <stdio.h>
#define SIZE 3
int main(void)
{
int queue[SIZE];
int front = 0;
int rear = 0;
// Insert values at the rear.
queue[rear++] = 10;
queue[rear++] = 20;
queue[rear++] = 30;
// Remove from the front.
printf("%d\n", queue[front++]);
// Remove the next item.
printf("%d\n", queue[front++]);
return 0;
}Answer
10
20Step-by-step explanation
The queue initially contains:
FRONT
↓
10 20 30
↑
REARThe first element inserted is removed first.
That's FIFO.
Key takeaway
Queue = FIFO.
Chapter 6 — Circular Queue
A simple array queue eventually reaches the end of the array even when earlier positions are free.
A circular queue solves this by wrapping around.
Question
What is printed?
#include <stdio.h>
#define SIZE 4
int main(void)
{
int queue[SIZE];
int front = 0;
int rear = 0;
// Insert three values.
queue[rear] = 10;
rear = (rear + 1) % SIZE;
queue[rear] = 20;
rear = (rear + 1) % SIZE;
queue[rear] = 30;
rear = (rear + 1) % SIZE;
// Remove one item.
front = (front + 1) % SIZE;
// Reuse the freed position.
queue[rear] = 40;
printf("%d\n", queue[rear]);
return 0;
}Answer
40Step-by-step explanation
The important expression is:
(rear + 1) % SIZEWhen rear reaches the end:
0 → 1 → 2 → 3 → 0 → 1 ...The % operator creates the circular behavior.
Key takeaway
Circular queues reuse array space by wrapping indexes around.
Module 7 — Hash Tables
Chapter 7 — Hashing
A hash table attempts to provide very fast lookup.
Conceptually:
key
↓
hash function
↓
index
↓
stored valueQuestion
Predict the output.
#include <stdio.h>
#define SIZE 10
int main(void)
{
int table[SIZE] = {0};
// Store 42 using a simple hash.
int key = 42;
int index = key % SIZE;
table[index] = 100;
// Find the value using the same hash.
printf("%d\n", table[key % SIZE]);
return 0;
}Answer
100Step-by-step explanation
For:
key = 42the hash is:
42 % 10 = 2Therefore:
table[2] = 100Searching for 42 produces the same index:
42 % 10 = 2and retrieves:
100Collision
Different keys can produce the same index:
12 % 10 = 2
42 % 10 = 2This is called a collision.
Common solutions:
separate chaining
open addressing
linear probing
quadratic probingKey takeaway
Hash tables trade memory for very fast average-case lookup.
Module 8 — Trees
Chapter 8 — Binary Tree
A binary tree allows each node to have at most two children.
Question
Predict the output.
#include <stdio.h>
struct Node
{
int data;
struct Node *left;
struct Node *right;
};
void preorder(struct Node *root)
{
if (root == NULL)
return;
// Visit the current node.
printf("%d ", root->data);
// Visit the left subtree.
preorder(root->left);
// Visit the right subtree.
preorder(root->right);
}
int main(void)
{
struct Node left = {2, NULL, NULL};
struct Node right = {3, NULL, NULL};
struct Node root = {1, &left, &right};
preorder(&root);
return 0;
}Answer
1 2 3Step-by-step explanation
The tree is:
1
/ \
2 3Preorder means:
ROOT
LEFT
RIGHTTherefore:
1 → 2 → 3Tree traversals
Preorder
Root → Left → RightInorder
Left → Root → RightPostorder
Left → Right → RootKey takeaway
Tree traversal determines the order in which nodes are visited.
Chapter 9 — Binary Search Tree
Question
What does this print?
#include <stdio.h>
struct Node
{
int data;
struct Node *left;
struct Node *right;
};
void inorder(struct Node *root)
{
if (root == NULL)
return;
// Visit smaller values first.
inorder(root->left);
printf("%d ", root->data);
// Visit larger values afterward.
inorder(root->right);
}
int main(void)
{
struct Node a = {2, NULL, NULL};
struct Node b = {8, NULL, NULL};
struct Node root = {5, &a, &b};
inorder(&root);
return 0;
}Answer
2 5 8Step-by-step explanation
A Binary Search Tree (BST) follows:
left subtree < root < right subtreeFor:
5
/ \
2 8inorder traversal gives:
2 5 8This is why inorder traversal of a valid BST produces sorted values.
Key takeaway
BSTs organize values so searching can avoid entire portions of the tree.
Module 9 — Heap & Priority Queue
Chapter 10 — Heap
A heap is commonly represented using an array.
For a zero-based array:
parent(i) = (i - 1) / 2
left(i) = 2i + 1
right(i) = 2i + 2Question
For this array:
int heap[] = {50, 30, 40, 10, 20};what is the parent of the element at index 4?
Answer
30Step-by-step explanation
Index:
i = 4Parent:
(4 - 1) / 2
= 3 / 2
= 1Index 1 contains:
30The tree representation is:
50
/ \
30 40
/ \
10 20A max heap keeps the largest value at the root.
A min heap keeps the smallest value at the root.
Heaps are commonly used to implement:
Priority queues
Key takeaway
Heap = tree-like priority structure commonly stored efficiently in an array.
Module 10 — Graphs
Chapter 11 — Graph Representation
A graph contains:
vertices/nodes
+
edges/connectionsExample:
A ─── B
| |
C ─── DQuestion
Predict the output.
#include <stdio.h>
int main(void)
{
// Adjacency matrix for three vertices.
int graph[3][3] =
{
{0, 1, 1},
{1, 0, 0},
{1, 0, 0}
};
// Check whether vertex 0 connects to vertex 2.
printf("%d\n", graph[0][2]);
return 0;
}Answer
1Step-by-step explanation
The matrix means:
0 1 2
----------
0 | 0 1 1
1 | 1 0 0
2 | 1 0 0Therefore:
graph[0][2]is 1.
In this representation:
1 = edge exists
0 = edge doesn't existTwo common graph representations are:
Adjacency matrix
O(V²) memoryAdjacency list
O(V + E) memorywhere:
V= verticesE= edges
Key takeaway
A graph represents relationships between objects.
Module 11 — Searching
Chapter 12 — Linear Search
Question
What does the function return?
#include <stdio.h>
int linear_search(int numbers[], int size, int target)
{
// Check every element from left to right.
for (int i = 0; i < size; i++)
{
if (numbers[i] == target)
return i;
}
// Target wasn't found.
return -1;
}
int main(void)
{
int numbers[] = {10, 20, 30, 40};
printf("%d\n", linear_search(numbers, 4, 30));
return 0;
}Answer
2Step-by-step explanation
The function checks:
10 → 20 → 3030 is at index 2.
Therefore:
return 2Worst-case complexity:
O(n)Key takeaway
Linear search checks elements sequentially.
Chapter 13 — Binary Search
Binary search requires sorted data.
Question
Predict the output.
#include <stdio.h>
int binary_search(int a[], int size, int target)
{
int low = 0;
int high = size - 1;
while (low <= high)
{
// Find the middle position.
int mid = low + (high - low) / 2;
if (a[mid] == target)
return mid;
if (a[mid] < target)
low = mid + 1;
else
high = mid - 1;
}
return -1;
}
int main(void)
{
int numbers[] = {10, 20, 30, 40, 50};
printf("%d\n", binary_search(numbers, 5, 40));
return 0;
}Answer
3Step-by-step explanation
Search:
10 20 30 40 50Middle:
30Target:
40Since:
40 > 30ignore the left half.
Search:
40 50Find 40.
Complexity:
O(log n)Why log n?
Each comparison approximately halves the search space:
1000
↓
500
↓
250
↓
125
↓
...Key takeaway
Binary search is extremely efficient, but the data must be appropriately ordered.
Module 12 — Sorting
Chapter 14 — Bubble Sort
Question
What is the final output?
#include <stdio.h>
int main(void)
{
int a[] = {3, 1, 2};
int n = 3;
// Repeatedly compare neighboring elements.
for (int i = 0; i < n - 1; i++)
{
for (int j = 0; j < n - 1 - i; j++)
{
// Swap if elements are in the wrong order.
if (a[j] > a[j + 1])
{
int temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
}
}
}
printf("%d %d %d\n", a[0], a[1], a[2]);
return 0;
}Answer
1 2 3Step-by-step explanation
Bubble sort repeatedly compares neighbors.
Initially:
3 1 2First pass:
3 1 → swap → 1 3 2
3 2 → swap → 1 2 3Sorted.
Worst-case:
O(n²)Key takeaway
Bubble sort is simple but inefficient for large datasets.
Chapter 15 — Selection Sort
Selection sort repeatedly finds the smallest remaining element.
Conceptually:
[5, 2, 4, 1]
find smallest → 1
put it first
[1, 2, 4, 5]Then continue with the remaining portion.
Complexity:
Best: O(n²)
Average: O(n²)
Worst: O(n²)Its advantage is that it performs relatively few swaps.
Key takeaway
Selection sort repeatedly selects the next smallest element.
Chapter 16 — Insertion Sort
Insertion sort builds the sorted portion one element at a time.
Question
What is the final output?
#include <stdio.h>
int main(void)
{
int a[] = {3, 1, 2};
int n = 3;
for (int i = 1; i < n; i++)
{
// Save the element being inserted.
int key = a[i];
int j = i - 1;
// Shift larger values to the right.
while (j >= 0 && a[j] > key)
{
a[j + 1] = a[j];
j--;
}
// Put key into its correct position.
a[j + 1] = key;
}
printf("%d %d %d\n", a[0], a[1], a[2]);
return 0;
}Answer
1 2 3Step-by-step explanation
Think of sorting playing cards.
You already have:
3Insert 1:
1 3Insert 2:
1 2 3Worst-case:
O(n²)But for nearly sorted data, insertion sort can perform very well.
Key takeaway
Insertion sort is particularly useful for small or nearly sorted datasets.
Module 13 — Divide and Conquer
Chapter 17 — Merge Sort
Merge sort follows:
Divide
↓
Sort smaller pieces
↓
MergeExample:
[8 3 2 9]
divide
↓
[8 3] [2 9]
↓ ↓
[3 8] [2 9]
merge
[2 3 8 9]Complexity
Best: O(n log n)
Average: O(n log n)
Worst: O(n log n)Typical additional space:
O(n)Key takeaway
Merge sort repeatedly divides the problem and efficiently merges sorted pieces.
Chapter 18 — Quicksort
Quicksort chooses a pivot.
Then it partitions elements around that pivot.
Conceptually:
[7 2 9 4 1]
pivot = 4
smaller: [2 1]
pivot: [4]
larger: [7 9]Then recursively sort the two sides.
Average complexity:
O(n log n)Worst case:
O(n²)The quality of pivot selection matters greatly.
Key takeaway
Quicksort partitions around a pivot and recursively sorts the partitions.
Chapter 19 — Heap Sort
Heap sort uses a heap.
For ascending order, a max heap can repeatedly move the largest element to the end.
Complexity:
Best: O(n log n)
Average: O(n log n)
Worst: O(n log n)It has an important theoretical advantage:
Worst-case
O(n log n)without requiring an auxiliary array like merge sort.
Key takeaway
Heap sort combines heap operations with sorting.
Module 14 — Recursion
Chapter 20 — Recursion
A recursive function calls itself.
Question
Predict the output.
#include <stdio.h>
void countdown(int n)
{
// Stop the recursion.
if (n == 0)
return;
printf("%d ", n);
// Call the function with a smaller problem.
countdown(n - 1);
}
int main(void)
{
countdown(3);
return 0;
}Answer
3 2 1Step-by-step explanation
countdown(3)
↓
countdown(2)
↓
countdown(1)
↓
countdown(0)
↓
returnEvery recursive function needs a base case.
Without:
if (n == 0)
return;the function could recurse indefinitely and eventually exhaust the call stack.
Key takeaway
Recursion = solving a problem by solving smaller versions of the same problem.
Module 15 — Complexity
Chapter 21 — Big O
Big O describes how an algorithm's resource requirements grow as input size increases.
Core complexities
| Complexity | General idea |
|---|---|
O(1) | Constant |
O(log n) | Repeatedly divide problem |
O(n) | Process each element |
O(n log n) | Efficient divide-and-conquer |
O(n²) | Nested comparison pattern |
O(2βΏ) | Explosive growth |
O(n!) | Extremely explosive growth |
Question
What is the complexity?
for (int i = 0; i < n; i++)
{
printf("%d\n", i);
}Answer
O(n)Because the loop executes approximately n times.
Another question
What is the complexity?
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
printf("%d %d\n", i, j);
}
}Answer
O(n²)The outer loop runs n times.
For every outer iteration, the inner loop runs n times:
n × n = n²Important distinction
Big O isn't simply:
“How many milliseconds does this program take?”
It describes growth as the input becomes larger.
Key takeaway
Big O helps you predict how an algorithm scales.
Module 16 — Space Complexity
Chapter 22 — Time vs Space
An algorithm can consume resources in two major ways:
Time → computation
Space → additional memoryExample:
int copy_array(int source[], int destination[], int n)
{
for (int i = 0; i < n; i++)
destination[i] = source[i];
return 0;
}The operation requires:
Time: O(n)The caller already provided the destination array, so the function itself doesn't allocate another n-element structure.
Contrast that with an algorithm that allocates:
int *copy = malloc(n * sizeof(int));Now additional memory proportional to n is used.
Key takeaway
Algorithm analysis considers both computation and memory usage.
Module 17 — Greedy Algorithms
Chapter 23 — Greedy Thinking
A greedy algorithm repeatedly chooses what looks best right now.
Classic examples include:
activity selection
fractional knapsack
some scheduling problems
Huffman coding
minimum spanning tree algorithms
But greedy is dangerous if the local best choice doesn't lead to the global best answer.
Example
Suppose you have:
Coins: 25, 10, 5, 1
Amount: 41Greedy chooses:
25
10
5
1giving:
4 coinsBut not every coin system behaves this nicely.
Key takeaway
Greedy algorithms make locally optimal choices; you must prove that those choices lead to a globally optimal solution.
Module 18 — Dynamic Programming
Chapter 24 — DP Basics
Dynamic programming is useful when a problem contains:
Overlapping subproblems
Optimal substructure
A classic example is Fibonacci.
Naive recursion repeatedly solves the same problems:
fib(5)
├── fib(4)
│ ├── fib(3)
│ └── fib(2)
└── fib(3)
├── fib(2)
└── fib(1)fib(3), fib(2), etc. are calculated repeatedly.
DP stores previous answers.
Question
What does this print?
#include <stdio.h>
int main(void)
{
int n = 6;
int dp[7];
dp[0] = 0;
dp[1] = 1;
// Build answers from smaller answers.
for (int i = 2; i <= n; i++)
dp[i] = dp[i - 1] + dp[i - 2];
printf("%d\n", dp[n]);
return 0;
}Answer
8Step-by-step explanation
The table becomes:
index: 0 1 2 3 4 5 6
value: 0 1 1 2 3 5 8Instead of repeatedly recomputing values, we save them.
This is called memoization/tabulation, depending on the approach.
Key takeaway
Dynamic programming avoids repeated work by remembering previously solved subproblems.
Module 19 — Tree Algorithms
Chapter 25 — Tree Traversal
The three fundamental depth-first traversals are:
Preorder:
Root → Left → Right
Inorder:
Left → Root → Right
Postorder:
Left → Right → RootFor:
A
/ \
B Cwe get:
Preorder = A B C
Inorder = B A C
Postorder = B C AFor a BST, inorder traversal is particularly important because it produces sorted order.
Key takeaway
Traversal algorithms define systematic ways to visit every tree node.
Module 20 — Graph Algorithms
Chapter 26 — BFS and DFS
Two fundamental graph traversal techniques are:
BFS — Breadth-First Search
Uses a queue.
It explores:
level by levelDFS — Depth-First Search
Uses:
recursion / stackIt explores deeply before backtracking.
Consider:
A
├── B
│ ├── D
│ └── E
└── CPossible DFS order:
A B D E CPossible BFS order:
A B C D EImportant applications
BFS:
shortest path in an unweighted graph
level-order traversal
DFS:
cycle detection
connected components
topological sorting
maze/backtracking problems
Key takeaway
BFS thinks in levels; DFS thinks in depth.
Module 21 — Important Graph Algorithms
Chapter 27 — Shortest Paths & Connectivity
Important algorithms to know:
BFS
Shortest path in an unweighted graph.
Dijkstra
Shortest paths when edge weights are non-negative.
Bellman-Ford
Handles negative edge weights and can detect negative cycles.
Floyd-Warshall
All-pairs shortest paths.
Typical complexity:
O(V³)Minimum Spanning Tree
Two famous algorithms:
Kruskal
sort edges
→ repeatedly select cheapest valid edgePrim
start with a vertex
→ repeatedly add cheapest connecting edgeKey takeaway
Graph algorithms solve problems involving paths, connectivity, dependencies and networks.
Final Phase 7 Cheat Sheet
You should now have this mental map:
DATA STRUCTURES
│
┌──────────────────┼──────────────────┐
│ │ │
Linear Hashing Trees
│ │ │
┌─────┼─────┐ │ ┌──────┼──────┐
Array List Stack Hash Binary BST Heap
│ │
Queue
│
CircularAnd:
ALGORITHMS
│
┌───────────────┼────────────────┐
│ │ │
Search Sort Traversal
│ │ │
Linear/Binary Bubble/Selection BFS / DFS
Insertion
Merge
Quick
HeapThen the higher-level thinking:
Algorithmic Thinking
│
├── Recursion
├── Big O
├── Space Complexity
├── Divide & Conquer
├── Greedy
└── Dynamic ProgrammingThe complexity table you should memorize
| Algorithm / Structure | Typical complexity |
|---|---|
| Array access | O(1) |
| Linear search | O(n) |
| Binary search | O(log n) |
| Linked-list traversal | O(n) |
| Stack push/pop | O(1) |
| Queue enqueue/dequeue | O(1) |
| Hash-table lookup | O(1) average |
| BST search | O(log n) average |
| BST search | O(n) worst case |
| Heap insert | O(log n) |
| Heap remove | O(log n) |
| Bubble sort | O(n²) |
| Selection sort | O(n²) |
| Insertion sort | O(n²) worst |
| Merge sort | O(n log n) |
| Quicksort | O(n log n) average |
| Quicksort | O(n²) worst |
| Heap sort | O(n log n) |
| BFS | O(V + E) |
| DFS | O(V + E) |
| Floyd-Warshall | O(V³) |
Phase 7 mastery target
You should be able to look at a problem and ask:
What data am I storing?
→ Choose a data structure.
Then:
What operation matters most?
→ Optimize the structure around that operation.
Then:
How will I solve the problem?
→ Choose an algorithm.
Then:
How does it scale?
→ Analyze time + space complexity.
That is the core of Data Structures & Algorithms in C.
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.