Lesson 4 — Advanced

Sorting: Merge, Quick, Counting, Radix

Sorting algorithms DSA ka foundation hai. Comparison-based sorts (merge, quick) O(n log n) pe limit hain, jabki non-comparison sorts (counting, radix) O(n) tak ja sakte hain khaas cases mein.

? 30 min✓ Advanced✓ Recursion, Arrays

Comparison vs Non-Comparison Sorts

Sorting algorithms do categories mein aate hain:

Interview mein sabse zyada puchhe jaate hain: Merge Sort, Quick Sort, Counting Sort. Inhe deeply samjho.

Merge Sort: Divide and Conquer

Merge sort array ko do halves mein break karta hai, recursively sort karta hai, phir merge karta hai. Stable sort hai — equal elements ka relative order preserve hota hai.

Steps:

  1. Array ko mid pe todo.
  2. Left half ko recursively sort karo.
  3. Right half ko recursively sort karo.
  4. Dono sorted halves ko merge karo.
Merge Sort Visualization:

 [38, 27, 43, 3, 9, 82, 10]
 / \
 [38, 27, 43, 3] [9, 82, 10]
 / \ / \
 [38, 27] [43, 3] [9, 82] [10]
 / \ / \ / \ |
 [38] [27] [43] [3] [9] [82] [10]
 \ / \ / \ / |
 [27, 38] [3, 43] [9, 82] [10]
 \ / \ /
 [3, 27, 38, 43] [9, 10, 82]
 \ /
 [3, 9, 10, 27, 38, 43, 82]

Time: O(n log n) always
Space: O(n) — extra array chahiye
Stable: Yes

Code: Merge Sort

Python
def merge_sort(arr):
 if len(arr) <= 1:
 return arr

 mid = len(arr) // 2
 left = merge_sort(arr[:mid])
 right = merge_sort(arr[mid:])

 return merge(left, right)


def merge(left, right):
 result = []
 i = j = 0

 while i < len(left) and j < len(right):
 if left[i] <= right[j]:
 result.append(left[i])
 i += 1
 else:
 result.append(right[j])
 j += 1

 # Baaki elements add karo
 result.extend(left[i:])
 result.extend(right[j:])
 return result


# Example
arr = [38, 27, 43, 3, 9, 82, 10]
print(merge_sort(arr))
# Output: [3, 9, 10, 27, 38, 43, 82]

Quick Sort: Partition-Based

Quicksort ek pivot choose karta hai, array ko partition karta hai (chote elements left, bade right), phir recursively sort karta hai. In-place sort hai — extra space nahi chahiye.

Pivot selection sabse important hai. Agar pivot hamesha middle element ho toh average case O(n log n) hai. Worst case O(n—) hai jab pivot sabse chota ya bada ho (already sorted array).

Quick Sort Visualization:

Array: [10, 80, 30, 90, 40, 50, 70]
Pivot: 70 (last element)

Partition:
 [< 70] 70 [> 70]
 [10, 30, 40, 50] 70 [80, 90]

Recursively sort:
 Left: [10, 30, 40, 50] ✓ pivot 50 ? [10, 30, 40] 50
 Right: [80, 90] ✓ already sorted

Final: [10, 30, 40, 50, 70, 80, 90]

Time: O(n log n) average, O(n—) worst
Space: O(log n) — recursion stack
Stable: No (in-place partition mein order change hota hai)

Code: Quick Sort

Python
def quick_sort(arr):
 if len(arr) <= 1:
 return arr

 pivot = arr[len(arr) // 2]
 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)


# In-place version (Lomuto partition)
def quick_sort_inplace(arr, low, high):
 if low < high:
 pi = partition(arr, low, high)
 quick_sort_inplace(arr, low, pi - 1)
 quick_sort_inplace(arr, pi + 1, high)


def partition(arr, low, high):
 pivot = arr[high]
 i = low - 1

 for j in range(low, high):
 if arr[j] <= pivot:
 i += 1
 arr[i], arr[j] = arr[j], arr[i]

 arr[i + 1], arr[high] = arr[high], arr[i + 1]
 return i + 1


# Example
arr = [10, 80, 30, 90, 40, 50, 70]
print(quick_sort(arr))
# Output: [10, 30, 40, 50, 70, 80, 90]

Counting Sort: Non-Comparison

Counting sort comparison use nahi karta. Agar elements known range mein hain (jaise 0-9), toh frequency count karo aur direct placement karo. O(n + k) time — n = elements, k = range.

Code: Counting Sort

Python
def counting_sort(arr):
 if not arr:
 return arr

 max_val = max(arr)
 min_val = min(arr)
 range_val = max_val - min_val + 1

 # Frequency array banao
 count = [0] * range_val
 output = [0] * len(arr)

 # Count occurrences
 for num in arr:
 count[num - min_val] += 1

 # Cumulative count
 for i in range(1, len(count)):
 count[i] += count[i - 1]

 # Build output (reverse traversal for stability)
 for num in reversed(arr):
 count[num - min_val] -= 1
 output[count[num - min_val]] = num

 return output


# Example
arr = [4, 2, 2, 8, 3, 3, 1]
print(counting_sort(arr))
# Output: [1, 2, 2, 3, 3, 4, 8]

Radix Sort

Radix sort digits ke hisaab se sort karta hai — pehle ones place, phir tens, phir hundreds. Har pass mein Counting Sort use hota hai as subroutine. O(d * (n + k)) time — d = digits, n = elements, k = base (10 for decimal).

Time Complexity Comparison

Algorithm Best Average Worst Space Stable
-----------------------------------------------------------------
Bubble Sort O(n) O(n—) O(n—) O(1) Yes
Insertion Sort O(n) O(n—) O(n—) O(1) Yes
Merge Sort O(nlogn) O(nlogn) O(nlogn) O(n) Yes
Quick Sort O(nlogn) O(nlogn) O(n—) O(logn) No
Heap Sort O(nlogn) O(nlogn) O(nlogn) O(1) No
Counting Sort O(n+k) O(n+k) O(n+k) O(n+k) Yes
Radix Sort O(dn) O(dn) O(dn) O(n+k) Yes

Interview mein yaad rakho:
- Default: Merge Sort (guaranteed O(n log n), stable)
- Space constraint: Quick Sort (in-place)
- Small range integers: Counting Sort (O(n))
- Digits-based: Radix Sort

Try it: code khud likho

Exercise: Implement Quick Sort

Diye gaye array [10, 80, 30, 90, 40, 50, 70] ko quicksort se sort karo.

Expected Output: [10, 30, 40, 50, 70, 80, 90]

Bonus: Merge sort implement karo aur dono ka comparison karo — kaunsa zyada efficient hai different input sizes pe.

Common Mistakes

Sorting algorithms samajh aa gaye✓ Ab greedy problems dekho — real-world problems solve karo jo greedy approach se hal hoti hain.

Next: Top Greedy Problems ?