1. Introduction to Sorting
Explained for a 5-Year-Old:
Imagine you have a big box of colorful toy cars all mixed up. Sorting is like putting all the red cars in one pile, all the blue cars in another, and so on, until everything is neat and easy to find. We do this with numbers on a computer, too, to arrange them from smallest to biggest (or biggest to smallest).
Formal Definition & Importance:
Sorting is the algorithmic process of arranging elements in a sequence (e.g., numerical order, lexicographical order) according to a total order. The efficiency of a sorting algorithm is critical, as it forms the basis for many other complex algorithms (like searching) and for optimizing data storage. A sorted array allows for drastically faster searching (e.g., Binary Search) compared to an unsorted one (Linear Search). The primary metrics for evaluating sorting algorithms are Time Complexity (how the runtime scales with input size `n`) and Space Complexity (how much extra memory is required).
2. Internal Sorting Algorithms
2.1 Insertion Sort
Explained for a 5-Year-Old:
Think about how you sort a hand of playing cards. You pick up one card at a time from the messy pile and slide it into its correct spot among the cards you're already holding, which are already sorted. You keep doing this until all the cards from the pile are in your hand, in perfect order.
Algorithm & Analysis:
Insertion Sort is a simple, in-place comparison-based sorting algorithm. It builds the final sorted array one item at a time. It iterates through the input elements and, for each element, finds its correct position in the sorted part of the array and inserts it there.
Time Complexity: Best Case: O(n) - when the array is already sorted. Worst Case: O(n²) - when the array is sorted in reverse. Average Case: O(n²). Space Complexity: O(1) - it sorts in-place. Stability: Yes, it is a stable sort as it does not change the relative order of elements with equal keys. It is efficient for small datasets and nearly sorted data.
Figure 1: First pass of Insertion Sort on [5, 2, 4, 6, 1, 3]. The element 2 is inserted into the sorted subarray [5].
Exam Walkthrough:
Sort the list: [12, 65, 34, 9, 56, 43, 10] (from 2022 Q19a). Initial:
[12, 65, 34, 9, 56, 43, 10].
Pass 1 (key=65): [12, 65, 34, 9, 56, 43, 10] (no change).
Pass 2 (key=34): [12, 34, 65, 9, 56, 43, 10].
Pass 3 (key=9): [9, 12, 34, 65, 56, 43, 10].
...and so on, until the final sorted array:
[9, 10, 12, 34, 43, 56, 65].
2.2 Merge Sort
Explained for a 5-Year-Old:
Imagine you have a huge pile of unsorted blocks. You split the pile into two smaller piles. Then you split those piles again, and again, until every pile has only one block (which is already sorted!). Then you start merging them back together: you take two single-block piles, sort them to make a two-block pile. Then you take two two-block piles and merge them into a perfect four-block pile, and so on, until you have one big, perfectly sorted pile again.
Algorithm & Analysis:
Merge Sort is a classic Divide and Conquer algorithm. It divides the unsorted list into n sublists, each containing one element (which are considered sorted). Then it repeatedly merges sublists to produce new sorted sublists until there is only one sublist remaining, which is the sorted list.
Time Complexity: Best, Average, and Worst Case are all O(n log n). This is because the list is divided log n times, and each level of division requires a linear O(n) merge operation. Space Complexity: O(n) - it requires an auxiliary array of the same size as the input for the merging process. Stability: Yes, it is a stable sort. Its consistent O(n log n) performance makes it highly reliable for large datasets.
Figure 2: Merge Sort's Divide and Conquer process on an example array.
3. Searching Algorithms
3.1 Binary Search
Explained for a 5-Year-Old:
Imagine you're looking for a specific page in a very thick book. You wouldn't start from page 1 and flip through every single page. Instead, you'd open the book right in the middle. Is the page number you need higher or lower than the page you're on? If it's lower, you'll only look in the first half of the book. You keep cutting the search area in half like this until you find the exact page. It's super fast!
Algorithm & Analysis:
Binary Search is a highly efficient searching algorithm that works on the principle of divide and conquer. It requires the array to be sorted beforehand. The algorithm compares the target value to the middle element of the array. If they are not equal, the half in which the target cannot lie is eliminated, and the search continues on the remaining half. This process is repeated until the value is found or the interval is empty.
Time Complexity: Best Case: O(1) - when the middle element is the target. Worst and Average Case: O(log n) - because the search space is halved in each iteration. Space Complexity: O(1) for the iterative version. O(log n) for the recursive version due to the call stack. Its efficiency makes it indispensable for searching in large, static datasets.
Figure 3: First step of Binary Search. The middle element (15) is less than the key (23), so the search continues in the right half of the array.
4. Hashing
4.1 Concept & Hash Functions
Explained for a 5-Year-Old:
Imagine a giant coat check room with a hundred hooks, numbered 0 to 99. The "hash function" is a magic rule. If you give the attendant your coat (your "data"), they look at the name tag (your "key") and use the rule to instantly know which exact hook to put it on. For example, the rule might be "add up the letters in the name and find the remainder when you divide by 100". This makes finding your coat super fast later!
Formal Definition:
Hashing is a technique used to uniquely identify a specific object from a group of similar objects. It uses a hash function `h(k)` to compute an index into an array of buckets or slots, from which the desired value can be found. The primary goal is to map keys to array indices in a way that allows for O(1) average-case time complexity for search, insert, and delete operations.
Collision: Since the number of possible keys is typically much larger than the number of array slots, a collision occurs when the hash function maps two different keys to the same index. A good hash function minimizes collisions, but a robust system must have a collision resolution technique to handle them.
Common Hash Functions:
- Division Method:
h(k) = k mod m, where `m` is the table size. `m` should be a prime number to reduce collisions. - Mid-Square Method: Square the key `k`, then extract the middle `r` digits as the hash value. Useful when key sizes vary.
- Folding Method: Divide the key `k` into equal-sized parts (except possibly the last), then sum the parts to get the hash value.
Figure 4: The process of hashing a key to obtain an array index.
4.2 Collision Resolution Techniques
Explained for a 5-Year-Old:
Sometimes, the magic rule tells the attendant to put two different coats on the same hook! That's a "collision". How do they solve it?
Open Chaining: The attendant just hangs a second hook from the first one and puts the new coat there. A chain of coats!
Linear Probing: The attendant sees the hook is full, so they try the very next hook. If that's full, they try the next one, and so on, until they find an empty hook.
Techniques & Analysis:
1. Open Addressing (Closed Hashing): All elements are stored within the hash table itself. When a collision occurs, the algorithm probes for the next available slot according to a specific rule.
- Linear Probing: If slot `h(k)` is occupied, try `h(k) + 1`, then `h(k) + 2`, and so on, wrapping around to the beginning of the table if necessary. Simple but suffers from primary clustering, where collided keys form long contiguous blocks.
- Double Hashing: Uses a second hash function `h2(k)` to determine the probe sequence. The i-th probe is at `(h(k) + i * h2(k)) mod m`. This helps to avoid clustering as the step size varies for different keys. `h2(k)` must be chosen carefully (e.g., `h2(k) = R - (k mod R)` where `R` is a prime smaller than `m`).
2. Chaining (Open Hashing): The hash table is an array of pointers, each pointing to the head of a linked list. All keys that hash to the same index `i` are stored in the linked list at `A[i]`. Insertion involves appending to the list. Searching involves computing the hash and then traversing the list. Deletion is straightforward in a linked list. This method avoids clustering entirely, but requires extra memory for the pointers.
Figure 5: Comparing Chaining and Linear Probing for inserting keys 23 and 30 (h(23)=3, h(30)=2) into a table of size 7.
Exam Walkthrough (from 2022 Q20b & 2023 Q20a):
Keys: {16, 21, 23, 50, 19, 26}. Table Size: 7. Hash Function: H(K) = K mod 7.
Open Chaining:
H(16)=2, H(21)=0, H(23)=2, H(50)=1, H(19)=5, H(26)=5.
Slot 0: [21] -> Slot 1: [50] -> Slot 2: [16] -> [23] -> Slot 5: [19] -> [26].
Linear Probing:
H(16)=2 -> Insert at 2. [ , ,16, , , , ]
H(21)=0 -> Insert at 0. [21, ,16, , , , ]
H(23)=2 -> Collides at 2, try 3. Insert at 3. [21, ,16,23, , , ]
H(50)=1 -> Insert at 1. [21,50,16,23, , , ]
H(19)=5 -> Insert at 5. [21,50,16,23, ,19, ]
H(26)=5 -> Collides at 5, try 6. Insert at 6. [21,50,16,23, ,19,26]
5. Heaps
5.1 Max Heap & Min Heap
Explained for a 5-Year-Old:
A Max-Heap is like a family tree where every parent is older (has a bigger number) than all their children. The oldest grandparent of all is at the very top of the tree. A Min-Heap is the opposite: every parent is younger (has a smaller number) than all their children, and the youngest baby is at the top.
Formal Definition & Properties:
A Heap is a specialized tree-based data structure that satisfies the heap property. It is also a complete binary tree, meaning all levels are fully filled except possibly the last, which is filled from left to right.
Max-Heap Property: For any given node `i`, the value of `i` is greater than or equal to the value of its children. `A[parent(i)] >= A[i]`. The largest element is always at the root (index 0).
Min-Heap Property: For any given node `i`, the value of `i` is less than or equal to the value of its children. `A[parent(i)] <= A[i]`. The smallest element is always at the root.
Array Representation: A complete binary tree can be efficiently stored in an array. For an element at index `i`:
Application: Heaps are the basis for Priority Queues (where the highest/lowest priority item is always needed next) and the Heap Sort algorithm.
Figure 6: A Max-Heap tree structure and its corresponding array representation. Note that every parent node is greater than its children.
Reference: 2024 Exam Scheme & 2026 Handbook for CST201 Data Structures