|
| 1 | +# Sorting Algorithms |
| 2 | + |
| 3 | +In computer science, a sorting algorithm takes a collection of items and arranges them in a specific order. This order is usually determined by comparing the items using a defined rule. |
| 4 | + |
| 5 | +## Real Life Example of Sorting |
| 6 | +- Sorting a deck of cards |
| 7 | +- Sorting names in alphabetical order |
| 8 | +- Sorting a list of items, etc. |
| 9 | + |
| 10 | +# Some common sorting techniques |
| 11 | + |
| 12 | +# 1. Bubble Sort |
| 13 | + |
| 14 | +Bubble sort is a basic sorting technique that iteratively steps through a list, comparing neighboring elements. If elements are out of order, it swaps them. While easy to understand, bubble sort becomes inefficient for large datasets due to its slow execution time. |
| 15 | + |
| 16 | +**Algorithm Overview:** |
| 17 | +- **Pass by Pass:** During each pass, the algorithm iterates through the list. |
| 18 | +- **Comparing Neighbors:** In each iteration, it compares adjacent elements in the list. |
| 19 | +- **Swapping for Order:** If the elements are in the wrong order (typically, the first being larger than the second), it swaps their positions. |
| 20 | +- **Bubbling Up the Largest:** This swapping process effectively pushes the largest element encountered in a pass towards the end of the list, like a bubble rising in water. |
| 21 | +- **Repeating Until Sorted:** The algorithm continues making passes through the list until no more swaps are needed. This indicates the entire list is sorted. |
| 22 | + |
| 23 | + |
| 24 | +## Bubble Sort Code in Python |
| 25 | + |
| 26 | +```python |
| 27 | +def bubble_sort(arr): |
| 28 | + n = len(arr) |
| 29 | + for i in range(n): |
| 30 | + for j in range(0, n-i-1): |
| 31 | + if arr[j] > arr[j+1]: |
| 32 | + arr[j], arr[j+1] = arr[j+1], arr[j] |
| 33 | + |
| 34 | +arr = [5, 3, 8, 1, 2] |
| 35 | +bubble_sort(arr) |
| 36 | +print("Sorted array:", arr) # Output: [1, 2, 3, 5, 8] |
| 37 | +``` |
| 38 | +## Example with Visualization |
| 39 | + |
| 40 | +Let's sort the list `[5, 3, 8, 1, 2]` using bubble sort. |
| 41 | + |
| 42 | +1. **Pass 1:** |
| 43 | + - Comparing neighbors: `[3, 5, 1, 2, 8]` |
| 44 | + - Swapping: `[3, 5, 1, 2, 8]` → `[3, 1, 5, 2, 8]` → `[3, 1, 2, 5, 8]` |
| 45 | + - Result: `[3, 1, 2, 5, 8]` |
| 46 | + |
| 47 | +2. **Pass 2:** |
| 48 | + - Comparing neighbors: `[1, 3, 2, 5, 8]` |
| 49 | + - Swapping: `[1, 3, 2, 5, 8]` → `[1, 2, 3, 5, 8]` |
| 50 | + - Result: `[1, 2, 3, 5, 8]` |
| 51 | + |
| 52 | +3. **Pass 3:** |
| 53 | + - Comparing neighbors: `[1, 2, 3, 5, 8]` |
| 54 | + - No swapping needed, the list is already sorted. |
| 55 | + |
| 56 | +## Complexity Analysis |
| 57 | + |
| 58 | +- **Worst Case:** `O(n^2)` comparisons and swaps. This happens when the list is in reverse order, and we need to make maximum swaps. |
| 59 | +- **Best Case:** `O(n)` comparisons. This occurs when the list is already sorted, but we still need O(n^2) swaps because of the nested loops. |
| 60 | +- **Average Case:** `O(n^2)` comparisons and swaps. This is the expected number of comparisons and swaps over all possible input sequences. |
| 61 | + |
| 62 | +</br> |
| 63 | +<hr> |
| 64 | +</br> |
| 65 | + |
| 66 | +# 2. Selection Sort |
| 67 | + |
| 68 | +Selection sort is a simple sorting algorithm that divides the input list into two parts: a sorted sublist and an unsorted sublist. The algorithm repeatedly finds the smallest (or largest, depending on sorting order) element from the unsorted sublist and moves it to the sorted sublist. It's not efficient for large datasets but performs better than bubble sort due to fewer swaps. |
| 69 | + |
| 70 | +**Algorithm Overview:** |
| 71 | +- **Initial State:** The entire list is considered unsorted initially. |
| 72 | +- **Selecting the Minimum:** The algorithm repeatedly selects the smallest element from the unsorted sublist and moves it to the sorted sublist. |
| 73 | +- **Expanding the Sorted Sublist:** As elements are moved to the sorted sublist, it expands until all elements are sorted. |
| 74 | +- **Repeating Until Sorted:** The process continues until the entire list is sorted. |
| 75 | + |
| 76 | +## Example with Visualization |
| 77 | + |
| 78 | +Let's sort the list `[5, 3, 8, 1, 2]` using selection sort. |
| 79 | + |
| 80 | +1. **Pass 1:** |
| 81 | + - Initial list: `[5, 3, 8, 1, 2]` |
| 82 | + - Find the minimum: `1` |
| 83 | + - Swap with the first element: `[1, 3, 8, 5, 2]` |
| 84 | + |
| 85 | +2. **Pass 2:** |
| 86 | + - Initial list: `[1, 3, 8, 5, 2]` |
| 87 | + - Find the minimum: `2` |
| 88 | + - Swap with the second element: `[1, 2, 8, 5, 3]` |
| 89 | + |
| 90 | +3. **Pass 3:** |
| 91 | + - Initial list: `[1, 2, 8, 5, 3]` |
| 92 | + - Find the minimum: `3` |
| 93 | + - Swap with the third element: `[1, 2, 3, 5, 8]` |
| 94 | + |
| 95 | +4. **Pass 4:** |
| 96 | + - Initial list: `[1, 2, 3, 5, 8]` |
| 97 | + - Find the minimum: `5` |
| 98 | + - No swapping needed, the list is already sorted. |
| 99 | + |
| 100 | +## Selection Sort Code in Python |
| 101 | + |
| 102 | +```python |
| 103 | +def selection_sort(arr): |
| 104 | + n = len(arr) |
| 105 | + for i in range(n): |
| 106 | + min_index = i |
| 107 | + for j in range(i+1, n): |
| 108 | + if arr[j] < arr[min_index]: |
| 109 | + min_index = j |
| 110 | + arr[i], arr[min_index] = arr[min_index], arr[i] |
| 111 | + |
| 112 | +arr = [5, 3, 8, 1, 2] |
| 113 | +selection_sort(arr) |
| 114 | +print("Sorted array:", arr) # Output: [1, 2, 3, 5, 8] |
| 115 | +``` |
| 116 | + |
| 117 | +## Complexity Analysis |
| 118 | +- **Worst Case**: `O(n^2)` comparisons and O(n) swaps. This occurs when the list is in reverse order, and we need to make maximum comparisons and swaps. |
| 119 | +- **Best Case**: `O(n^2)` comparisons and O(n) swaps. This happens when the list is in sorted order, but the algorithm still needs to iterate through all elements for comparisons. |
| 120 | +- **Average Case**: `O(n^2)` comparisons and O(n) swaps. This is the expected number of comparisons and swaps over all possible input sequences. |
| 121 | +</br> |
| 122 | +<hr> |
| 123 | +</br> |
| 124 | + |
| 125 | +# 3. Quick Sort |
| 126 | +Quick sort is a popular divide-and-conquer sorting algorithm known for its efficiency on average. It works by selecting a 'pivot' element from the array and partitioning the other elements into two sub-arrays according to whether they are less than or greater than the pivot. The sub-arrays are then recursively sorted. |
| 127 | + |
| 128 | +**Algorithm Overview:** |
| 129 | +- **Pivot Selection:** Choose a pivot element from the array. Common strategies include selecting the first, last, middle, or a randomly chosen element. |
| 130 | +- **Partitioning:** Rearrange the array so that all elements less than the pivot are on its left, and all elements greater than the pivot are on its right. This step ensures that the pivot element is placed in its correct sorted position. |
| 131 | +- **Recursion:** Apply the above steps recursively to the sub-arrays formed by partitioning until the base case is reached. The base case is usually when the size of the sub-array becomes 0 or 1, indicating it is already sorted. |
| 132 | +- **Base Case:** If the sub-array size becomes 0 or 1, it is already sorted. |
| 133 | + |
| 134 | +## Example with Visualization |
| 135 | + |
| 136 | +Let's sort the list `[5, 3, 8, 1, 2]` using quick sort. |
| 137 | + |
| 138 | +1. **Initial Array:** `[5, 3, 8, 1, 2]` |
| 139 | + |
| 140 | +2. **Choose Pivot:** Let's choose the last element, `2`, as the pivot. |
| 141 | + |
| 142 | +3. **Partitioning:** |
| 143 | + - We'll partition the array around the pivot `2`. All elements less than `2` will be placed to its left, and all elements greater than `2` will be placed to its right. |
| 144 | + |
| 145 | + - After partitioning, the array becomes `[1, 2, 5, 3, 8]`. The pivot element, `2`, is now in its correct sorted position. |
| 146 | + |
| 147 | +4. **Recursion:** |
| 148 | + - Now, we recursively sort the sub-arrays `[1]` and `[5, 3, 8]`. |
| 149 | + - For the sub-array `[5, 3, 8]`, we choose `8` as the pivot and partition it. |
| 150 | + - After partitioning, the sub-array becomes `[3, 5, 8]`. The pivot element, `8`, is now in its correct sorted position. |
| 151 | + |
| 152 | + |
| 153 | +5. **Concatenation:** |
| 154 | + - Concatenating the sorted sub-arrays `[1]`, `[2]`, `[3, 5, 8]`, we get the final sorted array `[1, 2, 3, 5, 8]`. |
| 155 | + |
| 156 | +## Quick Sort Code in Python (Iterative) |
| 157 | +```python |
| 158 | +def partition(arr, low, high): |
| 159 | + pivot = arr[high] |
| 160 | + i = low - 1 |
| 161 | + for j in range(low, high): |
| 162 | + if arr[j] < pivot: |
| 163 | + i += 1 |
| 164 | + arr[i], arr[j] = arr[j], arr[i] |
| 165 | + arr[i + 1], arr[high] = arr[high], arr[i + 1] |
| 166 | + return i + 1 |
| 167 | + |
| 168 | +def quick_sort_iterative(arr): |
| 169 | + stack = [(0, len(arr) - 1)] |
| 170 | + while stack: |
| 171 | + low, high = stack.pop() |
| 172 | + if low < high: |
| 173 | + pi = partition(arr, low, high) |
| 174 | + stack.append((low, pi - 1)) |
| 175 | + stack.append((pi + 1, high)) |
| 176 | + |
| 177 | +# Example usage: |
| 178 | +arr = [38, 27, 43, 3, 9, 82, 10] |
| 179 | +quick_sort_iterative(arr) |
| 180 | +print("Sorted array:", arr) # Output: [3, 9, 10, 27, 38, 43, 82] |
| 181 | + |
| 182 | +``` |
| 183 | +## Quick Sort Code in Python (Recursive) |
| 184 | + |
| 185 | +```python |
| 186 | +def quick_sort(arr): |
| 187 | + if len(arr) <= 1: |
| 188 | + return arr |
| 189 | + else: |
| 190 | + pivot = arr[-1] |
| 191 | + left = [x for x in arr[:-1] if x < pivot] |
| 192 | + right = [x for x in arr[:-1] if x >= pivot] |
| 193 | + return quick_sort(left) + [pivot] + quick_sort(right) |
| 194 | + |
| 195 | +arr = [5, 3, 8, 1, 2] |
| 196 | +sorted_arr = quick_sort(arr) |
| 197 | +print("Sorted array:", sorted_arr) # Output: [1, 2, 3, 5, 8] |
| 198 | +``` |
| 199 | +## Complexity Analysis |
| 200 | + |
| 201 | +- **Worst Case**: The worst-case time complexity of quick sort is `O(n^2)`. This occurs when the pivot selection consistently results in unbalanced partitioning, such as choosing the smallest or largest element as the pivot. |
| 202 | +-**Best Case**: The best-case time complexity is `O(n log n)`. This happens when the pivot selection leads to well-balanced partitioning, halving the array size in each recursive call. |
| 203 | +- **Average Case**: The average-case time complexity is `O(n log n)`. This is the expected time complexity when the pivot selection results in reasonably balanced partitioning across recursive calls. |
| 204 | +- **Space Complexity**: Quick sort has an `O(log n)` space complexity for the recursion stack, as it recursively sorts sub-arrays. |
| 205 | +</br> |
| 206 | +<hr> |
| 207 | +</br> |
| 208 | + |
| 209 | +# 4. Merge Sort |
| 210 | + |
| 211 | +Merge sort is a divide-and-conquer algorithm that recursively divides the input list into smaller sublists until each sublist contains only one element. Then, it repeatedly merges adjacent sublists while maintaining the sorted order until there is only one sublist remaining, which represents the sorted list. |
| 212 | + |
| 213 | +**Algorithm Overview:** |
| 214 | +- **Divide:** Split the input list into smaller sublists recursively until each sublist contains only one element. |
| 215 | +- **Merge:** Repeatedly merge adjacent sublists while maintaining the sorted order until there is only one sublist remaining, which represents the sorted list. |
| 216 | + |
| 217 | +## Example with Visualization |
| 218 | + |
| 219 | +Let's sort the list `[38, 27, 43, 3, 9, 82, 10]` using merge sort. |
| 220 | + |
| 221 | +1. **Initial Division:** |
| 222 | + - Divide the list into sublists: `[38, 27, 43, 3, 9, 82, 10]` |
| 223 | + - Visually it looks like |
| 224 | + `[38], [27], [43], [3], [9], [82], [10]` |
| 225 | + |
| 226 | +2. **Merge Passes:** |
| 227 | + - Merge adjacent sublists while maintaining sorted order: |
| 228 | + - Pass 1: `[27, 38]`, `[3, 43]`, `[9, 82]`, `[10]` |
| 229 | + - Pass 2: `[3, 27, 38, 43]`, `[9, 10, 82]` |
| 230 | + - Pass 3: `[3, 9, 10, 27, 38, 43, 82]` |
| 231 | + |
| 232 | + |
| 233 | +3. **Final Sorted List:** |
| 234 | + - `[3, 9, 10, 27, 38, 43, 82]` |
| 235 | + |
| 236 | +## Merge Sort Code in Python (Iterative) |
| 237 | + |
| 238 | +```python |
| 239 | +def merge_sort_iterative(arr): |
| 240 | + n = len(arr) |
| 241 | + curr_size = 1 |
| 242 | + while curr_size < n: |
| 243 | + left = 0 |
| 244 | + while left < n - 1: |
| 245 | + mid = min(left + curr_size - 1, n - 1) |
| 246 | + right = min(left + 2 * curr_size - 1, n - 1) |
| 247 | + merge(arr, left, mid, right) |
| 248 | + left += 2 * curr_size |
| 249 | + curr_size *= 2 |
| 250 | + |
| 251 | +def merge(arr, left, mid, right): |
| 252 | + n1 = mid - left + 1 |
| 253 | + n2 = right - mid |
| 254 | + L = [0] * n1 |
| 255 | + R = [0] * n2 |
| 256 | + for i in range(n1): |
| 257 | + L[i] = arr[left + i] |
| 258 | + for j in range(n2): |
| 259 | + R[j] = arr[mid + 1 + j] |
| 260 | + i = j = 0 |
| 261 | + k = left |
| 262 | + while i < n1 and j < n2: |
| 263 | + if L[i] <= R[j]: |
| 264 | + arr[k] = L[i] |
| 265 | + i += 1 |
| 266 | + else: |
| 267 | + arr[k] = R[j] |
| 268 | + j += 1 |
| 269 | + k += 1 |
| 270 | + while i < n1: |
| 271 | + arr[k] = L[i] |
| 272 | + i += 1 |
| 273 | + k += 1 |
| 274 | + while j < n2: |
| 275 | + arr[k] = R[j] |
| 276 | + j += 1 |
| 277 | + k += 1 |
| 278 | + |
| 279 | +arr = [38, 27, 43, 3, 9, 82, 10] |
| 280 | +merge_sort_iterative(arr) |
| 281 | +print("Sorted array:", arr) # Output: [3, 9, 10, 27, 38, 43, 82] |
| 282 | +``` |
| 283 | +## Merge Sort Code in Python (Recursive) |
| 284 | +```python |
| 285 | +def merge_sort(arr): |
| 286 | + if len(arr) > 1: |
| 287 | + mid = len(arr) // 2 |
| 288 | + left_half = arr[:mid] |
| 289 | + right_half = arr[mid:] |
| 290 | + |
| 291 | + merge_sort(left_half) |
| 292 | + merge_sort(right_half) |
| 293 | + |
| 294 | + i = j = k = 0 |
| 295 | + |
| 296 | + # Merge the two sorted halves |
| 297 | + while i < len(left_half) and j < len(right_half): |
| 298 | + if left_half[i] < right_half[j]: |
| 299 | + arr[k] = left_half[i] |
| 300 | + i += 1 |
| 301 | + else: |
| 302 | + arr[k] = right_half[j] |
| 303 | + j += 1 |
| 304 | + k += 1 |
| 305 | + |
| 306 | + # Check if any elements are remaining in the left half |
| 307 | + while i < len(left_half): |
| 308 | + arr[k] = left_half[i] |
| 309 | + i += 1 |
| 310 | + k += 1 |
| 311 | + |
| 312 | + # Check if any elements are remaining in the right half |
| 313 | + while j < len(right_half): |
| 314 | + arr[k] = right_half[j] |
| 315 | + j += 1 |
| 316 | + k += 1 |
| 317 | + |
| 318 | +# Example usage: |
| 319 | +arr = [38, 27, 43, 3, 9, 82, 10] |
| 320 | +merge_sort(arr) |
| 321 | +print("Sorted array:", arr) # Output: [3, 9, 10, 27, 38, 43, 82] |
| 322 | +``` |
| 323 | +## Complexity Analysis |
| 324 | +- **Time Complexity**: `O(n log n)` for all cases. Merge sort always divides the list into halves until each sublist contains only one element, and then merges them back together, resulting in O(n log n) time complexity. |
| 325 | +- **Space Complexity**: `O(n)` auxiliary space. In the iterative version, merge sort uses additional space for creating temporary sublists during merging operations. |
| 326 | + |
| 327 | +</br> |
| 328 | +<hr> |
| 329 | +</br> |
0 commit comments