C++
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C++
-
Section 4: Memory Management
-
Section 5: Templates and Generic Programming
-
Section 6: The Standard Template Library
-
Section 7: Modern C++ Features
-
Section 8: Error Handling
-
Section 9: Multithreading
-
Section 10: Operator Overloading and Type Conversion
-
Section 11: Advanced Topics
-
Section 12: Tooling and Build Systems
-
Section 13: Design Patterns in C++
-
Section 14: Interfacing with C and Systems Programming
-
Section 15: Networking and IPC Basics
-
Section 16: Graphics and Game Programming Basics
-
Section 17: The Boost Libraries
-
Section 18: Data Structures and Algorithms in C++
-
Section 19: Practical Projects
-
Section 20: More Concurrency Patterns
-
Section 21: More OOP and Design Practice
-
Section 22: File I/O and Streams
-
Section 23: More Standard Library
-
Section 24: Practice Exercises
-
Section 25: Interview and Algorithm Practice
-
Section 26: Compiler and Language Internals
-
Section 27: GUI and Application Frameworks Overview
-
Section 28: Testing and Quality Practices
-
Section 29: Numerics Library
-
Section 30: Concepts Library (C++20)
-
Section 31: Ranges Library (C++20) In Depth
-
Section 32: More Utility Library
212: Implementing Sorting Algorithms in C++
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
lowandhighusing<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.
There are no comments for now.