Design and Analysis of AlgorithmsUnit 611 min read
Sorting & Searching Algorithms: Techniques, Analysis & Applications
Unit 6 of Design and Analysis of Algorithms covers fundamental sorting (comparison-based, non-comparison-based) and searching (linear, binary) algorithms, their time/space complexity, and real-world implementations in Nepalese systems like eSewa and Ncell.
Core Concepts
1. Sorting Algorithms: Classification & Comparison
Sorting rearranges elements into a specific order (ascending/descending). Algorithms differ in efficiency, stability, and adaptability.
Comparison-Based vs. Non-Comparison-Based
| Type | Examples | Key Idea | Best Case | Worst Case |
|---|---|---|---|---|
| Comparison-Based | Bubble Sort, Quick Sort, Merge Sort | Compare elements pairwise to decide order. | ||
| Non-Comparison-Based | Counting Sort, Radix Sort, Bucket Sort | Use auxiliary data (e.g., counts, digits) to sort without comparisons. |
Why does this matter?
- Comparison-based sorts have a lower bound of (proven by decision tree analysis).
- Non-comparison sorts exploit additional information (e.g., integer ranges) for linear time.
2. Key Sorting Algorithms (Visualized)
A. Bubble Sort (Simple but Inefficient)
How it works: Repeatedly swaps adjacent elements if they are in the wrong order. Time Complexity:
- Best: (already sorted)
- Average/Worst:
Code (Iterative):
def bubble_sort(arr):
n = len(arr)
for i in range(n):
swapped = False
for j in range(0, n-i-1):
if arr[j] > arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
swapped = True
if not swapped: break # Early exit if sorted
Trace for [5,3,8,4]:
| Pass | Swaps Performed | Array State |
|---|---|---|
| 1 | (5,3), (8,4) | [3,5,4,8] |
| 2 | (5,4) | [3,4,5,8] |
| 3 | None | [3,4,5,8] (sorted) |
B. Merge Sort (Divide & Conquer)
How it works:
- Divide: Split array into halves.
- Conquer: Recursively sort each half.
- Merge: Combine sorted halves.
Time Complexity: Always (stable, not in-place).
flowchart TD
A["MergeSort([38,27,43,3,9,82,10])"] --> B["Divide: [38,27,43] | [3,9,82,10]"]
B --> C["Recurse Left: [38,27,43] → [27,38,43]"]
B --> D["Recurse Right: [3,9,82,10] → [3,9,10,82]"]
C --> E["Merge [27,38,43] + [3,9,10,82] → [3,9,10,27,38,43,82]"]Code (Recursive):
def merge_sort(arr):
if len(arr) > 1:
mid = len(arr)//2
L = arr[:mid]
R = arr[mid:]
merge_sort(L)
merge_sort(R)
i = j = k = 0
while i < len(L) and j < len(R):
if L[i] < R[j]:
arr[k] = L[i]
i += 1
else:
arr[k] = R[j]
j += 1
k += 1
while i < len(L): arr[k] = L[i]; i += 1; k += 1
while j < len(R): arr[k] = R[j]; j += 1; k += 1
Trace for [38,27,43,3,9,82,10]:
| Step | Left Subarray | Right Subarray | Merged Result |
|---|---|---|---|
| Initial Split | [38,27,43] | [3,9,82,10] | |
| After Left Merge | [27,38,43] | [3,9,10,82] | |
| Final Merge | [3,9,10,27,38,43,82] |
C. Quick Sort (Efficient, In-Place)
How it works:
- Pivot Selection: Choose a pivot (e.g., last element).
- Partition: Rearrange so elements < pivot are left, > pivot are right.
- Recurse: Sort subarrays.
Time Complexity:
- Best/Average:
- Worst: (if pivot is smallest/largest).
Code (Randomized Pivot):
import random
def quick_sort(arr):
if len(arr) <= 1: return arr
pivot = random.choice(arr)
left = [x for x in arr if x < pivot]
middle = [x for x in arr if x == pivot]
right = [x for x in arr if x > pivot]
return quick_sort(left) + middle + quick_sort(right)
Trace for [10,80,30,90,40,50,70] (pivot=70):
| Partition Step | Left Array | Right Array | Pivot Position |
|---|---|---|---|
| First | [10,30,40,50] | [80,90] | 70 |
| Second (Left) | [10,30,40] | [50] | 50 |
| Third (Right) | [80] | [90] | 90 |
3. Searching Algorithms
A. Linear Search (Brute-Force)
How it works: Check each element sequentially until the target is found. Time Complexity: (worst/average/best).
Code:
def linear_search(arr, target):
for i in range(len(arr)):
if arr[i] == target:
return i
return -1
Trace for [4,2,7,1] searching 7:
| Index | Element | Match? |
|---|---|---|
| 0 | 4 | No |
| 1 | 2 | No |
| 2 | 7 | Yes |
B. Binary Search (Efficient for Sorted Data)
How it works: Repeatedly divide the search interval in half. Time Complexity: (must be sorted).
Code (Recursive):
def binary_search(arr, target):
low, high = 0, len(arr)-1
while low <= high:
mid = (low + high) // 2
if arr[mid] == target:
return mid
elif arr[mid] < target:
low = mid + 1
else:
high = mid - 1
return -1
Trace for [1,3,5,7,9] searching 5:
| Step | Low | High | Mid | Mid Value | Action |
|---|---|---|---|---|---|
| 1 | 0 | 4 | 2 | 5 | Found at index 2 |
In the Real World
eSewa (Nepal):
- Uses sorted transaction queues to prioritize payments (e.g., electricity bills, fines).
- Binary search optimizes user lookup in the database of 10M+ accounts.
Ncell (Telecom):
- Merge sort organizes call logs by timestamp for billing reports.
- Quick sort ranks customers by data usage for targeted promotions.
Daraz (E-Commerce):
- Linear search checks inventory for product availability in real-time.
- Hash tables (with chaining) store user profiles for access.
NEPSE (Stock Exchange):
- Binary search trees maintain share prices for efficient trading order matching.
- Heap sort ranks stocks by volatility for portfolio analysis.
Worked Example: Sorting Ncell Customer Data
Scenario:
Ncell has 5 customers with data usage (in GB): [3.2, 1.5, 4.7, 0.9, 2.1]. Sort to identify top 3 users for a bonus.
Solution: Use Quick Sort (in-place, efficient for medium-sized data).
data = [3.2, 1.5, 4.7, 0.9, 2.1]
quick_sort(data) # Sorted: [0.9, 1.5, 2.1, 3.2, 4.7]
top_3 = data[:3] # [0.9, 1.5, 2.1] → Wait, this is the lowest! Correct approach:
Correction: To get highest usage, sort in descending order:
quick_sort(data, reverse=True) # [4.7, 3.2, 2.1, 1.5, 0.9]
top_3 = data[:3] # [4.7, 3.2, 2.1]
Output: Ncell rewards customers with usage > 2.1 GB.
Comparison Table: Sorting Algorithms
| Algorithm | Best Case | Avg Case | Worst Case | Space Complexity | Stable? | In-Place? | Notes |
|---|---|---|---|---|---|---|---|
| Bubble Sort | Yes | Yes | Simple but slow. | ||||
| Merge Sort | Yes | No | Stable, good for linked lists. | ||||
| Quick Sort | * | No | Yes | Fastest in practice. | |||
| Insertion Sort | Yes | Yes | Efficient for small/nearly sorted data. |
*Stack space for recursion.
Searching Algorithm Comparison
| Algorithm | Time Complexity | Space Complexity | Requirements | Use Case |
|---|---|---|---|---|
| Linear Search | None | Unsorted data, small datasets. | ||
| Binary Search | Sorted data | Large sorted datasets (e.g., phone books). | ||
| Hash Table | avg | Hash function | Fast lookups (e.g., dictionaries). |
Exam Tip
Define Clearly:
- Binary search: "A divide-and-conquer algorithm that repeatedly halves the search space by comparing the target to the middle element of a sorted array."
- Stable sort: "Preserves the relative order of equal elements."
Pseudocode > Code:
- Exams often ask for iterative/recursive algorithms in plain English. Focus on steps, not syntax.
Complexity is Key:
- Always state best/average/worst case for sorting/searching.
- Example: "Merge sort has time in all cases but requires auxiliary space."
Real-World Links:
- Relate to Nepalese systems:
- "eSewa uses hash tables for user authentication (O(1) lookup)."
- "Ncell’s billing system sorts transactions (Merge Sort) before generating reports."
- Relate to Nepalese systems:
Trace Tables:
- For binary search, show low/high/mid values at each step.
- For sorting, illustrate array state after each pass.
Common Pitfalls:
- Off-by-one errors in binary search (e.g.,
high = midvshigh = mid - 1). - Unsorted input for binary search (always check!).
- Quick Sort pivot choice: Randomized pivot avoids worst-case .
- Off-by-one errors in binary search (e.g.,
Practice Questions (Exam Style)
Define:
- What is a stable sorting algorithm? Give an example.
- Explain why linear search is in the worst case.
Algorithm Design:
- Write the iterative binary search algorithm and analyze its time complexity.
- Convert the recursive Quick Sort to an iterative version using a stack.
Application:
- "NTC needs to sort 10,000 electricity bill records by due date. Recommend a sorting algorithm and justify your choice."
- "Pathao’s ride-matching system uses a priority queue to assign drivers. Which sorting algorithm underlies this queue?"
Trace:
- Perform one pass of Bubble Sort on
[64, 34, 25, 12, 22, 11, 90]. - Trace binary search for
[5, 10, 15, 20, 25]searching15.
- Perform one pass of Bubble Sort on
Comparison:
- "Why is Merge Sort preferred over Quick Sort for sorting linked lists?"
- "When would you use Linear Search over Binary Search?"
Based on the TU BSc CSIT syllabus for Design and Analysis of Algorithms (CSC314), unit 6.
Discussion
Loading…