Array Algorithms
Now that you’ve learned how to pass arrays into functions, let’s explore some common algorithms that may be useful for this course. Luckily since Java and C++ share similar syntax, many of the algorithms you encountered in CSCE146 can be easily transferred over to C++ with minor adjustments 🚀.
To keep our code organized, lets create a massive project that holds all our array algorithms. The project will be like a code bank where we can copy and paste array algorithms to other projects as we need them.
Project Setup
Section titled “Project Setup”If you have something similar to the suggested file structure from earlier in the tutorial,
then create a folder called Arrays or Array Algorithms where your Tutorials or Practice code are located.
I’ve gone ahead and listed the filenames that we will be using:
DirectoryCSCE240
DirectoryAssignments
- …
DirectoryLectures
- …
DirectoryTest
- …
DirectoryTutorials
DirectoryArrays
- algorithms.cc
- algorithms.h
- driver.cc
- makefile
- …
Since we know the names of the files for the project, we can go ahead and write the scripts to
compile and link everything in our makefile:
# Built-in variables:# $< = the first prerequisite# $^ = all prerequisites# $@ = the target
compiler = g++flags = -Wall -std=c++17compile = $(compiler) -clink = $(compiler) $(flags)
algorithms.o : algorithms.cc algorithms.h $(compile) $(flags) $<
driver.o : driver.cc algorithms.h $(compile) $(flags) $<
driver : driver.o algorithms.o $(link) $^ ./a.out
clean : rm *.o a.outSearching
Section titled “Searching”In your header file, lets create a couple of prototypes for different searching algorithms. This is the only time I’ll show the full header file. Future additions will only show what code to add:
// Copyright 2024 CSCE240
#ifndef ALGORITHMS_H_#define ALGORITHMS_H_
/* Searching Algorithms */
// Linear
int IndexOf(const int[], int, int);int IndexOf(const char[], char); // char overload
bool Contains(const int[], int, int);bool Contains(const char[], char); // char overload
// Binary
bool BinaryContains(const int[], int, int);bool BinaryContains(const int[], int, int, int); // recursive helper function
#endifNow move over to your source file, algorithms.cc, to write the implementations.
Linear Search
Section titled “Linear Search”We’ll first write the implementation for the linear search functions
since they are pretty straight forward. Starting from the first index
to the last index of the array, if the current value matches a target,
stop the loop and return something.
IndexOf
Section titled “IndexOf”IndexOf is useful when you need to know where an element is located. We
return an integer which represents an index in the array or we return -1
which symbolizes that the target was not found.
Numeric Function
Section titled “Numeric Function”This is the only time I’ll show the complete source file, future snippets will only show the new code to add:
// Copyright 2024 CSCE240
#include "algorithms.h"
#include <iostream>
using std::cout;using std::endl;
/* Searching Algorithms */
// Linear
int IndexOf(const int array[], int size, int target) { for (int i = 0; i < size; ++i) { if (array[i] == target) return i; } return -1;}Char Function
Section titled “Char Function”You may have noticed from the prototype in the header file that we didn’t require an integer for the
size. This is to show how you can use a different approach with char arrays. Instead
of iterating for a set number of times, you can use the null terminator \0 as a
deciding factor for when to end iteration.
// Char Overloadint IndexOf(const char array[], char target) { int i = 0; while (array[i] != '\0') { if (array[i] == target) return i; ++i; } return -1;}We’ve completed the implementation of the IndexOf functions so we can now move to
driver.cc to test these functions. This will be the only time the complete driver
will be shown.
// Copyright 2024 CSCE240
#include <iostream>
#include "algorithms.h"#include "namespaces.h"
using std::cout;using std::endl;
int main() { const int kSize = 5; int arr1[kSize] = {2, 4, 6, 8, 10}; cout << (IndexOf(arr1, kSize, 6) == 2) << endl; cout << (IndexOf(arr1, kSize, 5) == -1) << endl; char arr2[kSize] = "code"; cout << (IndexOf(arr2, 'd') == 2) << endl; cout << (IndexOf(arr2, 'f') == -1) << endl;
return 0;}make driverg++ -c -Wall -std=c++17 driver.ccg++ -c -Wall -std=c++17 algorithms.ccg++ -Wall -std=c++17 driver.o algorithms.o./a.out1111Now that we’ve verified that the functions are working correctly, we can comment out the
cout lines so that we are only getting output for the functions we are testing instead
of everything.
Contains
Section titled “Contains”Contains can be used when you need to know if an element exists in an array. While
iterating we return true if the target element is found. Otherwise we return false
at the end of the function.
Numeric Function
Section titled “Numeric Function”Move back to your source file, algorithms.cc, and add the implementation for the
Contains function:
bool Contains(const int arr[], int size, int target) { for (int i = 0; i < size; ++i) { if (arr[i] == target) return true; } return false;}Char Function
Section titled “Char Function”Again we’ll use the null terminator approach with the overloaded char function:
bool Contains(const char array[], char target) { int i = 0; while (array[i] != '\0') { if (array[i] == target) return true; ++i; } return false;}We can use the previously created arrays to also test the Contains functions:
const int kSize = 5;int arr1[kSize] = {2, 4, 6, 8, 10};cout << (Contains(arr1, kSize, 6) == true) << endl;cout << (Contains(arr1, kSize, 5) == false) << endl;char arr2[kSize] = "code";cout << (Contains(arr2, 'd') == true) << endl;cout << (Contains(arr2, 'f') == false) << endl;make driverg++ -c -Wall -std=c++17 driver.ccg++ -c -Wall -std=c++17 algorithms.ccg++ -Wall -std=c++17 driver.o algorithms.o./a.out1111Now that we know the functions work, we can also comment out every line utilizing cout and arr2.
Binary Search
Section titled “Binary Search”Binary Search is a faster approach to searching than the linear way as it looks to divide & conquer an array by halving it into smaller relevant sections. If you need a refresher, you can view this quick video by Fireship.
I provided the Java version, which should be very similar to the code found in CSCE146, next to the C++ version along with marks to show which lines have been changed. Add the C++ implementation to the source file:
static boolean BinaryContains(int[] array, int target) { return BinaryContains(array, target, 0, array.length - 1);}
// Recursive Helper Functionstatic boolean BinaryContains( int[] array, int target, int startIndex, int endIndex) { if (startIndex > endIndex) return false;
// calculate the midpoint int mid = (startIndex + endIndex) / 2;
if (array[mid] == target) return true; // check to the left of the midpoint else if (array[mid] > target) return BinaryContains( array, target, 0, mid - 1 ); // check to the right of the midpoint else return BinaryContains(array, target, mid + 1, endIndex);}bool BinaryContains(const int array[], int size, int target) { return BinaryContains(array, target, 0, size - 1);}
bool BinaryContains(const int array[], int target, int start_index, int end_index) { if (start_index > end_index) return false;
// calculate the midpoint int mid = (start_index + end_index) / 2;
if (array[mid] == target) return true; // check to the left of the midpoint else if (array[mid] > target) return BinaryContains(array, target, 0, mid - 1); // check to the right of the midpoint else return BinaryContains(array, target, mid + 1, end_index);}Now to test our new function in the driver. Since arr1 is already sorted, we can use it as a testing parameter:
int arr1[kSize] = {2, 4, 6, 8, 10};cout << (BinaryContains(arr1, kSize, 4) == true) << endl;cout << (BinaryContains(arr1, kSize, 9) == false) << endl;make driverg++ -c -Wall -std=c++17 algorithms.ccg++ -Wall -std=c++17 driver.o algorithms.o./a.out11Now that we’ve test all our Searching Algorithms you can comment out the cout statements, and arr1. We’ll create
a new array in the next section.
Sorting
Section titled “Sorting”As you learned in CSCE146, many programs require you to organize data. You’ll probably get at least 1 assignment where sorting a numeric array will either make or break your program. We’ll go over four that you should be familiar with: Bubble Sort, Selection Sort, Merge Sort, and Quick Sort. For each section we’ll follow the same pattern: Declare, Implement, and Test. We’ll also look to sort our arrays using ascending order.
Utility Functions
Section titled “Utility Functions”Before getting into the Sorting Algorithms, lets create a couple of utility functions to help with implementation and
testing. Most algorithms will look to swap elements in a similar way so we’ll create a Swap function. We’ll always need to
print our arrays to check that our functions are working, so we’ll also create a Print function.
-
Declare
Go to your header file
algorithms.hand declare the following utility functions:algorithms.h /* Utility Algorithms */void Print(const int[], int);void Swap(int[], int, int); -
Implement
Now in our source file,
algorithms.cc, add the implementation for each function:algorithms.cc void Swap(int array[], int i, int j) {int temp = array[i];array[i] = array[j];array[j] = temp;}void Print(const int array[], int size) {for (int i = 0; i < size; ++i) {cout << array[i] << endl;}}
BubbleSort
Section titled “BubbleSort”The Bubble Sort algorithm works by comparing each element in an array to the one beside it, then swapping them if they are less than the current element. You start from the front of the array and work your way to the end meaning that the last indices are sorted before the beginning indices. If you prefer a visualized explanation, you can view this short video by Boot Dev.
-
Declare
Lets declare the BubbleSort Function in our header file first:
algorithms.h /* Sorting Algorithms */void BubbleSort(int[], int); -
Implement
Next we define the implementation to the BubbleSort Function in the source file:
algorithms.cc /* Sorting Algorithms */void BubbleSort(int array[], int size) {bool has_swapped = true;while (has_swapped) {has_swapped = false;for (int i = 0; i < size - 1; ++i) {if (array[i] > array[i + 1]) {Swap(array, i, i + 1);has_swapped = true;}}--size;}} -
Test
The final step is to test our functions in the driver file:
drive.cc int array[kSize] = {5, 7, 3, 4, 1,};BubbleSort(array, kSize);Print(array, kSize);Terminal window make driverg++ -c -Wall -std=c++17 driver.ccg++ -c -Wall -std=c++17 algorithms.ccg++ -Wall -std=c++17 driver.o algorithms.o./a.out13457
SelectionSort
Section titled “SelectionSort”The Selection Sort algorithm is another one of the starter sorting algorithms. How it works is that you assume that the current indexed element contains the smallest element. You then compare the current smallest element to the rest of the elements in the array, updating the index with the smallest element as you need to.
At the end of the loop, if the current smallest or largest index has changed, then you know that a swap must be made. The difference between this and the Bubble Sort algorithm is that you first sort the beginning elements, then you sort the ending elements. If you prefer to watch a video, Michael Sambol created a short video to help explain the Selection Sort algorithm.
-
Declare
First we’ll declare the
SelectionSortfunction in our header file:algorithms.h void SelectionSort(int[], int); -
Implement
Next we’ll add the implementation in the source file:
void SelectionSort(int array[], int size) {for (int i = 0; i < size - 1; ++i) {int smallest_index = i;for (int j = i + 1; j < size; ++j) {if (array[smallest_index] > array[j]) smallest_index = j;}if (smallest_index != i) Swap(array, i, smallest_index);}} -
Test
As always, lets test the function to see if it works as intended:
drive.cc int array[kSize] = {5, 7, 3, 4, 1,};SelectionSort(array, kSize);Print(array, kSize);Terminal window make driverg++ -c -Wall -std=c++17 driver.ccg++ -c -Wall -std=c++17 algorithms.ccg++ -Wall -std=c++17 driver.o algorithms.o./a.out13457
MergeSort
Section titled “MergeSort”The first advanced sorting algorithm learned in CSCE146 is the Merge Sorting algorithm. Merge Sort is a divide-and-conquer algorithm that splits an array into smaller subarrays, sorts each subarray, and then merges the sorted subarrays back together.
Here’s a quick video by Boot Dev if you need more of a visual explanation.
-
Declare
Just like in CSCE146, we’ll declare the primary function to call in the
mainfunction along with a utility function that will only be called in theMergeSortfunction.algorithms.h void MergeSort(int[], int);void Merge(int[], int[], int, int[], int); -
Implement
Now we’ll move on to defining the implementation. Java code from CSCE146 will be provided along with a C++ version. First are the
MergeSortfunctions:SortingAlgorithms.java static void mergeSort(int[] array) {// check if the array has one elementint size = array.length;if (size < 2) return;// calculate middle indexint mid = size / 2;// determine sizes of left and right sub arraysint leftSize = mid;int rightSize = size - mid;// create left and right sub arraysint[] left = new int[leftSize];int[] right = new int[rightSize];// populate left sub arrayfor (int i = 0; i < mid; i++) left[i] = array[i];// populate right sub arrayfor (int i = mid; i < size; i++) right[i - mid] = array[i];// recursively create smaller sub arraysmergeSort(left);mergeSort(right);// merge sub arraysmerge(left, right, array);}algorithms.cc void MergeSort(int array[], int size) {// check if array has one elementif (size < 2) return;// calculate mid indexint mid = size / 2;// determine sizes of left and right subarraysconst int kLeftSize = mid;const int kRightSize = size - mid;// create left and right subarraysint left[kLeftSize];int right[kRightSize];// populate left subarrayfor (int i = 0; i < mid; ++i) {left[i] = array[i];}// populate right subarrayfor (int i = mid; i < size; ++i) {right[i - mid] = array[i];}// recursively create smaller subarraysMergeSort(left, kLeftSize);MergeSort(right, kRightSize);// merge subarraysMerge(array, left, kLeftSize, right, kRightSize);}Next are the utility
Mergefunctions:SortingAlgorithms.java static void merge(int[] left, int[] right, int[] array) {// sizes of left and right sub arraysint leftSize = left.length;int rightSize = right.length;// pointers for left, right, and merged arraysint i = 0; // left array indexint j = 0; // right array indexint k = 0; // merged array's index// merge elements into the array in sorted orderwhile (i < leftSize && j < rightSize) {// compare elements from left and right sub arraysif (left[i] <= right[j]) {array[k] = left[i];i++;k++;} else {array[k] = right[j];j++;k++;}}// copy remaining elements from left sub arraywhile (i < leftSize) {array[k] = left[i];i++;k++;}// copy remaining elements from right sub arraywhile (j < rightSize) {array[k] = right[j];j++;k++;}}algorithms.cc void Merge(int array[], int left_array[], int left_size, int right_array[],int right_size) {int i = 0; // left array indexint j = 0; // right array indexint k = 0; // merged array's index// merge elements into the array in sorted orderwhile (i < left_size && j < right_size) {// compare elements from left and right subarraysif (left_array[i] <= right_array[j]) {array[k] = left_array[i];++i;++k;} else {array[k] = right_array[j];++j;++k;}}// copy remaining elements from left subarraywhile (i < left_size) {array[k] = left_array[i];++i;++k;}// copy remaining elements from right subarraywhile (j < right_size) {array[k] = right_array[j];++j;++k;}} -
Test
Finally, we replace the previous function called with the new one in the driver:
drive.cc int array[kSize] = {5, 7, 3, 4, 1,};MergeSort(array, kSize);Print(array, kSize);Terminal window make driverg++ -c -Wall -std=c++17 driver.ccg++ -c -Wall -std=c++17 algorithms.ccg++ -Wall -std=c++17 driver.o algorithms.o./a.out13457
QuickSort
Section titled “QuickSort”The last sorting algorithm we’ll look at is QuickSort. Quick Sort is a divide-and-conquer
algorithm that selects a “pivot” element, then rearranges the array so that all elements
less than the pivot are on one side, and all elements greater than the pivot are on the other.
It then recursively sorts these smaller partitions, resulting in a fully sorted array with
efficient average-case performance.
If you prefer a visual explanation, you can look at a short video by Computerphile
-
Declare
Just like in CSCE146, we’ll declare a primary function to call along with two utility functions that will only be called in the primary function:
algorithms.h void QuickSort(int[], int);void Quick(int[], int, int);int Partition(int[], int, int); -
Implement
Now we move to the source file and add the implementation. First the primary function
QuickSortwhich consists of calling a utility function with an array, the beginning index, and the ending index as the parameters.The difference is the added
sizeparameter which we need to get the last index of the array to call the utility function:SortingAlgorithms.java static void QuickSort(int[] array) {Quick(array, 0, array.length - 1);}algorithms.cc void QuickSort(int array[], int size) {Quick(array, 0, size - 1);}Next we define the implementation for the
Quickfunction we just called in the primary function. There are no changes in implementation from Java to C++.The
Quickfunction is the core of theQuickSortalgorithm, implementing the recursive divide-and-conquer approach. It works by calling thePartitionfunction to divide the array into two parts around a pivot element and recursively calling itself to sort the left and right partitions.SortingAlgorithms.java static void Quick(int[] array, int startIndex, int endIndex) {// check if array is sortedif (startIndex >= endIndex) return;// get the pivot element's indexint pivot = Partition(array, startIndex, endIndex);Quick(array, startIndex, pivot - 1); // leftQuick(array, pivot + 1, endIndex); // right}algorithms.cc void Quick(int array[], int start_index, int end_index) {// check if array is sortedif (start_index >= end_index) return;// get the pivot element's indexint pivot = Partition(array, start_index, end_index);Quick(array, start_index, pivot - 1); // leftQuick(array, pivot + 1, end_index); // right}The last function to implement is the utility
Partitionfunction. ThePartitionfunction rearranges the elements in the specified subarray around a pivot element. It selects the last element as the pivot and ensures that smaller elements are placed to the left and larger elements are placed to the right.Again, the implementation is the same between both languages:
SortingAlgorithms.java static int Partition(int[] array, int startIndex, int endIndex) {// select the last element as the pivotint pivot = array[endIndex];int i = startIndex;// iterate and move elements smaller than the pivot to the leftfor (int j = startIndex; j <= endIndex; j++) {if (array[j] < pivot) {Swap(array, i, j);i++;}}// swap the pivot element with the element at index iSwap(array, i, endIndex);return i; // where the pivot is}algorithms.cc int Partition(int array[], int start_index, int end_index) {// select the last element as the pivotint pivot = array[end_index];int i = start_index;// iterate and move elements smaller than the pivot to the leftfor (int j = start_index; j <= end_index; ++j) {if (array[j] < pivot) {Swap(array, i, j);++i;}}// swap the pivot element with the element at index iSwap(array, i, end_index);return i; // where the pivot is} -
Test
Finally, we can conduct our final sorting test in the driver file:
drive.cc int array[kSize] = {5, 7, 3, 4, 1,};QuickSort(array, kSize);Print(array, kSize);Terminal window make driverg++ -c -Wall -std=c++17 driver.ccg++ -c -Wall -std=c++17 algorithms.ccg++ -Wall -std=c++17 driver.o algorithms.o./a.out13457