In contests you almost never write a sort. What you do need: the right comparator, linear-time sorts when keys are small, partitioning, selection, inversion counting, coordinate compression, and the facts about sortedness that turn into problem solutions.
The algorithms in one table
| Algorithm | Time | Space | Stable | Notes |
|---|---|---|---|---|
std::sort (introsort) | worst | no | quicksort + heapsort fallback + insertion sort for small ranges | |
std::stable_sort | yes | merge sort; if memory is unavailable | ||
| merge sort | yes | counting cross pairs during merges (inversions, CDQ) | ||
| heapsort | no | poor cache behaviour | ||
| insertion sort | yes | fastest for tiny or nearly-sorted arrays | ||
| counting sort | yes | keys in | ||
| LSD radix sort | yes | 32-bit keys in 4 byte passes; beats std::sort 2-4x for | ||
| bucket sort | expected | — | only for random-ish real keys |
The lower bound: a comparison sort distinguishes orders, so it needs comparisons in the worst case. Counting and radix sort beat it by using the key values, not comparisons.
Why each sort is (or isn’t) stable, briefly: merge sort takes from the left half on ties (<=, not <); counting/radix sort place elements by scanning backward through the input during placement, which preserves the original relative order of equal keys; selection and quicksort can jump an equal element past others via a swap, so neither is stable by default; heapsort’s root-swap is likewise order-destroying. Why heapsort is to build, not : bottom-up heapify (sift down from n/2-1 to 0) does amortized work per node rather than , since most nodes are near the bottom and sift only a short distance — cheaper than sequential inserts.
Comparators that come up
// fractions a/b with b > 0, compared exactly
sort(all(v), [](auto& x, auto& y) { return (i128)x.a * y.b < (i128)y.a * x.b; });
// strings concatenated to the smallest number: order by a+b < b+a (transitive, provably)
sort(all(s), [](const string& a, const string& b) { return a + b < b + a; });
// by angle around the origin without atan2: half-plane first, then cross product
auto half = [](P p) { return p.y < 0 || (p.y == 0 && p.x < 0); };
sort(all(pts), [&](P a, P b) { return half(a) != half(b) ? half(a) < half(b) : a.cross(b) > 0; });
// indices by key (argsort)
vector<int> id(n); iota(all(id), 0); sort(all(id), [&](int i, int j) { return a[i] < a[j]; });Exchange-argument sorts (job scheduling, greedy merges) need the derived relation to be transitive; check it on random triples if in doubt.
Proof: the string-concatenation comparator is a valid (transitive) order
Define by (lexicographic). This is equivalent to comparing the infinite periodic extensions and lexicographically: standard fact about periodic strings, since and have the same length , and the first position where and differ (if any, within, say, the first or simply the first characters, which suffices to detect any difference between two periodic sequences with periods ) occurs at the same relative position and with the same outcome as the first difference between and — a short case check on whether that position falls in the -part or -part of each concatenation confirms the two comparisons always agree. Since is then literally “compare two fixed infinite sequences lexicographically”, and ordinary lexicographic order on sequences is transitive, is transitive.
Partitioning and selection
Three-way partition (Dutch national flag). Invariant: [0, lt) < pivot, [lt, i) = pivot, [i, gt] unknown, (gt, n) > pivot. When swapping with gt, don’t advance i, because the incoming element is unexamined.
void partition3(vector<int>& a, int l, int r, int p, int& lt, int& gt) { // [l, r]
int i = l; lt = l; gt = r;
while (i <= gt) {
if (a[i] < p) swap(a[lt++], a[i++]);
else if (a[i] > p) swap(a[i], a[gt--]);
else i++;
}
}Quicksort with a random pivot and three-way partition is expected even with many equal keys. Hoare’s two-way partition does about 3x fewer swaps than Lomuto’s.
Proof: the three-way partition invariant is maintained
Before the loop,
lt=i=landgt=r, so the three nontrivial regions[l,lt),[lt,i),(gt,r]are all empty and[i,gt]is the whole range — the invariant holds vacuously. Each iteration examinesa[i]: ifa[i]<p, swapping it intoa[lt]is safe becausea[lt](the first element of the “=p” region, ora[i]itself if that region is empty) becomes the new last element of an extended “<p” region, while the incoming value at positioniis exactly the olda[lt], which is=pby the invariant — so incrementing bothltandiextends the “<p” region and keeps the “=p” region’s content valid; ifa[i]==p, it’s already correctly placed at the boundary, so onlyiadvances, extending “=p”; ifa[i]>p, swapping it toa[gt]extends the “>p” region by one, but the element swapped in fromgtis unexamined, soimust not advance (it is examined again next iteration). In every case one region grows by exactly one element and the unprocessed region[i,gt]shrinks by exactly one, so the invariant persists and the loop terminates (unprocessed region strictly shrinks) with[i,gt]empty, i.e. everything correctly partitioned into<p,=p,>p.
Named uses: “sort colors” (3 fixed values), segregating even/odd or sign with a simplified 2-way version, wiggle sort, and -way generalisations with counting sort or more pointers.
Selection (-th smallest):
| Method | Cost |
|---|---|
nth_element(a.begin(), a.begin() + k, a.end()) | expected; afterwards is in place, smaller to its left, larger to its right |
| quickselect, random pivot | expected; recurse into one side only |
| median of medians | worst case: groups of 5, |
partial_sort | top sorted in |
| heap of size | streaming top-, |
| two heaps | running median, per insert (Heaps) |
| order-statistic tree / BIT descent | dynamic -th, |
| wavelet tree / persistent segment tree | -th in a subarray (Range Order Statistics) |
Selection on a sorted matrix or on pairwise sums is binary search on the value with a counting check (Binary Search), or Frederickson’s method on sorted matrices.
Selection pitfalls
nth_elementtakes the iterator to position , not . A fixed or first-element pivot (quickselect or quicksort) gets hacked on Codeforces — sorted or adversarial input forces — always randomise the pivot. The median of an even-length array needs an explicit choice of lower vs. upper median.
Inversions
An inversion is a pair with .
- The minimum number of adjacent swaps to sort is the inversion count.
- The minimum number of arbitrary swaps to sort a permutation is .
- Each swap of two elements changes the inversion count’s parity, so the sign of a permutation is .
- Over all permutations the expected number of inversions is .
Proof: the four inversion facts above
Adjacent swaps = inversion count. Each adjacent swap changes the relative order of exactly one pair, so it changes by exactly ; sorting needs , so at least swaps are needed. Exactly suffice: repeatedly swap any adjacent inverted pair (one exists whenever , since a non-sorted sequence has some adjacent descent), which strictly decreases by 1 each time (bubble sort’s correctness).
Arbitrary swaps ( = number of cycles). A swap of two elements in different cycles merges them into one cycle (decreasing the cycle count by 1); a swap within one cycle of length splits it into two cycles (increasing the count by 1). To reach the identity ( cycles, all length 1), starting from cycles, every swap changes by exactly , and only merging swaps (decreasing a cycle toward length 1, eventually) make progress — the minimum sequence uses exactly one merge-type swap per “extra” element needed to split each cycle down to fixed points, giving exactly swaps (and this many always suffice: fix cycles one at a time, each needing swaps to resolve).
Sign formula. Each swap (adjacent or not, a transposition) changes the permutation’s sign by (a classical fact: a transposition is an odd permutation). So after transpositions from the identity, ; taking (adjacent swaps) or (arbitrary swaps) — both valid swap sequences reaching the same permutation — gives , consistent since any two sequences of transpositions reaching the same permutation have the same parity of length (sign is well-defined).
Expected inversions . By linearity of expectation over the pairs, each pair , , is inverted with probability exactly (symmetry: and are equally likely for a uniform random permutation), so .
Tested: all four facts checked by brute force (exact inversion/cycle counts and sign, compared against a reference permutation-sign computation; expected inversions estimated by Monte Carlo over random permutations) for small .
- The number of permutations with exactly inversions has generating function . That’s an DP with prefix sums, or using Euler’s pentagonal theorem for large .
- The minimum adjacent swaps to turn into (same multiset): map each value of to its target position in (equal values matched in order), then count inversions.
ll countInv(vector<int>& a, int l, int r, vector<int>& buf) { // [l, r), sorts a
if (r - l < 2) return 0;
int m = (l + r) / 2;
ll res = countInv(a, l, m, buf) + countInv(a, m, r, buf);
int i = l, j = m, k = l;
while (i < m || j < r)
if (j == r || (i < m && a[i] <= a[j])) buf[k++] = a[i++];
else res += m - i, buf[k++] = a[j++];
copy(buf.begin() + l, buf.begin() + r, a.begin() + l);
return res;
}The Fenwick version (scan left to right, add i - prefix(a[i])) is shorter after compression. Variants:
| Question | Technique |
|---|---|
| pairs with | two pointers inside the merge step |
| inversions after each cyclic shift of a permutation | moving the first value to the end changes the count by |
| inversions of many subarrays | Mo + BIT ; or second-offline Mo / block precomputation |
| inversions with point updates | BIT of sorted blocks, or CDQ in |
| Kendall tau distance between rankings | inversions of |
Coordinate compression
vector<ll> xs = values; // include every value that will be queried
sort(all(xs)); xs.erase(unique(all(xs)), xs.end());
auto id = [&](ll x) { return int(lower_bound(all(xs), x) - xs.begin()); };- Collect all values first: array values, query endpoints, thresholds, derived values such as .
- For intervals, compressing only endpoints merges distinct gaps. Use half-open and insert both and , or keep the real length
xs[i+1] - xs[i]of each compressed cell and weight by it (rectangle union). - If keys arrive online and can’t be collected first, use a dynamic segment tree or a hash map.
- “Compress, then index a BIT by value” is one of the most reused pairs in CP: inversions, dominance counting, LIS with weights, offline 2D queries.
- The technique generalises past numbers to any “map arbitrary keys to a dense range”: strings (sort and rank, or
map<string,int>), pairs, graph vertices given by arbitrary labels, time points in an offline sweep, or states in a sparse-state-space DP.
Facts about sorted order
- Sorting makes “all pairs” conditions local: the minimum is between neighbours; the number of pairs with needs two pointers.
- Sorting by one key and processing in order (a sweep) turns 2D problems into 1D data-structure problems.
- Rearrangement inequality: is maximised when both are sorted the same way and minimised when sorted oppositely.
Proof: the rearrangement inequality
Take any arrangement that is not sorted the same way: there exist indices with but (an “inversion” between the two sequences’ relative order). Swapping and changes the sum by (both factors positive) — strictly increasing the sum. So any arrangement with such an inversion is not maximal; repeating removes all such inversions, reaching the same-order arrangement, which must therefore be the (unique, up to ties) maximum. The minimum follows symmetrically (swapping removes an inversion of the opposite kind only when sorted oppositely is suboptimal, so by the same exchange argument run in the other direction, oppositely-sorted is the minimum).
- A sequence can be sorted by a stack (one push/pop pass) iff it avoids the pattern 231, and with two stacks or a deque the conditions are more complex (pattern classes).
- Sorting elements with an oracle that compares in : about queries with merge sort or binary insertion. Ford-Johnson (merge insertion) is near optimal for small .
- Sorting networks: Batcher’s odd-even merge sort and bitonic sort use comparators of fixed pattern (branchless, SIMD-friendly). The 0-1 principle: a network sorts all inputs iff it sorts all 0/1 inputs.
- Pancake sorting (prefix reversals): at most with the obvious greedy. Sorting by adjacent transpositions: inversions. Sorting by arbitrary transpositions: cycles. Sorting by block moves or reversals with signs: research problems; brute force with BFS for small .