Quick sort recursive/iterative speed variation and how to graph them on C++?

120 views Asked by At

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

enter image description here

0

There are 0 answers