Programming & Data Structures
Subject 3 of 10 Β· C fundamentals, pointers, the classic data structures, and the complexity table that ties them together.
1. C Programming Basics
Data types and variables
A variable is a named storage location; its data type decides how many bytes it occupies and what operations are legal. Declaration introduces the variable, definition allocates storage, initialization gives it a value. A const qualifier makes a variable read-only after initialization.
Storage classes
| Class | Storage | Scope | Lifetime |
|---|---|---|---|
| auto | Stack | Local to block | Block execution |
| register | CPU register (hint) | Local to block | Block execution |
| static | Data segment | Local to block/file | Whole program |
| extern | Data segment | Global (all files) | Whole program |
Scope is where a name is visible; lifetime is how long its storage exists. A static local keeps its value between calls β the classic function-call counter. Block scope: a variable declared inside { } dies at the closing brace. Shadowing: an inner declaration hides an outer one with the same name.
2. Operators & Expressions
The operator families
| Family | Operators |
|---|---|
| Arithmetic | + - * / % (plus ++ --) |
| Relational | < <= > >= == != |
| Logical | && || ! |
| Bitwise | & | ^ ~ << >> |
| Assignment | = += -= *= /= %= |
Short-circuit evaluation: in a && b, if a is false, b is never evaluated. Same idea for ||. This makes guards like if (p && p->data) safe.
! > * / % > + - > relational > == > && > || > assignment. When in doubt, add parentheses β they're free and unambiguous.
3. Control Flow
if / else if / else for branching, switch for multi-way dispatch on an integer or character constant (every case needs its own break unless fall-through is intended). Loops: while (test first), do-while (runs at least once), for (init; condition; update). break exits the loop, continue skips to the next iteration.
switch (grade) {
case 'A': printf("Excellent"); break;
case 'B': printf("Good"); break;
default: printf("Keep trying");
}
4. Recursion
Recursion solves a problem by solving smaller instances of itself. Every recursive function needs a base case (stops the recursion) and a recursive case (shrinks toward the base). Each call gets its own stack frame β that's why deep recursion can overflow the stack.
// Factorial
int fact(int n) {
if (n <= 1) return 1; // base case
return n * fact(n - 1); // recursive case
}
// Fibonacci
int fib(int n) {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2);
}
Tower of Hanoi: move n disks from source to destination using an auxiliary peg. Move nβ1 disks aside, move the largest, move nβ1 back on top β total moves = 2n β 1.
GCD (Euclid): gcd(a, b) = gcd(b, a % b), base case gcd(a, 0) = a.
5. Pointers & Memory
Pointer basics
A pointer holds a memory address. & takes an address, * dereferences it. Pointer arithmetic moves in units of the pointed-to type: p + 1 jumps one whole element, not one byte.
| Pointer type | What it is |
|---|---|
| NULL pointer | Points nowhere β the safe "uninitialized" state |
| Void pointer (void *) | Generic address; cast before dereferencing |
| Wild pointer | Uninitialized pointer β points at garbage, dereferencing is undefined behavior |
| Dangling pointer | Points at memory already freed β the classic use-after-free bug |
Initialize every pointer (to NULL if you have nothing yet), and set it to NULL right after free(). Those two habits kill most pointer bugs.
Dynamic memory allocation
int *p = malloc(n * sizeof(int)); // uninitialized block
int *q = calloc(n, sizeof(int)); // zero-initialized block
p = realloc(p, m * sizeof(int)); // resize (may move the block)
free(p); p = NULL; // release and null out
A memory leak is allocated memory never freed β the program slowly bleeds RAM until it dies or gets killed.
6. Arrays & Strings
An array is a contiguous block of same-type elements with O(1) random access. In C, a string is just a char array terminated by '\0'.
i = index, size = bytes per element
2D arrays: row-major vs column-major
Row-major lays each row contiguously, so iterating row-by-row is cache-friendly in C. Get the order wrong and performance falls off a cliff on large matrices.
7. Linked Lists
Singly linked list
Each node holds data plus a pointer to the next node. Memory is allocated per node at runtime (no fixed capacity), but there's no random access β reaching element k costs O(k).
struct Node {
int data;
struct Node *next;
};
| Operation | Time |
|---|---|
| Insert at head | O(1) |
| Delete at head | O(1) |
| Insert/delete at position k | O(k) β must walk there first |
| Search | O(n) |
Doubly linked list
Adds a prev pointer to each node, so traversal runs both directions and deletion is O(1) once you hold the node. The price: extra memory per node and more pointer updates on every insert/delete (four pointer fixes for a middle insertion).
struct DNode {
int data;
struct DNode *prev;
struct DNode *next;
};
8. Stacks
LIFO β last in, first out. You only ever touch the top: push adds, pop removes and returns, peek looks without removing, isEmpty checks.
void push(int x) {
if (top == MAX - 1) { /* stack overflow */ return; }
stack[++top] = x;
}
int pop(void) {
if (top == -1) { /* stack underflow */ return -1; }
return stack[top--];
}
Applications: function call management (the call stack), expression evaluation and infixβpostfix conversion, undo/redo, backtracking (DFS is a stack in disguise).
The classic recursive demo needs 2n β 1 moves for n disks. Each recursive call is a stack frame β run it for n = 64 and the universe ends first.
9. Queues
FIFO β first in, first out. enqueue adds at the rear, dequeue removes from the front.
Circular queue
Wraps the array around so freed front slots get reused β no shifting needed. Indices advance with modulo arithmetic:
- Empty: front == -1
- Full: (rear + 1) % size == front
- Enqueue: rear = (rear + 1) % size; dequeue: front = (front + 1) % size
Double-ended queue (Deque)
Insert and delete at both ends. Variants: input-restricted (insert at one end only) and output-restricted (delete at one end only). Powers sliding-window algorithms and work-stealing schedulers.
Applications: BFS traversal, process scheduling (ready queue), buffering (I/O queues, print spooling), resource management.
10. Trees
Terminology
| Term | Meaning |
|---|---|
| Root | The top node; the only node with no parent |
| Leaf | A node with no children |
| Depth | Edges from the root to a node |
| Height | Longest downward path to a leaf (a lone node has height 0) |
| Degree | Number of children of a node |
Binary tree properties
- Max nodes at level l: 2l
- Max nodes in a tree of height h: 2h+1 β 1
- Minimum height of n nodes: βlogβ nβ
Traversals
| Traversal | Order | Yields |
|---|---|---|
| Inorder | Left β Root β Right | Sorted sequence on a BST |
| Preorder | Root β Left β Right | Prefix expression; tree copy structure |
| Postorder | Left β Right β Root | Postfix expression; safe deletion order |
| Level order | Level by level (uses a queue) | Breadth-first visit order |
Binary Search Tree (BST)
The ordering invariant: left subtree < root < right subtree, recursively. Search, insert, and delete all run in O(h) where h is the height β O(log n) on average, O(n) if insertions arrive sorted and the tree degenerates into a line. Deletion cases: leaf (just remove), one child (bypass), two children (replace with inorder successor, then delete it).
11. Balanced BSTs & AVL
Balance is what keeps tree operations at O(log n). Two families: height-balanced trees (AVL, Red-Black) and multiway trees (B-trees).
B-trees
A B-tree of order m: every node holds at most mβ1 keys and m children; every node except the root holds at least βm/2ββ1 keys; the root has at least 2 children unless it's a leaf; all leaves sit at the same level. That's what makes them the backbone of databases and filesystems β one node = one disk block.
AVL trees
A BST with a strict balance rule: for every node, |height(left) β height(right)| β€ 1. The balance factor is stored per node; insertions and deletions that violate it trigger rotations:
| Case | Rotation |
|---|---|
| Left-Left (LL) | Single right rotation |
| Right-Right (RR) | Single left rotation |
| Left-Right (LR) | Left rotation on child, then right rotation |
| Right-Left (RL) | Right rotation on child, then left rotation |
AVL keeps the height at ~1.44 log n, so every operation stays O(log n) with slightly costlier inserts than Red-Black trees β pick AVL for read-heavy workloads.
12. Heaps
A binary heap is a complete binary tree (every level full except possibly the last, filled left to right) with the heap property: in a min-heap every parent β€ its children (root is the minimum); in a max-heap every parent β₯ its children. Stored compactly in an array: children of index i at 2i+1, 2i+2.
| Operation | Time |
|---|---|
| Insert | O(log n) β append at end, bubble up |
| Extract min/max | O(log n) β take root, move last element up, heapify down |
| Peek min/max | O(1) |
| Build heap (heapify) | O(n) |
Uses: priority queues, heap sort (O(n log n), in-place), scheduling. Note: a heap is not a BST β only the root is ordered; siblings are unrelated.
13. Hashing
Hashing maps keys to table indices with a hash function, giving average O(1) search, insert, and delete. Common functions: division (h(k) = k mod m), multiplication (multiply by a constant, take the fractional part), and universal hashing (randomized family to defeat adversarial keys).
n = elements stored, m = table slots. Free space = (1 β Ξ±) Γ 100%
Lower Ξ± β fewer collisions β better performance. When Ξ± crosses a threshold (commonly ~0.7 for open addressing), the table rehashes into a bigger one. Hard constraint: with open addressing, Ξ± can never exceed 1 β everything must fit inside the table.
Collision resolution: chaining
Each slot holds a linked list of all keys that hash there β colliding elements simply join the chain. No table-overflow problem (chains grow indefinitely), simple to implement, and performance degrades gracefully as Ξ± rises.
| Method | Avg successful search |
|---|---|
| Chaining | 1 + Ξ±/2 |
| Linear probing | Β½(1 + 1/(1 β Ξ±)) |
Chaining beats linear probing once Ξ± > 0.5, which is why it's the right pick for high-utilization tables. Linear probing suffers primary clustering β occupied slots clump together and probes get longer.
14. Graphs
A graph is vertices plus edges. Adjacency matrix: a VΓV matrix with a 1 where an edge exists β O(1) edge lookup, but O(VΒ²) space even for sparse graphs. Adjacency list: each vertex stores its neighbors β O(V + E) space, the default for sparse graphs.
Traversal
- BFS (queue): explores level by level β finds shortest paths in unweighted graphs. O(V + E).
- DFS (stack/recursion): dives deep first β used for topological sort, cycle detection, connected components. O(V + E).
15. Complexity Cheat Sheet
Average-case access / search / insert / delete, worst case, and space β the table every exam draws from:
| Data Structure | Access | Search | Insert | Delete | Space |
|---|---|---|---|---|---|
| Array | O(1) | O(n) | O(n) | O(n) | O(n) |
| Stack | O(n) | O(n) | O(1) | O(1) | O(n) |
| Doubly Linked List | O(n) | O(n) | O(1) | O(1) | O(n) |
| Skip List | O(log n) | O(log n) | O(log n) | O(log n) | O(n log n) |
| Hash Table | β | O(1) | O(1) | O(1) | O(n) |
| Binary Search Tree | O(log n) | O(log n) | O(log n) | O(log n) | O(n) |
| AVL Tree | O(log n) | O(log n) | O(log n) | O(log n) | O(n) |
| Red-Black Tree | O(log n) | O(log n) | O(log n) | O(log n) | O(n) |
| B-Tree | O(log n) | O(log n) | O(log n) | O(log n) | O(n) |
Arrays: instant access, expensive insert/delete (shifting). Linked lists: cheap insert/delete at a known position, slow search. Hash tables: best for key-based lookup. BST degrades to O(n) unbalanced β AVL, Red-Black, and B-trees exist to guarantee the O(log n). Skip lists are the probabilistic middle ground: expected O(log n) with far simpler code.