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

AlgorithmTimeSpaceStableNotes
std::sort (introsort) worstnoquicksort + heapsort fallback + insertion sort for small ranges
std::stable_sortyesmerge sort; if memory is unavailable
merge sortyescounting cross pairs during merges (inversions, CDQ)
heapsortnopoor cache behaviour
insertion sortyesfastest for tiny or nearly-sorted arrays
counting sortyeskeys in
LSD radix sortyes32-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.

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.

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):

MethodCost
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_sorttop sorted in
heap of size streaming top-,
two heapsrunning median, per insert (Heaps)
order-statistic tree / BIT descentdynamic -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_element takes 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 .

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:

QuestionTechnique
pairs with two pointers inside the merge step
inversions after each cyclic shift of a permutationmoving the first value to the end changes the count by
inversions of many subarraysMo + BIT ; or second-offline Mo / block precomputation
inversions with point updatesBIT of sorted blocks, or CDQ in
Kendall tau distance between rankingsinversions 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.
  • 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 .