I'm a second semester computer science engineering student and I had to measure the time taken for recursive/iterative quick sort implementations to sort vectors of length 1-500 and graph using C++ for my university coursework.
I expected the recursive quick sort to be slower and iterative quick sort to be quicker. But the opposite happened when i plotted the graphs. Recursive implementation was slightly faster. How could this happen? Is there a problem with my quick sort algorithms or the way I measured execution time?
I will share the code I used to measure execution time on C++ and the code I used to plot the graph. BTW I couldn't graph it on C++ no matter how hard I tried as I'm very new to C++. So, for graph plotting, I used python. Any help regarding how to graph this using C++ is also golden. (I tried to plot using Gnuplot, I followed YouTube guides, read online articles but nothing gave me a clear idea)
I use a Ryzen 7 6800H 3.2GHz CPU and a DDR5 4800MHz 16GB RAM.
C++ code for measuring execution time
#include <vector>
#include <random>
#include <iostream>
#include <sstream>
#include <limits>
#include <chrono>
#include <array>
#include <fstream>
#include <numeric>
#include <stack>
using namespace std;
using namespace std::chrono;
void swap(int* a, int* b)
{
int temp = *a;
*a = *b;
*b = temp;
}
//function to partition the array
int partition(vector<int>& arr, int low, int high) {
//pick the rightmost element as a pivot from the array
int pivot = arr[high];
int partIndex = low;
for (int i = low; i < high; i++) {
//if current element is smaller than or equal to the pivot
if (arr[i] <= pivot) {
swap(arr[i], arr[partIndex]);
partIndex++;
}
}
swap(arr[partIndex], arr[high]);
return partIndex;
}
//recursive quick sort function
void recursivequickSort(vector<int>& arr, int low, int high) {
//when low is less than high
if (low < high) {
int partIndex = partition(arr, low, high);
//smaller elements than pivot go left and
//higher elements go right
recursivequickSort(arr, low, partIndex - 1);
recursivequickSort(arr, partIndex + 1, high);
}
}
//iterative Quicksort routine
void iterativeQuicksort(vector<int>& arr, int n)
{
//create a stack of std::pairs
stack<pair<int, int>> s;
//get the low and high index of the given array
int low = 0;
int high = n - 1;
//push the low and high index of the array into the stack
s.push(make_pair(low, high));
//loop till stack is empty
while (!s.empty())
{
low = s.top().first, high = s.top().second;
s.pop();
//rearrange elements across pivot
//lower to left, higher to right
int pivot = partition(arr, low, high);
if (pivot - 1 > low) {
s.push(make_pair(low, pivot - 1));
}
if (pivot + 1 < high) {
s.push(make_pair(pivot + 1, high));
}
}
}
//write the lists to a file
void writeListToFile(const std::vector<int>& list, int length) {
std::ofstream outputFile("processed_lists.txt", std::ios::app); // Open file in append mode
if (!outputFile.is_open()) {
std::cerr << "Error: Could not open the file." << std::endl;
return;
}
// Write the processed list to the file
outputFile << "list with length " << length << " = [";
for (int i = 0; i < length - 1; ++i) {
outputFile << list[i] << ", ";
}
outputFile << list[length - 1] << "]\n";
outputFile.close(); // Close the file
}
//function to generate a random vector of given length
std::vector<int> generateRandomVector(int length) {
std::vector<int> randomVector;
// Seed the random number generator
std::random_device rd;
std::mt19937 gen(rd());
// Define the distribution for random integers (range from -100 to 100, for example)
std::uniform_int_distribution<int> dis(-10000, 10000);
// Generate random elements and fill the vector
for (int i = 0; i < length; ++i) {
randomVector.push_back(dis(gen));
}
return randomVector;
}
// Function to call both iterative and recursive merge sort on a given vector and return an array with the execution times
array<double, 2> sortingComparison(vector<int>& arr, int numTrials) {
// Copy the input vector for recursive merge sort
vector<int> recursiveArr = arr;
vector<int> iterativeArr = arr;
vector<int> quickRecursiveTimes;
vector<int> quickIterativeTimes;
for (int i = 0; i < numTrials; i++) {
auto startIterative = high_resolution_clock::now();
iterativeQuicksort(iterativeArr, iterativeArr.size());
auto stopIterative = high_resolution_clock::now();
auto quickIterativeTime = duration_cast<microseconds>(stopIterative - startIterative);
quickIterativeTimes.push_back(quickIterativeTime.count());
auto startRecursive = high_resolution_clock::now();
recursivequickSort(recursiveArr, 0, recursiveArr.size() - 1);
auto stopRecursive = high_resolution_clock::now();
auto quickRecursiveTime = duration_cast<microseconds>(stopRecursive - startRecursive);
quickRecursiveTimes.push_back(quickRecursiveTime.count());
}
// Calculate the sum of execution times for each sorting algorithm
long long sumQuickRecursive = accumulate(quickRecursiveTimes.begin(), quickRecursiveTimes.end(), 0LL);
long long sumQuickIterative = accumulate(quickIterativeTimes.begin(), quickIterativeTimes.end(), 0LL);
// Calculate the average execution time for each sorting algorithm
double averageQuickRecursive = static_cast<double>(sumQuickRecursive) / numTrials;
double averageQuickIterative = static_cast<double>(sumQuickIterative) / numTrials;
// Return array containing average execution times
return { averageQuickRecursive, averageQuickIterative};
}
int main() {
int upperBound = 500;
int numTrials = 5; // Number of trials for each input size
// Create vectors to store execution times
vector<double> quickRecursiveTimes;
vector<double> quickIterativeTimes;
quickRecursiveTimes.reserve(upperBound);
quickIterativeTimes.reserve(upperBound);
// Add time measurements to the vectors
for (int i = 1; i <= upperBound; ++i) {
// Generate vector with i random numbers
vector<int> numbers = generateRandomVector(i);
writeListToFile(numbers, numbers.size());
// Get time measurements for each sorting algorithm
array<double, 2> executionTimes = sortingComparison(numbers, numTrials);
quickRecursiveTimes.push_back(executionTimes[0]);
quickIterativeTimes.push_back(executionTimes[1]);
}
// Print the two lists
cout << "Quick recursive time measurements (micro seconds): ";
cout << "[";
for (int i = 0; i < upperBound - 1; ++i) {
cout << quickRecursiveTimes[i] << ", ";
}
cout << quickRecursiveTimes[upperBound - 1] << "]" << endl;
cout << "Quick iterative time measurements (micro seconds): ";
cout << "[";
for (int i = 0; i < upperBound - 1; ++i) {
cout << quickIterativeTimes[i] << ", ";
}
cout << quickIterativeTimes[upperBound - 1] << "]" << endl;
return 0;
}
Python code for plotting
import matplotlib.pyplot as plt
import numpy as np
# Load the data from the text file
quick_recursive_times = [] # List of recursive time measurements
quick_iterative_times = [] # List of iterative time measurements
# Generate x-axis values (list lengths)
x_values = np.arange(1, len(quick_recursive_times) + 1)
# Plot the merge recursive time measurements in blue
plt.plot(x_values, quick_recursive_times, color='blue', label='Quick sort recursive Approach')
# Plot the merge iterative time measurements in red
plt.plot(x_values, quick_iterative_times, color='red', label='Quick sort iterative Approach')
# Label the axes and title
plt.xlabel('List Length')
plt.ylabel('Time (micro seconds)')
plt.title('Sorting Execution Time Comparison')
# Add a legend
plt.legend()
# Show grid
plt.grid(True)
# Show the plot
plt.show()
Execution Time vs Input Size graph using Python
