Skip to Content
Course content

212: Implementing Sorting Algorithms in C++

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Look, in a professional environment, you'll almost never write your own sorting algorithm from scratch. You'll use std::sort from the <algorithm> header because it's heavily optimized and usually implements Introsort—a hybrid that switches between QuickSort, HeapSort, and Insertion Sort depending on the data. But if you want to actually understand how data moves in memory and why some programs crawl to a halt when the input size doubles, you have to get your hands dirty with the implementation yourself.

The "Easy" Way That Kills Performance

When people first try to implement a sort, they usually go for something like Bubble Sort. It's intuitive: you just keep swapping adjacent elements until the largest one "bubbles" to the top. I've seen this in junior code reviews more times than I can count, usually because the developer thought, "It's only a few lines of code, it doesn't matter."

struct Score {
    std::string name;
    int points;
};

void bubbleSort(std::vector<Score>& scores) {
    for (size_t i = 0; i < scores.size(); ++i) {
        for (size_t j = 0; j < scores.size() - 1; ++j) {
            if (scores[j].points < scores[j + 1].points) {
                std::swap(scores[j], scores[j + 1]);
            }
        }
    }
}

The problem here isn't that it doesn't work; it's that it's catastrophically inefficient. This is an O(n²) algorithm. If you're sorting 10 high scores for a local leaderboard, you won't notice a thing. But if you're sorting 100,000 users in a global database, you're looking at roughly 10 billion comparisons. Your CPU will be screaming, and your users will think the app has crashed. It's the classic trap of prioritizing "simple code" over "scalable code."

Divide, Conquer, and the Pivot

To do this better, we need to stop looking at the list as one giant block and start breaking it down. This is where QuickSort comes in. Instead of blindly swapping neighbors, we pick a "pivot" element and partition the array so that everything smaller than the pivot is on the left and everything larger is on the right. Then, we recursively do the same for the two halves.

I prefer QuickSort for teaching this because it forces you to think about pointers and indices. You aren't just looping; you're manipulating the boundaries of the data. Here is how I'd structure a basic implementation:

int partition(std::vector<Score>& scores, int low, int high) {
    int pivot = scores[high].points;
    int i = (low - 1);

    for (int j = low; j < high; j++) {
        if (scores[j].points > pivot) {
            i++;
            std::swap(scores[i], scores[j]);
        }
    }
    std::swap(scores[i + 1], scores[high]);
    return (i + 1);
}

void quickSort(std::vector<Score>& scores, int low, int high) {
    if (low < high) {
        int pi = partition(scores, low, high);
        quickSort(scores, low, pi - 1);
        quickSort(scores, pi + 1, high);
    }
}

The Cost of Speed and the Edge Case Trap

Now, we've jumped from O(n²) to O(n log n) on average. For those 100,000 users, we went from billions of operations down to roughly 1.6 million. That is a massive win. However, nothing is free in C++. QuickSort has a "worst-case" scenario. If you pick the last element as the pivot and the array is already sorted, you end up right back at O(n²). It's a bit ironic, isn't it? The algorithm can fail specifically because the data is already in the state you wanted it to be in.

You'll also notice that QuickSort is an "in-place" sort. Unlike MergeSort, which requires a temporary array to hold the split data (costing you extra memory), QuickSort just swaps elements within the original vector. In systems with tight memory constraints—like an embedded controller or a high-performance game engine—that memory saving is often more important than the risk of a worst-case time complexity. To mitigate the "already sorted" trap, professionals usually pick a random element as the pivot or use the "median-of-three" rule, but the core logic remains the same: divide the problem until it's too small to be a problem.




📋 Practical Task

Exercise: Implementing a Randomized Pivot for a Game Leaderboard

You have been given a Leaderboard class that uses the QuickSort implementation from the lesson. However, during testing, the team noticed that the application hangs when the leaderboard is already sorted (the "Worst Case" scenario discussed in the lesson).

Your Task: Modify the partition function to implement a randomized pivot. Instead of always using scores[high] as the pivot, you must:

  • Generate a random index between low and high using <random>.
  • Swap the element at that random index with the element at high.
  • Proceed with the partitioning logic as usual.

Verify your fix by creating a std::vector<Score> that is already sorted in descending order and ensuring the quickSort function completes instantly without hitting a stack overflow or timing out.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.