Sorting and Searching — sort, stable_sort, partial_sort, binary_search, lower_bound
In this tutorial, you will learn about Sorting and Searching. We cover key concepts, practical examples, and best practices to help you master this topic.
C++ provides sort (introsort O(n log n)), stable_sort (mergesort), partial_sort (heap selection), and binary search variants (lower_bound, upper_bound, equal_range) for efficiently ordering and searching ranges.
What You'll Learn
You will sort containers with std::sort, preserve relative order of equivalent elements with stable_sort, find the top k elements with partial_sort and nth_element, search sorted ranges with binary_search, lower_bound, upper_bound, and equal_range, and understand performance characteristics of each algorithm.
Why It Matters
Sorting and searching are fundamental to computing. The STL provides optimized implementations that you should never need to write yourself. Understanding which algorithm to use — and whether your data needs to be sorted at all — separates novice from intermediate programmers.
Learning Path
graph LR
A["36: Algorithms Overview"] --> B["37: Sorting & Searching"]
B --> C["38: Modifying Algorithms"]
C --> D["39: Numeric Algorithms"]
style A fill:#4a90d9,stroke:#2c5f8a,color:#fff
style B fill:#4a90d9,stroke:#2c5f8a,color:#fff
style C fill:#4a90d9,stroke:#2c5f8a,color:#fff
style D fill:#4a90d9,stroke:#2c5f8a,color:#fff
std::sort
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
int main() {
std::vector<int> v = {5, 2, 8, 1, 9, 3, 7, 4, 6};
// Default: ascending
std::sort(v.begin(), v.end());
for (int x : v) std::cout << x << " ";
std::cout << "\n"; // 1 2 3 4 5 6 7 8 9
// Descending
std::sort(v.begin(), v.end(), std::greater<int>());
for (int x : v) std::cout << x << " ";
std::cout << "\n"; // 9 8 7 6 5 4 3 2 1
// Custom comparator
std::sort(v.begin(), v.end(), [](int a, int b) {
return a % 10 < b % 10; // sort by last digit
});
// Sorting with structs
struct Person { std::string name; int age; };
std::vector<Person> people = {{"Alice", 30}, {"Bob", 25}, {"Charlie", 35}};
std::sort(people.begin(), people.end(),
[](const Person& a, const Person& b) {
return a.age < b.age;
});
// Complexity: O(n log n) on average
}
sort uses introsort (quicksort + heapsort), O(n log n) worst-case.
std::stable_sort
#include <iostream>
#include <vector>
#include <algorithm>
struct Task {
int priority;
std::string name;
};
int main() {
std::vector<Task> tasks = {
{3, "Low A"}, {1, "High A"}, {3, "Low B"},
{2, "Mid A"}, {1, "High B"}
};
// stable_sort preserves relative order of equal elements
std::stable_sort(tasks.begin(), tasks.end(),
[](const Task& a, const Task& b) {
return a.priority < b.priority;
});
for (const auto& [p, n] : tasks) {
std::cout << p << ": " << n << "\n";
}
// Output:
// 1: High A
// 1: High B (High A appears before High B — order preserved)
// 2: Mid A
// 3: Low A
// 3: Low B (Low A before Low B — order preserved)
}
stable_sort uses mergesort, O(n log n) with O(n) extra memory. Use it when you need to preserve the original order of equivalent elements.
std::partial_sort
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> scores = {55, 88, 72, 95, 63, 91, 78, 82, 87};
// Find top 3 scores (not fully sorted)
std::partial_sort(scores.begin(), scores.begin() + 3, scores.end(),
std::greater<int>());
std::cout << "Top 3: ";
for (int i = 0; i < 3; ++i) {
std::cout << scores[i] << " ";
}
std::cout << "\n"; // 95 91 88
// Rest of array is in arbitrary order
std::cout << "Rest: ";
for (size_t i = 3; i < scores.size(); ++i) {
std::cout << scores[i] << " ";
}
std::cout << "\n";
}
Complexity: O(n log k) where k is the number of sorted elements, k = 3 here.
std::nth_element
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> v = {55, 88, 72, 95, 63, 91, 78, 82, 87};
// Find the median (5th element, index 4)
auto mid = v.begin() + v.size() / 2;
std::nth_element(v.begin(), mid, v.end());
std::cout << "Median: " << *mid << "\n";
// Elements before mid are all <= median
// Elements after mid are all >= median
// Neither side is sorted
std::cout << "Left: ";
for (auto it = v.begin(); it != mid; ++it) std::cout << *it << " ";
std::cout << "\nMid: " << *mid << "\n";
std::cout << "Right: ";
for (auto it = mid + 1; it != v.end(); ++it) std::cout << *it << " ";
std::cout << "\n";
}
nth_element is O(n). Use it when you need the kth smallest/largest element or percentile without full sorting.
Binary Search on Sorted Ranges
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> v = {1, 3, 5, 7, 9, 11, 13, 15};
// binary_search: returns bool
bool found = std::binary_search(v.begin(), v.end(), 7);
std::cout << "7 found: " << found << "\n"; // 1
// lower_bound: first element >= value
auto low = std::lower_bound(v.begin(), v.end(), 8);
if (low != v.end()) {
std::cout << "First >= 8: " << *low << " at index "
<< (low - v.begin()) << "\n"; // 9 at index 4
}
// upper_bound: first element > value
auto up = std::upper_bound(v.begin(), v.end(), 7);
if (up != v.end()) {
std::cout << "First > 7: " << *up << "\n"; // 9
}
// equal_range: pair of lower_bound and upper_bound
v = {1, 2, 2, 2, 3, 4, 5};
auto [low2, high2] = std::equal_range(v.begin(), v.end(), 2);
for (auto it = low2; it != high2; ++it) {
std::cout << *it << " "; // 2 2 2
}
std::cout << "\n";
std::cout << "Count of 2: " << (high2 - low2) << "\n"; // 3
}
Comparing Sorting Algorithms
| Algorithm | Complexity | Stable | Extra Memory | Use Case |
|---|---|---|---|---|
sort |
O(n log n) avg | No | O(log n) | General purpose |
stable_sort |
O(n log n) | Yes | O(n) | Need stability |
partial_sort |
O(n log k) | No | O(log k) | Top-k elements |
nth_element |
O(n) avg | No | O(1) | Median/percentile |
sort_heap |
O(n log n) | No | O(1) | When heap exists |
Common Mistakes
Mistake 1: Using sort When Data is Nearly Sorted
// If almost sorted, insertion sort is faster
// Use std::sort (introsort handles this well, but consider std::stable_sort for nearly-sorted data)
Mistake 2: Binary Search on Unsorted Range
std::vector<int> v = {5, 3, 1, 4, 2};
std::binary_search(v.begin(), v.end(), 3); // undefined behavior
Always sort before binary search.
Mistake 3: Off-by-One with lower_bound/upper_bound
lower_bound(value) returns the first position where value could be inserted to maintain order.
upper_bound(value) returns the last position where value could be inserted.
Mistake 4: Using sort When nth_element Would Suffice
Finding a median with sort is O(n log n). Using nth_element is O(n).
Mistake 5: Forgetting That sort is Not Stable
If you need to sort by multiple criteria (e.g., first by age, then by name), either use stable_sort for the secondary criterion first, or use a single comparator that checks both.
Mistake 6: Using sort with Non-Random Access Iterators
std::list<int> lst;
// std::sort(lst.begin(), lst.end()); // Error
Use lst.sort() member function instead.
Practice Questions
- What is the difference between
sortandstable_sort? - When would you use
partial_sortinstead ofsort? - How do you find the position where to insert a value in a sorted vector?
- Which algorithm finds the median in O(n) time?
- Write a function that finds all elements with a given value in a sorted vector and returns a range of iterators.
Challenge
Implement a "top-k frequent elements" function: given a vector of integers and k, return the k most frequent elements. Use nth_element or partial_sort with a frequency map.
FAQ
Mini Project
Build a grade report generator:
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
#include <iomanip>
struct Student {
std::string name;
double grade;
};
void printReport(std::vector<Student> students) {
std::cout << "Grade Report\n";
std::cout << "============\n";
// Sort by grade descending
std::sort(students.begin(), students.end(),
[](const Student& a, const Student& b) {
return a.grade > b.grade;
});
for (size_t i = 0; i < students.size(); ++i) {
std::cout << std::setw(2) << (i + 1) << ". "
<< std::setw(15) << std::left << students[i].name
<< std::right << std::setw(5) << students[i].grade;
// Letter grade
if (students[i].grade >= 90) std::cout << " A";
else if (students[i].grade >= 80) std::cout << " B";
else if (students[i].grade >= 70) std::cout << " C";
else std::cout << " D/F";
std::cout << "\n";
}
// Median grade
auto mid = students.begin() + students.size() / 2;
std::nth_element(students.begin(), mid, students.end(),
[](const Student& a, const Student& b) {
return a.grade < b.grade;
});
std::cout << "\nMedian grade: " << mid->grade << "\n";
// Top 25% threshold
auto top = students.begin() + students.size() / 4;
std::nth_element(students.begin(), top, students.end(),
[](const Student& a, const Student& b) {
return a.grade > b.grade;
});
std::cout << "Top 25% threshold: " << top->grade << "\n";
}
int main() {
std::vector<Student> class1 = {
{"Alice", 95}, {"Bob", 82}, {"Charlie", 78},
{"David", 91}, {"Eve", 67}, {"Frank", 88},
{"Grace", 73}, {"Hank", 59}, {"Ivy", 85}
};
printReport(class1);
}
What's Next
Sorting and searching are core algorithm categories. The next lesson covers modifying algorithms: copy, move, transform, replace, and the erase-remove idiom.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro