Algorithms
-
Quick sort Example, \( O(n\ log\ n)\)
-
Bubble sort Example, \( O(n^2)\)
-
Selection Sort Example, \( O(n^2)\)
-
Insertion Sort Example, \(O(n^2) \)
-
All timings are based on an 11th Gen Intel Core i5-11500 @ 2.70GHz 6 Cores.
-
The C library function
clock_t clock(void)
returns the number ofclock ticks
elapsed since the program was launched. To get the number of seconds used by the CPU, you will need to divide byCLOCKS_PER_SEC
, which will be implemented inmain()
.
In this lab we are going to explore various sorting algorithms sorting a small dataset that is meant to describe heights of various people:
Quick Sort Algorithm \(O(n\ log\ n)\):
-
Create a new C folder called
SortingAlgorithms
-
Create a header and C file called
sort.h
,sort.c
andmain.c
.-
The algorithm works by first:
-
Divide and Conquer: Quicksort is a fast, efficient sorting algorithm, that uses a divide-and-conquer strategy to sort an array.
-
Picking a Pivot: It starts by selecting a 'pivot' element from the array, usually the middle element
-
Partitioning: The array is then partitioned into two parts – elements less than the pivot are moved before it, and elements greater than the pivot are moved after it.
-
Recursive Sorting: This partitioning creates a "partial order". The algorithm then recursively applies the same process to the sub-arrays formed by the partition.
-
-
-
Reproduce the following code in sort.h`:
-
Reproduce the following code in
sort.c
:#include "sort.h" void quicksortMiddle(int arr[], int low, int high) { if (low < high) { int pivot = arr[(low + high) / 2]; // Selecting the middle element as the pivot int i = low; // lower bounds of array int j = high; // upper bounds of array int temp; // for swapping elements around // while low is less than hight (number of elements) while (i <= j) { while (arr[i] < pivot) i++; // Moving elements smaller than pivot to the left while (arr[j] > pivot) j--; // Moving elements greater than pivot to the right if (i <= j) { temp = arr[i]; // Swapping elements arr[i] = arr[j]; arr[j] = temp; i++; // move up the array 0 to n j--; // move down the array n to 0 } } // Recursively sort the two partitions if (low < j) quicksortMiddle(arr, low, j); if (i < high) quicksortMiddle(arr, i, high); } } // Utility function to print array void printArray(int arr[], int size) { for (int i = 0; i < size; i++) { printf("%d ", arr[i]); } printf("\n"); }
-
Now you have the first sorting algorithm is set up we need get the
main.c
file ready to invoke the sorting function and calcualte the how long it took. Modify the code,main.c
, to include the following, pay close attention to the comments as they provide verbose guidance:#include "sort.h" int main() { int array1[] = {90, 65, 70, 55, 60, 80}; // partially sorted array of heights int n = sizeof(array1) / sizeof(array1[0]); // get the middle of the array printf("Original Array: \n"); printArray(array1, n); clock_t start, end; // Set up variables to time the algorithm start = clock(); // start the timer // We are iterating over a large number , 10 million times, as this will take nano seconds, and without a lot more code time.h does not go below a micro seconds. We are going turn millseconds and then convert to nano seconds. for (int i = 0; i < 10000000; i++) { // Using the Middle Element as Pivot quicksortMiddle(array1, 0, n - 1); } end = clock(); // end the timer // calculate the time taken - CLOCKS_PER_SEC is time.h macro double cpu_time_used = ((double)((double)end - (double)start)) / CLOCKS_PER_SEC; printf("Sorted with Middle Element as Pivot:\n"); printArray(array1, n); printf("Time taken: %lf", cpu_time_used); return 0; }
-
If you have reproduce the above you should see the following output when you run the program:
- You should see that the original array is sorted and the the time taken.
- Time taken currentlty shows as milliseconds, however this 10^6 times larger than actual time. Remember we repeat the algorithm \(10\cdot10^6\) times, so we need to divide by the same to get the actual time. Timing: \[33.47ns \equiv 33.47 \cdot 10^{-8} = \frac{3347 \cdot 10^{-3}}{10 \cdot 10^6} \leftarrow \frac{0.334700}{10000000}\]
-
The graphic below illustrates how the sorting was performed:
Bubble Sort Algorithm \(O(n\ log\ n)\):
The Bubble sort works by repeatedly stepping through the list to be sorted, comparing each pair of adjacent items and swapping them if they are in the wrong order. This process is repeated until no more swaps are needed, indicating that the list is sorted.
-
Here's how bubble sort works:
-
Start at the beginning of the array.
-
Compare the first two elements. If the first element is greater than the second element, swap them.
-
Move to the next pair of elements and repeat step 2.
-
Continue this process until the end of the array is reached.
-
If any swaps were made during the previous pass through the array, repeat steps 1-4. Otherwise, the array is sorted.
-
Bubble sort works by "bubbling" the largest elements to the end of the array in each pass, hence its name.
-
-
To implement the bubble sort algorithm, revist the
sort.h
and add the prototypevoid bubblesort(int arr[], int n)
-
Next open the
sort.c
file and add thebubblesort()
functionality, place the following between the two functions,quicksortMiddle()
,printArray()
:void quicksortMiddle(int arr[], int low, int high) { ... } void bubbleSort(int arr[], int n) { // Outer loop to traverse the array from the beginning to the second-to-last element for (int i = 0; i < n - 1; ++i) { // Inner loop to compare adjacent elements and perform swaps // The loop runs from the beginning to (n - i - 1) to avoid unnecessary comparisons for (int j = 0; j < n - i - 1; ++j) { // If the current element is greater than the next element, swap them if (arr[j] > arr[j + 1]) { // Swap arr[j] and arr[j+1] int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } } void printArray(int arr[], int size) ...
-
Like before we need to invoke the
bubblesort()
in themain()
, you'll pleased to note we are adding one line, and commenting another, go back tomain.c
: -
If you have reproduce the above you should see the following output when you run the program:
-
You should see that the original array is sorted and the the time taken.
-
Time taken currentlty shows as milliseconds, however this 10^6 times larger than actual time. Remember we repeat the algorithm \(10\cdot10^6\) times, so we need to divide by the same to get the actual time.
-
Timing: \[34.5ns \equiv 33.2 \cdot 10^{-8} = \frac{345 \cdot 10^{-3}}{10 \cdot 10^6} \leftarrow \frac{0.345000}{10000000}\]
-
-
The graphic below illustrates how the sorting was performed:
Selection Sort
Selection sort works by repeatedly finding the minimum element from the unsorted part of the array and moving it to the beginning.
-
How it works:
-
Start with the entire array considered as unsorted.
-
Find the minimum element in the unsorted portion of the array.
-
Swap the minimum element with the first element of the unsorted portion.
-
Move the boundary of the unsorted portion one element to the right.
-
Repeat steps 2-4 until the entire array is sorted.
-
Selection sort is called "selection" because it repeatedly selects the smallest (or largest, depending on the sorting order) element and moves it to its correct position.
-
-
To implement the bubble sort algorithm, revist the
sort.h
and add the prototypevoid selectionsort(int arr[], int n)
-
Next open the
sort.c
file and add theselectionsort()
functionality, place the following between the two functions,bubblesort()
,printArray()
:... void bubblesort(nt arr[], int n){ ... } // Function to perform selection sort on an array void selectionSort(int arr[], int n) { // Outer loop to traverse the array from the beginning to the second-to-last element for (int i = 0; i < n - 1; ++i) { // Assume the current index is the index of the minimum element int min_idx = i; // Inner loop to find the index of the minimum element in the unsorted portion of the array for (int j = i + 1; j < n; ++j) { // If the current element is less than the element at the assumed minimum index, // update the minimum index to the current index if (arr[j] < arr[min_idx]) { min_idx = j; } } // Swap the element at the current index with the element at the minimum index int temp = arr[i]; arr[i] = arr[min_idx]; arr[min_idx] = temp; } } void printArray(int arr[], int size) ...
-
Like before we need to invoke the
selectionSort()
in themain()
, you'll pleased to note we are adding one line, and commenting another, go back tomain.c
: -
If you have reproduce the above you should see the following output when you run the program:
-
You should see that the original array is sorted and the the time taken.
-
Time taken currentlty shows as milliseconds, however this 10^6 times larger than actual time. Remember we repeat the algorithm \(10\cdot10^6\) times, so we need to divide by the same to get the actual time.
-
Timing: \[31.6ns \equiv 31.6 \cdot 10^{-8} = \frac{316 \cdot 10^{-3}}{10 \cdot 10^6} \leftarrow \frac{0.316000}{10000000}\]
-
-
The graphic below illustrates how the sorting was performed:
Insertion Sort
Insertion sort works by building a sorted array one element at a time by repeatedly taking the next element from the unsorted part of the array and inserting it into its correct position in the sorted part.
-
How it works:
-
Start with the second element of the array (assuming the first element is already sorted).
-
Compare the current element with the elements before it in the sorted portion of the array.
-
Shift elements in the sorted portion to the right to make room for the current element, if necessary.
-
Insert the current element into its correct position in the sorted portion.
-
Move to the next element in the unsorted portion and repeat steps 2-4.
-
Repeat this process until the entire array is sorted.
-
-
Insertion sort is like sorting a hand of cards: you pick up one card at a time and insert it into its correct position among the cards you're already holding.
-
To implement the bubble sort algorithm, revist the
sort.h
and add the prototypevoid insertionsort(int arr[], int n)
. -
Next open the
sort.c
file and add theinsertionsort()
functionality, place the following between the two functions,selectionSort()
,printArray()
:... void selectionSort(int arr[], int n) { ... } // Function to perform insertion sort on an array void insertionSort(int arr[], int n) { // Iterate through the array starting from the second element for (int i = 1; i < n; ++i) { // Store the current element in a variable key int key = arr[i]; // Initialize a variable j to track the index of the previous element int j = i - 1; // Move elements of arr[0..i-1], that are greater than key, // to one position ahead of their current position while (j >= 0 && arr[j] > key) { arr[j + 1] = arr[j]; // Move the element to the next position j = j - 1; // Move to the previous element } // Insert the key into its correct position in the sorted part of the array arr[j + 1] = key; } } void printArray(int arr[], int size) ...
-
Like before we need to invoke the
insertionsort()
in themain()
, you'll pleased to note we are adding one line, and commenting another, go back tomain.c
: -
If you have reproduce the above you should see the following output:
-
You should see that the original array is sorted and the the time taken.
-
Time taken currentlty shows as milliseconds, however this 10^6 times larger than actual time. Remember we repeat the algorithm \(10\cdot10^6\) times, so we need to divide by the same to get the actual time.
-
Timing: \[17.5ns \equiv 17.5 \cdot 10^{-8} = \frac{175 \cdot 10^{-3}}{10 \cdot 10^6} \leftarrow \frac{0.175000}{10000000}\]
-
-
The graphic below illustrates how the sorting was performed:
Outcome:
So we can see that the following sorting algorithms can be ranked by time complexity:
- Insertion Sort @ 17.5ns
- Selection Sort @ 31.6ns
- Quick Sort @ 33.47ns
- Bubble Sort @ 34.5ns
Investigation and exploration
-
Try running each algorithm to see if you get different results, record them and work out the average time, either in code or outside.
-
Run each algorithm again using the following two arrays, and again compare the times:
-
Sorted
int array1[] = {55, 60, 65, 70, 80, 90};
-
Revesed Sorted
int array1[] = {90, 80, 70, 65, 60, 55};
-
-
Increase the size of the array and see if the current performance changes accross the each algorithm and rank them.