|
| 1 | +# Sorting algorithms |
| 2 | + |
| 3 | +Sorting are algorithms that put elements of a list in a certain order. It is cruxial to understand the basics of sorting in order to start understanding more complex algorithms and why you have to pay attention to efficiency. |
| 4 | + |
| 5 | +Many of the algorithms will have to swap elements from the array, vector or list. In order to do that, we will need to create a function that swaps two elements. Here is the function: |
| 6 | + |
| 7 | +```c++ |
| 8 | +// A function to swap two elements |
| 9 | +void swap(int *xp, int *yp) { |
| 10 | + int temp = *xp; |
| 11 | + *xp = *yp; |
| 12 | + *yp = temp; |
| 13 | +} |
| 14 | +``` |
| 15 | +
|
| 16 | +## Bubble sort |
| 17 | +
|
| 18 | +Bubble sort is the simplest sorting algorithm that works by repeatedly swapping the adjacent elements if they are in wrong order. |
| 19 | +
|
| 20 | +```c++ |
| 21 | +// A function to implement bubble sort |
| 22 | +void bubbleSort(int arr[], int n) { |
| 23 | + int i, j; |
| 24 | + for (i = 0; i < n-1; i++) |
| 25 | + // Last i elements are already in place |
| 26 | + for (j = 0; j < n-i-1; j++) |
| 27 | + if (arr[j] > arr[j+1]) |
| 28 | + swap(&arr[j], &arr[j+1]); |
| 29 | +} |
| 30 | +``` |
| 31 | + |
| 32 | +As you can see, the algorithm is very simple, but it is not very efficient. It has a time complexity of O(n^2) and a space complexity of O(1). |
| 33 | + |
| 34 | +One of the drawbacks of this algorithm is the sheer amount of swaps. In the worst scenario, it does n^2 swaps, which is a lot. If your machine have slow writes, it will be very slow. |
| 35 | + |
| 36 | +## Insertion sort |
| 37 | + |
| 38 | +Insertion sort is a simple sorting algorithm that works the way we sort playing cards in our hands. You pick one card and insert it in the correct position in the sorted part of the list. You repeat this process until you have sorted the whole list. Here is the code: |
| 39 | + |
| 40 | +```c++ |
| 41 | +// A function to implement insertion sort |
| 42 | +void insertionSort(int arr[], int n) { |
| 43 | + int i, key, j; |
| 44 | + for (i = 1; i < n; i++) { |
| 45 | + key = arr[i]; |
| 46 | + j = i - 1; |
| 47 | + |
| 48 | + /* Move elements of arr[0..i-1], that are |
| 49 | + greater than key, to one position ahead |
| 50 | + of their current position */ |
| 51 | + while (j >= 0 && arr[j] > key) { |
| 52 | + arr[j + 1] = arr[j]; |
| 53 | + j = j - 1; |
| 54 | + } |
| 55 | + arr[j + 1] = key; |
| 56 | + } |
| 57 | +} |
| 58 | +``` |
| 59 | +
|
| 60 | +It falls in the same category of algorithms that are very simple, but not very efficient. It has a time complexity of O(n^2) and a space complexity of O(1). |
| 61 | +
|
| 62 | +Although it have the same complexity as bubble sort, it is a little bit more efficient. It does less swaps than bubble sort, but it is still not very efficient. It will swap all numbers to the left of the current number, which is a lot of swaps. |
| 63 | +
|
| 64 | +## Selection sort |
| 65 | +
|
| 66 | +Selection sort is a simple sorting algorithm. This sorting algorithm is an in-place comparison-based algorithm in which the list is divided into two parts, the sorted part at the left end and the unsorted part at the right end. Initially, the sorted part is empty and the unsorted part is the entire list. The smallest element is selected from the unsorted array and swapped with the leftmost element, and that element becomes a part of the sorted array. This process continues moving unsorted array boundary by one element to the right. Here is the code: |
| 67 | +
|
| 68 | +```c++ |
| 69 | +// A function to implement selection sort |
| 70 | +void selectionSort(int arr[], int n) { |
| 71 | + int i, j, min_idx; |
| 72 | + |
| 73 | + // One by one move boundary of unsorted subarray |
| 74 | + for (i = 0; i < n-1; i++) { |
| 75 | + // Find the minimum element in unsorted array |
| 76 | + min_idx = i; |
| 77 | + for (j = i+1; j < n; j++) |
| 78 | + if (arr[j] < arr[min_idx]) |
| 79 | + min_idx = j; |
| 80 | + |
| 81 | + // Swap the found minimum element with the first element |
| 82 | + swap(&arr[min_idx], &arr[i]); |
| 83 | + } |
| 84 | +} |
| 85 | +``` |
| 86 | + |
| 87 | +It is also a simple algorithm, but it is a little bit more efficient than the previous two. It has a time complexity of O(n^2) and a space complexity of O(1). |
| 88 | + |
| 89 | +It does less swaps than the previous two algorithms, potentially n swaps, but it is still not very efficient. It selects for the current position, the smallest number to the right of it and swaps it with the current number. It does this for every number in the list, which fatally a lot of swaps. |
| 90 | + |
| 91 | +## Merge sort |
| 92 | + |
| 93 | +Merge sort is a divide and conquer algorithm. It divides input array in two halves, calls itself for the two halves and then merges the two sorted halves. Here is the code: |
| 94 | + |
| 95 | +```c++ |
| 96 | +// recursive merge sort |
| 97 | +void mergeSort(int arr[], int l, int r) { |
| 98 | + if (l < r) { |
| 99 | + // Same as (l+r)/2, but avoids overflow for |
| 100 | + // large l and h |
| 101 | + int m = l+(r-l)/2; |
| 102 | + |
| 103 | + // Sort first and second halves |
| 104 | + mergeSort(arr, l, m); |
| 105 | + mergeSort(arr, m+1, r); |
| 106 | + |
| 107 | + merge(arr, l, m, r); |
| 108 | + } |
| 109 | +} |
| 110 | + |
| 111 | +// merge function |
| 112 | +void merge(int arr[], int l, int m, int r) { |
| 113 | + int i, j, k; |
| 114 | + int n1 = m - l + 1; |
| 115 | + int n2 = r - m; |
| 116 | + |
| 117 | + // allocate memory for the sub arrays |
| 118 | + int *L = new int[n1]; |
| 119 | + int *R = new int[n2]; |
| 120 | + |
| 121 | + /* Copy data to temp arrays L[] and R[] */ |
| 122 | + for (i = 0; i < n1; i++) |
| 123 | + L[i] = arr[l + i]; |
| 124 | + for (j = 0; j < n2; j++) |
| 125 | + R[j] = arr[m + 1+ j]; |
| 126 | + |
| 127 | + /* Merge the temp arrays back into arr[l..r]*/ |
| 128 | + i = 0; // Initial index of first subarray |
| 129 | + j = 0; // Initial index of second subarray |
| 130 | + k = l; // Initial index of merged subarray |
| 131 | + while (i < n1 && j < n2) { |
| 132 | + if (L[i] <= R[j]) { |
| 133 | + arr[k] = L[i]; |
| 134 | + i++; |
| 135 | + } |
| 136 | + else { |
| 137 | + arr[k] = R[j]; |
| 138 | + j++; |
| 139 | + } |
| 140 | + k++; |
| 141 | + } |
| 142 | + |
| 143 | + /* Copy the remaining elements of L[], if there are any */ |
| 144 | + while (i < n1) { |
| 145 | + arr[k] = L[i]; |
| 146 | + i++; |
| 147 | + k++; |
| 148 | + } |
| 149 | + |
| 150 | + /* Copy the remaining elements of R[], if there |
| 151 | + are any */ |
| 152 | + while (j < n2) { |
| 153 | + arr[k] = R[j]; |
| 154 | + j++; |
| 155 | + k++; |
| 156 | + } |
| 157 | + |
| 158 | + // deallocate memory |
| 159 | + delete[] L; |
| 160 | + delete[] R; |
| 161 | +} |
| 162 | +``` |
| 163 | +
|
| 164 | +It is a very efficient algorithm that needs extra memory to work. It has a time complexity of O(n*log(n)) and a space complexity of O(n). It is a very efficient algorithm, but it is not very simple. It is quite more complex than the previous algorithms. It is a divide and conquer algorithm, which means that it divides the problem in smaller problems and solves them. It divides the list in two halves, sorts them and then merges them. It does this recursively until it has a list of size 1, which is sorted. Then it merges the lists and returns the sorted list. |
| 165 | +
|
| 166 | +## Quick sort |
| 167 | +
|
| 168 | +Quick sort is a divide and conquer algorithm. It picks an element as pivot and partitions the given array around the picked pivot. Here is the code: |
| 169 | +
|
| 170 | +```c++ |
| 171 | +// recursive quick sort |
| 172 | +void quickSort(int arr[], int low, int high) { |
| 173 | + if (low < high) { |
| 174 | + /* pi is partitioning index, arr[p] is now |
| 175 | + at right place */ |
| 176 | + int pi = partition(arr, low, high); |
| 177 | + |
| 178 | + // Separately sort elements before |
| 179 | + // partition and after partition |
| 180 | + quickSort(arr, low, pi - 1); |
| 181 | + quickSort(arr, pi + 1, high); |
| 182 | + } |
| 183 | +} |
| 184 | +
|
| 185 | +// partition function |
| 186 | +int partition (int arr[], int low, int high) { |
| 187 | + int pivot = arr[high]; // pivot |
| 188 | + int i = (low - 1); // Index of smaller element |
| 189 | + |
| 190 | + for (int j = low; j <= high- 1; j++) { |
| 191 | + // If current element is smaller than or |
| 192 | + // equal to pivot |
| 193 | + if (arr[j] <= pivot) { |
| 194 | + i++; // increment index of smaller element |
| 195 | + swap(&arr[i], &arr[j]); |
| 196 | + } |
| 197 | + } |
| 198 | + swap(&arr[i + 1], &arr[high]); |
| 199 | + return (i + 1); |
| 200 | +} |
| 201 | +``` |
| 202 | + |
| 203 | +It is a very efficient algorithm that don't needs extra memory, which means it is in-place. In average, it can be as fast as mergesort with time complexity of O(n*log(n)), but in the worst case it can be as slow as O(n^2). But it is a better choice if you are not allowed to use extra memory. It is a divide and conquer algorithm, which means that it divides the problem in smaller problems and solves them. It selects a pivot and partitions the list around the pivot. It does this recursively until it has a list of size 1, which is sorted. Then it merges the lists and returns the sorted list. |
| 204 | + |
| 205 | +## Counting sort |
| 206 | + |
| 207 | +Counting sort is a specialized algorithm for sorting numbers. It only works well if you have a small range of numbers. It counts the number of occurrences of each number and then uses the count to place the numbers in the right position. Here is the code: |
| 208 | + |
| 209 | +```c++ |
| 210 | +// counting sort |
| 211 | +void countingSort(int arr[], int n) { |
| 212 | + int max=arr[0]; |
| 213 | + int min[0]; |
| 214 | + |
| 215 | + // find the max and min number |
| 216 | + for(int i=0; i<n; i++) { |
| 217 | + if(arr[i]>max) { |
| 218 | + max=arr[i]; |
| 219 | + } |
| 220 | + if(arr[i]<min) { |
| 221 | + min=arr[i]; |
| 222 | + } |
| 223 | + } |
| 224 | + |
| 225 | + // allocate memory for the count array |
| 226 | + int *count = new int[max-min+1]; |
| 227 | + |
| 228 | + // initialize the count array |
| 229 | + for(int i=0; i<max-min+1; i++) { |
| 230 | + count[i]=0; |
| 231 | + } |
| 232 | + |
| 233 | + // count the number of occurrences of each number |
| 234 | + for(int i=0; i<n; i++) { |
| 235 | + count[arr[i]-min]++; |
| 236 | + } |
| 237 | + |
| 238 | + // place the numbers in the right position |
| 239 | + int j=0; |
| 240 | + for(int i=0; i<max-min+1; i++) { |
| 241 | + while(count[i]>0) { |
| 242 | + arr[j]=i+min; |
| 243 | + j++; |
| 244 | + count[i]--; |
| 245 | + } |
| 246 | + } |
| 247 | + |
| 248 | + // deallocate memory |
| 249 | + delete[] count; |
| 250 | +} |
| 251 | +``` |
| 252 | +
|
| 253 | +Counting sort is a very efficient sorting algorithm which do not rely on comparisons. It has a time complexity of O(n+k) where k is the range of numbers. Space complexity is O(k) which means it is not an in-place sorting algorithm. It is a very efficient algorithm, but it is not very simple. It counts the number of occurrences of each number and then uses the count to place the numbers in the right position. |
| 254 | +
|
| 255 | +## Radix sort |
| 256 | +
|
| 257 | +Radix sort is a specialized algorithm for sorting numbers. It only works well if you have a small range of numbers. It sorts the numbers by their digits. Here is the code: |
| 258 | +
|
| 259 | +```c++ |
| 260 | +// Radix sort |
| 261 | +void radixSort(int arr[], int n) { |
| 262 | + int max=arr[0]; |
| 263 | + |
| 264 | + // find the max number |
| 265 | + for(int i=0; i<n; i++) { |
| 266 | + if(arr[i]>max) { |
| 267 | + max=arr[i]; |
| 268 | + } |
| 269 | + } |
| 270 | + |
| 271 | + // allocate memory for the count array |
| 272 | + int *count = new int[10]; // 10 digits |
| 273 | + |
| 274 | + // allocate memory for the output array |
| 275 | + int *output = new int[n]; |
| 276 | + |
| 277 | + // do counting sort for every digit |
| 278 | + for(int exp=1; max/exp>0; exp*=10) { |
| 279 | + // initialize the count array |
| 280 | + for(int i=0; i<10; i++) { |
| 281 | + count[i]=0; |
| 282 | + } |
| 283 | + |
| 284 | + // count the number of occurrences of each number |
| 285 | + for(int i=0; i<n; i++) { |
| 286 | + count[(arr[i]/exp)%10]++; |
| 287 | + } |
| 288 | + |
| 289 | + // change count[i] so that count[i] now contains actual position of this digit in output[] |
| 290 | + for(int i=1; i<10; i++) { |
| 291 | + count[i]+=count[i-1]; |
| 292 | + } |
| 293 | + |
| 294 | + // build the output array |
| 295 | + for(int i=n-1; i>=0; i--) { |
| 296 | + output[count[(arr[i]/exp)%10]-1]=arr[i]; |
| 297 | + count[(arr[i]/exp)%10]--; |
| 298 | + } |
| 299 | + |
| 300 | + // copy the output array to the input array |
| 301 | + for(int i=0; i<n; i++) { |
| 302 | + arr[i]=output[i]; |
| 303 | + } |
| 304 | + } |
| 305 | +} |
| 306 | +``` |
| 307 | + |
| 308 | +Radix sort is just a counting sort that is applied to every digit. It has a time complexity of O(n*k) where k is the number of digits. |
| 309 | + |
| 310 | +## Conclusion |
| 311 | + |
| 312 | +This is the first time we will talk about efficiency, and for now on, you will start evaluating and taking care about your algorithms' efficiency. You will learn more about efficiency in the next semester and course when we cover data structures. |
0 commit comments