Chapter 17 – Advanced Sorting Algorithms
Chapter 17: Advanced Sorting Algorithms Welcome back, dear reader! Now that we’ve dipped our toes into the world of simple sorting algorithms, it’s time to dive a little deeper. In this chapter, we’ll explore some more powerful sorting algorithms that are designed to handle larger datasets efficiently. Before we jump in, take a moment to appreciate how far you’ve come! Sorting is one of the most important concepts in data structures, and understanding it thoroughly gives you a solid foundation for tackling more complex problems. So, what’s next? Let’s begin with a sorting algorithm that’s fast, efficient, and widely used in real-world applications — Quick Sort. 17.1 Quick Sort Definition: Quick Sort is a divide-and-conquer algorithm. It works by selecting a "pivot" element from the array and partitioning the other elements into two sub-arrays: those smaller than the pivot and those larger. The pivot is then placed in its correct position, and the process is recursively applied to the sub-arrays. How It Works: Quick Sort might sound a little tricky, but let’s break it down step-by-step: Pick an element from the array. This is called the pivot. Partition the array into two parts: elements less than the pivot go to the left, and elements greater than the pivot go to the right. Recursively apply Quick Sort to the left and right sub-arrays. Combine the results, and you have a sorted array! Let’s walk through an example to see how Quick Sort works in action. Quick Sort Example: Imagine we have the following array: [30, 10, 50, 20, 60, 40] We choose 30 as our pivot (you can choose any element, but for simplicity, we’ll take the first one). Elements less than 30: [10, 20] Elements greater than 30: [50, 60, 40] Now we recursively sort these two sub-arrays. The left sub-array [10, 20] is already sorted, so no further action is needed there. But the right sub-array [50, 60, 40] needs sorting. We choose 50 as the pivot for this sub-array: Elements less than 50: [40] Elements greater than 50: [60] Now, both [40] and [60] are sorted individually. We combine everything, and voila! The final sorted array is: [10, 20, 30, 40, 50, 60] See how it works? It’s like dividing the problem into smaller pieces until it’s easy to solve, and then putting everything back together. Time Complexity: Best case: O(n log n) — This happens when the pivot divides the array into two nearly equal parts each time. Worst case: O(n²) — This occurs when the pivot is always the smallest or largest element, leading to unbalanced partitions. Average case: O(n log n) — Generally, Quick Sort is quite efficient, and this is the expected performance for most datasets. Quick Sort Implementation in C: Let’s write a Quick Sort program in C, but instead of using just arrays, we’ll also show you how to use pointers. Pointers are powerful tools in C, and they make managing memory much easier. #include <stdio.h> // Function to swap two elements using pointers void swap(int *a, int *b) { int temp = *a; *a = *b; *b = temp; } // Partition function that returns the index of the pivot int partition(int arr[], int low, int high) { int pivot = arr[high]; // Choosing the last element as the pivot int i = (low – 1); // Index of the smaller element for (int j = low; j <= high – 1; j++) { if (arr[j] < pivot) { i++; // Increment the index of the smaller element swap(&arr[i], &arr[j]); } } swap(&arr[i + 1], &arr[high]); return (i + 1); } // Quick Sort function that uses recursion void quickSort(int arr[], int low, int high) { if (low < high) { // Get the pivot element in its correct position int pi = partition(arr, low, high); // Recursively sort the left and right sub-arrays quickSort(arr, low, pi – 1); quickSort(arr, pi + 1, high); } } // Function to print the array void printArray(int arr[], int size) { for (int i = 0; i < size; i++) { printf("%d ", arr[i]); } printf("\n"); } int main() { int arr[] = {30, 10, 50, 20, 60, 40}; int n = sizeof(arr) / sizeof(arr[0]); printf("Original array: \n"); printArray(arr, n); quickSort(arr, 0, n – 1); printf("Sorted array: \n"); printArray(arr, n); return 0; } Explanation: The partition() function is the heart of the Quick Sort algorithm. It rearranges the array by moving smaller elements to the left of the pivot and larger elements to the right. We use pointers in the swap() function to directly modify the elements of the array in memory. The quickSort() function is recursive and sorts the array by dividing it into smaller sub-arrays around the pivot. Why Use Quick Sort? Quick Sort is fast and works well for most real-world data. It’s not only good for small datasets but also scales efficiently for larger ones. And since it’s a divide-and-conquer algorithm, it can be optimized for parallel processing. But wait! We’re not done yet. Next, we’ll look at another very important sorting algorithm — Merge Sort. This one is also based on the divide-and-conquer approach, but it works in a slightly different way. Should we continue with Merge Sort? I promise it’s super interesting! Alright! Let’s keep the momentum going and dive into Merge Sort — another powerful sorting algorithm that you’ll often encounter in the real world. If you liked the divide-and-conquer strategy of Quick Sort, you’re going to love Merge Sort because it takes this strategy to the next level with guaranteed performance. 17.2 Merge Sort Definition: Merge Sort is a divide-and-conquer algorithm that breaks the array into smaller sub-arrays, sorts those sub-arrays, and then merges them back together in the correct order. The key idea in Merge Sort is that merging two sorted arrays is easier than sorting a large unsorted array. So instead of sorting the entire array in one go, we: Break the array into two halves. Recursively sort both halves. Merge the two










