Why Randomized Quicksort / Quickselect Matters
Suppose a dashboard receives eight response times: $[7,2,9,2,5,1,8,2]$. To display every value in increasing order, it needs a sorting algorithm. To find only the sixth-smallest value, it needs an order statistic: the answer is $7$, and sorting all the other values is unnecessary.
Quicksort and Quickselect share a powerful operation: choose a pivot and partition the data into values smaller than, equal to, and larger than that pivot. Quicksort continues on both unequal regions to order the whole array. Quickselect continues on at most one region, the one containing the requested rank. Random pivot choices avoid tying performance to an unfortunate fixed pivot rule such as always choosing the first value.
You will implement both algorithms using the same in-place three-way partition, trace every comparison and swap in a small run, and verify matching outputs in Python, C++, and Java. The three-way split is especially useful when many values repeat: once the equal block is identified, neither algorithm needs to process that block again.
Randomized Quicksort orders an entire array; Randomized Quickselect finds one ranked value by following only the necessary partition. Both are always correct under a valid comparison order, regardless of the chosen pivots; randomness affects the work performed. With independent uniform pivot choices, Quicksort takes expected $O(n\log n)$ time and Quickselect takes expected $\Theta(n)$ time, while unlucky choices can still cause quadratic work.
Core Concepts and Terminology
The input is an array $A$ of comparable values. The runnable examples use signed $32$-bit integers, so equality and ordering are exact. Quicksort mutates $A$ into a nondecreasing permutation of the original values. Quickselect also mutates $A$ and returns the value that would occupy zero-based position $k$ in a sorted copy, requiring $0\le k\lt n$. Repeated values occupy repeated ranks; the rank is not a rank among distinct values.
Every active range is half-open: $[\mathrm{lo},\mathrm{hi})$ includes lo and excludes hi, with length $m=\mathrm{hi}-\mathrm{lo}$. Choose a pivot index in this range and copy its value into a separate variable $p$. The pivot's original array slot may move during partitioning; keeping the copied value avoids changing the comparison target accidentally.
A completed partition returns boundaries $\mathrm{lt}$ and $\mathrm{gt}$ such that
$$A[\mathrm{lo}:\mathrm{lt}]\lt p,\qquad A[\mathrm{lt}:\mathrm{gt}]=p,\qquad A[\mathrm{gt}:\mathrm{hi}]\gt p.$$
These expressions mean that every value in the corresponding slice has the stated relationship to $p$. They do not mean the unequal slices are internally sorted. Because $p$ was copied from the active range, the equal slice is nonempty, which guarantees progress.
| Term | Definition and role |
|---|---|
| Pivot | A copied value from the active range; choosing $p=2$ separates the sample into one smaller value, three equal values, and four larger values. |
| Uniform pivot index | In the ideal model, every one of the $m$ active positions has probability $1/m$; a value occurring $f$ times is chosen with probability $f/m$. |
| Three-way partition | An in-place rearrangement into less-than, equal-to, and greater-than regions; it removes all copies of the pivot from further processing. |
| Zero-based rank | Position in sorted order, counting duplicates; rank $5$ in $[1,2,2,2,5,7,8,9]$ is $7$. |
| Partition boundary | A returned endpoint such as $\mathrm{lt}=1$ or $\mathrm{gt}=4$; it is not necessarily the original pivot index. |
| Selection postcondition | $A[k]=v$, every earlier value is at most $v$, and every later value is at least $v$; neither side must be sorted. |
| Las Vegas algorithm | Correctness does not depend on a lucky random choice; the running time varies with the choices. |
| Seed | An initial pseudorandom state used to reproduce a run; a fixed seed specifies a deterministic sequence, not independent mathematical randomness. |
| Stable sort | A sort preserving the input order of equal-key records; the swapping implementation here is not stable. |
| In-place partition | Rearranges the existing array with constant extra state; recursion and any caller-made copy must be counted separately. |
For the sample, a uniform index selects pivot value $2$ with probability $3/8$, since it occupies three positions. Each of $1,5,7,8,9$ has probability $1/8$. Choosing uniformly among the six distinct values would be a different distribution. A balanced split is helpful, but randomization does not promise that every pivot is balanced.
How Randomized Quicksort / Quickselect Works, Step by Step
Use the Dutch national flag partition with four regions. Initially set $\mathrm{lt}=i=\mathrm{lo}$ and $\mathrm{gt}=\mathrm{hi}$. Before every iteration:
- $[\mathrm{lo},\mathrm{lt})$ contains values smaller than $p$.
- $[\mathrm{lt},i)$ contains values equal to $p$.
- $[i,\mathrm{gt})$ is unclassified.
- $[\mathrm{gt},\mathrm{hi})$ contains values larger than $p$.
While $i\lt\mathrm{gt}$, classify $A[i]$. If it is smaller than $p$, swap it with $A[\mathrm{lt}]$, then advance both $\mathrm{lt}$ and $i$. If it is larger, decrement $\mathrm{gt}$ and swap $A[i]$ with $A[\mathrm{gt}]$; leave $i$ unchanged because the incoming value has not been classified. If it equals $p$, advance only $i$.
Each iteration reduces the unclassified length $\mathrm{gt}-i$ by exactly one. Consequently, partitioning a range of length $m$ performs exactly $m$ classification iterations, even when some iterations revisit the same array index. Swaps preserve the multiset of values.
flowchart TD
A["Active range with at least two values"] --> B["Choose pivot; set lt = i = lo, gt = hi"]
B --> C{"i less than gt?"}
C -->|Yes| D{"Compare A[i] with pivot"}
D -->|Smaller| E["Swap with lt; advance lt and i"]
D -->|Equal| F["Advance i"]
D -->|Larger| G["Decrease gt; swap with i; keep i"]
E --> C
F --> C
G --> C
C -->|No| H{"Requested task?"}
H -->|Sort| I["Sort both unequal ranges"]
H -->|Select| J{"Position of k?"}
J -->|Before lt| K["Continue in left range"]
J -->|In equal range| L["Return A[k]"]
J -->|At or after gt| M["Continue in right range"]
For Quicksort, stop on ranges of length zero or one. After partitioning, sort the two unequal ranges and leave the equal range untouched. The implementation recurses only into the smaller unequal range and uses a loop to process the larger one. This changes the call-stack usage, not the sorting result. On equal-size sides, it recurses into the right side first; specifying that tie rule also makes the random draw order reproducible.
For Quickselect, maintain $\mathrm{lo}\le k\lt\mathrm{hi}$, with $k$ remaining an absolute array index throughout. If $k\lt\mathrm{lt}$, replace hi by lt. If $k\ge\mathrm{gt}$, replace lo by gt. Otherwise return $A[k]$: it lies in the equal block and has the requested value. A singleton active range supplies the answer directly, without another partition. Do not subtract the discarded prefix from $k$ in this absolute-index version.
Worked Example with a Full Trace
Use $A=[7,2,9,2,5,1,8,2]$, zero-based target rank $k=5$, and seed $6$. The code's specified generator first picks index $1$, so copy pivot $p=2$. Start with $(\mathrm{lt},i,\mathrm{gt})=(0,0,8)$.
The following table records the state after every classification. A “swap” names zero-based indices, and a self-swap is omitted by the code. The comparisons column counts only evaluated key comparisons: < costs one, while reaching > costs one additional comparison.
| Iteration | Value examined and action | Array after action | $(\mathrm{lt},i,\mathrm{gt})$ | Key comparisons this step |
|---|---|---|---|---|
| $1$ | $7\gt2$: decrease gt, swap $0,7$ | $[2,2,9,2,5,1,8,7]$ | $(0,0,7)$ | $2$ |
| $2$ | $2=2$: advance i | $[2,2,9,2,5,1,8,7]$ | $(0,1,7)$ | $2$ |
| $3$ | $2=2$: advance i | $[2,2,9,2,5,1,8,7]$ | $(0,2,7)$ | $2$ |
| $4$ | $9\gt2$: decrease gt, swap $2,6$ | $[2,2,8,2,5,1,9,7]$ | $(0,2,6)$ | $2$ |
| $5$ | $8\gt2$: decrease gt, swap $2,5$ | $[2,2,1,2,5,8,9,7]$ | $(0,2,5)$ | $2$ |
| $6$ | $1\lt2$: swap $0,2$, advance both | $[1,2,2,2,5,8,9,7]$ | $(1,3,5)$ | $1$ |
| $7$ | $2=2$: advance i | $[1,2,2,2,5,8,9,7]$ | $(1,4,5)$ | $2$ |
| $8$ | $5\gt2$: decrease gt, skip self-swap $4,4$ | $[1,2,2,2,5,8,9,7]$ | $(1,4,4)$ | $2$ |
The first partition therefore has $8$ scans, $15$ key comparisons, and $4$ nontrivial swaps. Its equal block is $[1,4)$. Quicksort leaves that block and the singleton $[0,1)$ alone, then processes $[4,8)$. Quickselect also keeps $[4,8)$ because $k=5\ge4$.
The next table finishes the run. The initial state before the first row is the original array; every listed pivot index refers to the array as it exists immediately before that partition. Generator states are recorded after the accepted draw. None of these draws is rejected.
| Partition | Active range | Generator state | Chosen index and copied pivot | Equal block | Array after partition | Added scans / comparisons / swaps |
|---|---|---|---|---|---|---|
| $1$ | $[0,8)$ | $100842$ | Index $1$, pivot $2$ | $[1,4)$ | $[1,2,2,2,5,8,9,7]$ | $8/15/4$ |
| $2$ | $[4,8)$ | $1694851494$ | Index $5$, pivot $8$ | $[6,7)$ | $[1,2,2,2,5,7,8,9]$ | $4/6/2$ |
| $3$ | $[4,6)$ | $1145965850$ | Index $5$, pivot $7$ | $[5,6)$ | $[1,2,2,2,5,7,8,9]$ | $2/3/0$ |
Within partition $2$, the examined values are $5,8,9,7$. The first two advance i; $9$ swaps with index $7$, bringing $7$ into the scan position; $7$ then swaps with the equal-region boundary. The resulting left range is $[4,6)$ and the right range is the singleton $[7,8)$. Within partition $3$, $5$ is smaller than $7$ and requires only an omitted self-swap, then $7$ extends the equal block. No further work remains for Quicksort.
Quickselect keeps $[4,6)$ after partition $2$ because $5\lt6$, then stops in partition $3$ because $5\in[5,6)$. Both runs happen to use the same three partitions here. Their totals are $14$ scans, $24$ key comparisons, $6$ nontrivial swaps, and $3$ raw generator draws.
The selected array happens to be completely sorted in this example. That is incidental: Quickselect promises only the selected value and its partition postcondition. For example, with target rank $2$ on a fresh copy, it stops after the very first partition with $[1,2,2,2,5,8,9,7]$; the suffix remains unsorted.
Trace It by Hand
Pause after the first partition, with equal block $[1,4)$ and array $[1,2,2,2,5,8,9,7]$. Predict the next action for ranks $0$, $2$, and $5$. Rank $0$ keeps singleton $[0,1)$ and returns $1$; rank $2$ is already in the equal block and returns $2$; rank $5$ keeps $[4,8)$ and continues.
Now examine iteration $4$ of the full trace. After swapping $9$ with index $6$, the new value at index $2$ is $8$, which is also larger than the pivot. Advancing i after that swap would skip an unclassified value and break the partition invariant. Keeping i unchanged allows the next step to classify it.
Implementation in Python, C++, and Java
Each panel is a standalone program implementing both algorithms with the same range convention, branch order, side-order tie rule, and sample. Both mutate their input arrays; the sample creates two copies so that selecting does not start from the sorting run's rearranged data. Quicksort accepts an empty array, while selecting from one rejects the rank. The supported seed range is $1$ through $2147483646$, and array length must not exceed $2147483646$.
Using the same numeric seed with three unrelated language-library generators would not generally reproduce the same pivot sequence. Instead, all three programs specify the same small educational generator:
$$s\leftarrow(16807s)\bmod2147483647,\qquad r=s-1.$$
The raw values $r$ lie in $[0,B)$ with $B=2147483646$. To sample an offset below $m$, calculate $L=B-(B\bmod m)$, discard draws with $r\ge L$, and return $r\bmod m$ otherwise. The accepted interval has a multiple of $m$ values, with equal counts for each residue. Thus this conversion avoids modulo bias if the raw draws are uniform; it does not turn a deterministic generator into independent randomness. Use $64$-bit arithmetic for the state multiplication in C++ and Java so that it cannot overflow.
The generator is chosen for transparent, portable replay, not for cryptographic security or as a proof of the ideal-random expected-time theorem. For a fixed seed, every pivot is deterministic. The theoretical analysis below assumes fresh independent uniform pivot indices, and the rejection sampler has expected fewer than two raw draws per offset under independent uniform raw draws. The implementations report the actual draw count, including rejected draws.
Statistics have precise meanings: partitions counts completed partition calls, scans counts classification iterations, comparisons counts evaluated < and > tests against the copied pivot, swaps counts swaps of two different positions, and draws counts state updates in the generator. Loop tests, rank comparisons, seed checks, and integer arithmetic are not key comparisons. A swap of equal-valued entries still counts if their indices differ.
Use Python 3, C++14 or later, or Java 17 or later. Run Python directly; compile C++ with g++ -std=c++14 -O2 main.cpp -o quick; compile and run Java with javac Main.java and java Main. Every program prints these six lines:
sorted = [1, 2, 2, 2, 5, 7, 8, 9]sort: partitions=3, scans=14, comparisons=24, swaps=6, draws=3rank 5 = 7selected = [1, 2, 2, 2, 5, 7, 8, 9]select: partitions=3, scans=14, comparisons=24, swaps=6, draws=3checks = PASS
The example's check verifies the expected full sort, selected value, and selection inequalities. A stronger general test must also verify preservation of the multiset, since a faulty algorithm could return the right rank value while corrupting other entries. Replaying a seed validates a particular path; testing many seeds and small exhaustive inputs exercises many other paths.
Complexity, Correctness, and Invariants
Partitioning a nonempty active range of length $m$ performs exactly $m$ scans and between $m$ and $2m$ key comparisons in this implementation, with $O(1)$ extra state. Pivot selection overhead is accounted for separately through the generator draws. The following bounds use constant-cost key comparisons and the usual ideal uniform-pivot model for expected running time.
Quicksort's best case over all allowed arrays is $\Theta(n)$: on an all-equal array of length at least two, one partition ends the entire sort. For distinct keys, balanced splits instead give best-case $\Theta(n\log n)$ work. For every fixed input its expected work is $O(n\log n)$, and on distinct-key inputs it is $\Theta(n\log n)$. Its worst-case partition work is $\Theta(n^2)$: repeated extreme pivots can leave active sizes $n,n-1,\ldots,2$. Duplicates can reduce the work substantially.
Quickselect's best and expected work are $\Theta(n)$ for nontrivial input; even a first pivot that answers the query requires a full initial partition. Its worst-case work is $\Theta(n^2)$ if pivots repeatedly discard only one value while the requested rank stays in the long side. Empty and singleton sort calls, and singleton selection, take constant time.
The implemented Quicksort has an $O(\log n)$ upper bound on live auxiliary stack space for every pivot sequence: the recursively chosen smaller side has size at most $(m-1)/2$. The larger side reuses the current frame through a loop. Some inputs, including all-equal arrays and repeated extreme splits, use only constant stack space despite very different running times. Quickselect is iterative and uses $O(1)$ live auxiliary space. Caller-made copies and printed output are excluded; the sample's copies use $\Theta(n)$ additional storage. Java also allocates small boundary objects during execution, so total allocation over time differs from live auxiliary space.
| Method | Best time | Expected or typical assumption | Worst time | Auxiliary space and tradeoff |
|---|---|---|---|---|
| Implemented three-way randomized Quicksort | $\Theta(n)$ with all equal keys; $\Theta(n\log n)$ for distinct keys | $O(n\log n)$ for any input with independent uniform pivots | $\Theta(n^2)$ | At most $O(\log n)$ live stack from recursing on the smaller side; not stable. |
| Implemented randomized Quickselect | $\Theta(n)$ | $\Theta(n)$ with independent uniform pivots | $\Theta(n^2)$ | $O(1)$ live auxiliary space; returns one rank, with unsorted sides allowed. |
| Conventional array mergesort | $\Theta(n\log n)$ without adaptive shortcuts | No randomness needed | $\Theta(n\log n)$ | $\Theta(n)$ buffer; can preserve equal-key order. |
| In-place heapsort | $\Theta(n\log n)$ on distinct keys | No randomness needed | $\Theta(n\log n)$ | $O(1)$ auxiliary space; suitable as a sorting fallback. |
| Heap for smallest $q$ values | $\Theta(n)$ when $q=1$ | Typical bound $O(n\log q)$ for $2\le q\le n$ | $O(n\log q)$ | $O(q)$ space; works with a stream instead of retaining the full array. |
| Median-of-medians selection | $\Theta(n)$ | Deterministic rank-based pivot rule | $\Theta(n)$ | Storage depends on implementation; more machinery for a worst-case linear guarantee. |
| Select then sort the smallest $q$ | $\Theta(n+q\log q)$ for distinct keys in a favorable run | Expected $O(n+q\log q)$ using these randomized routines | Can be $\Theta(n^2)$ without fallbacks | Requires careful handling of ties at the cutoff; avoids sorting the other $n-q$ items. |
Why does randomization help? For distinct keys, let $Q(m)$ be expected total partition scans for sorting $m$ items. Since every pivot rank $r$ from $0$ through $m-1$ is equally likely,
$$Q(m)=m+\frac{1}{m}\sum_{r=0}^{m-1}\bigl(Q(r)+Q(m-1-r)\bigr),\qquad m\ge2,$$
with $Q(0)=Q(1)=0$. This recurrence grows as $\Theta(m\log m)$. The familiar exact comparison formulas for other Quicksort partition schemes should not be substituted for these measured counters: this partition scans the pivot too, and sometimes performs two relational tests per scan.
For Quickselect, call a pivot good when its rank is in the middle half of the active elements, up to harmless integer rounding. A uniformly sampled index has at least a constant probability of being good, and a good pivot leaves at most about $3m/4$ elements in either unequal side. Duplicates only help this removal argument by making an entire equal block available to discard or answer the query. The expected number of trials before a good pivot is constant, while bad pivots never increase the active size. Grouping work into shrinking-size phases gives
$$\mathbb E[T(n)]\le Cn\left(1+\frac34+\left(\frac34\right)^2+\cdots\right)=O(n),$$
for a suitable constant $C$ that absorbs partition cost, the expected trials per phase, and small-range rounding. The initial scan gives the matching $\Omega(n)$ lower bound. This expectation is over pivot choices on a fixed input, not an assumption that real-world input arrays arrive in random order.
During partitioning, the four regions are smaller, equal, unclassified, and larger, and every iteration shrinks the unclassified region by one while preserving the multiset. At termination, the nonempty equal block is final: sorting only the two unequal regions orders the whole range, while selecting only the region containing $k$ preserves the desired rank. These arguments hold for every pivot sequence; randomness is used for performance analysis.
A proof by cases establishes the invariant. In the smaller branch, the swapped-out value from the equal region is equal to $p$, so increasing both boundaries restores the regions; if that region was empty, the operation is simply a self-swap. In the larger branch, the outgoing value enters the larger region while the incoming value remains unclassified. In the equal branch, advancing $i$ extends the equal region. Initially the classified regions are empty; when $i=\mathrm{gt}$, no unclassified values remain.
For sorting correctness, apply induction on active-range length: each recursive or iterated child is strictly smaller, each child becomes sorted, and all left values are smaller than the equal block, which is smaller than all right values. For selection, partitioning identifies exactly which absolute positions the equal values occupy in a sorted arrangement of the active multiset. The discarded left and right regions stay on the correct sides of every remaining candidate, so when $k$ reaches an equal block or singleton, the returned value has the original requested rank.
Variants, Pitfalls, and Optimizations
The most expensive duplicate-key mistake is repeatedly processing values already known to equal the pivot. A two-way scheme that puts every equal key on one side can leave sizes $n-1$ and $0$ on an all-equal input, even if the pivot index is randomized. Randomization changes which identical element is selected; it cannot create a useful split between equal values. Three-way partitioning removes the whole equal block in one pass.
Other practical choices affect different properties:
- Keep endpoint conventions consistent. This partition returns an equal interval $[\mathrm{lt},\mathrm{gt})$, not a single final pivot slot. Its children are $[\mathrm{lo},\mathrm{lt})$ and $[\mathrm{gt},\mathrm{hi})$. Lomuto and Hoare partitions have different return contracts; their recursion rules are not interchangeable with these.
- Reclassify incoming values. Never advance $i$ immediately after the greater-than swap. The trace shows why the same index may need several visits, even though the total number of scans is exactly the original range length.
- Keep the pivot value fixed. A reference to the original pivot slot may start referring to another key after swaps. Copy the integer pivot or use a design that otherwise protects the key being compared.
- Preserve the rank convention. Zero-based rank $k$ is the $(k+1)$-st smallest value. Absolute-index selection keeps $k$ unchanged; a version that copies or rebases subarrays must adjust its local rank explicitly. For an even-length median, specify whether you want the lower middle, upper middle, or mean of the two; selecting one rank does not compute that mean.
- Improve worst-case guarantees deliberately. Introsort falls back to heapsort when partition depth becomes too large, giving worst-case $O(n\log n)$ sorting. For selection, a fallback policy must account for the work already spent: a depth threshold alone can permit $O(n\log n)$ earlier work, so it is not by itself a proof of worst-case linear selection. A linear work budget followed by a correct deterministic linear selector can support such a guarantee.
- Use small-range cutoffs or better pivot sampling carefully. Insertion sort on tiny ranges often reduces overhead. Median-of-three or larger random samples can improve split quality, but deterministic sampling is not an adversarial worst-case guarantee. Either change alters the measured trace and statistics.
- Choose stability and mutation consciously. The shown algorithms reorder equal-key records. If stable ordering matters, choose a stable algorithm or design a separate stable partition with its associated storage cost. Copy input data when callers need it preserved.
An ordering must be consistent. Floating-point NaN is neither smaller, larger, nor equal under ordinary comparisons, so this integer-oriented branch structure would incorrectly treat it as equal to a pivot. A floating-point version must specify an explicit total ordering and a NaN policy. For records, compare keys consistently and avoid subtraction-based comparators that can overflow on extreme integers.
Reproducibility requires more than recording a seed: record the generator, range-conversion rule, array order, comparison order, and traversal order. A public fixed seed is useful for teaching and regression tests but does not prevent crafted inputs. In production, choose an appropriate generator and fallback strategy for the workload; the correctness invariant should remain valid even if every pivot is poor.
Do not recurse into the equal block, skip the value swapped into the scan position, or mix partition return conventions. Expected linear selection and expected $O(n\log n)$ sorting are not worst-case guarantees, and a fixed seed is not a source of independent randomness. Test duplicates, extreme ranks, bad pivot sequences, permutation preservation, and the selection inequalities as well as the final answer.
Practice Problems and Real-World Use
Validate sorting against an independently sorted copy and verify identical multiplicities. Validate selection against the sorted copy's $k$-th value, check $A[k]$, verify that earlier values are at most it and later values are at least it, and verify the multiset. Do not require Quickselect's entire output to match a sorted array or another implementation's arrangement.
For exhaustive small tests, enumerate arrays over a tiny alphabet such as $\{-1,0,1\}$ and test every valid rank across several seeds. Add sorted and reverse-sorted distinct arrays, all-equal arrays, alternating extremes, duplicate-heavy data, empty sorts, singleton selection, rejected ranks, and invalid seeds. Instrument partition boundaries to check the four-region invariant after every step; test the random range conversion separately with a small enumerable raw range so that every residue count can be verified exactly.
| Problem or system | Idea it tests | Difficulty |
|---|---|---|
| Reproduce the sample trace | Match every array transition and the $14$ scans, $24$ key comparisons, $6$ swaps, and $3$ draws. | Beginner |
| K-th smallest with repeated values | Use zero-based absolute rank and terminate anywhere inside the equal block. | Beginner |
| Median of a batch | Specify the even-size convention; obtain the correct one or two order statistics. | Intermediate |
| Top-$q$ leaderboard | Select a cutoff, handle ties explicitly, then sort only the retained $q$ items if ordered output is required. | Intermediate |
| Response-time percentile analysis | Define the rank or interpolation convention before selecting values; do not confuse an exact batch result with a streaming quantile estimate. | Intermediate |
| Duplicate-heavy inventory sorting | Show one-partition behavior on equal keys and compare with a two-way partition that handles equality poorly. | Intermediate |
| Adversarial pivot laboratory | Force extreme pivots and observe quadratic scan counts while the smaller-side recursion keeps the stack bounded. | Advanced |
| Production fallback design | Track partition work or depth, select a justified fallback, and distinguish sorting and selection guarantees. | Advanced |
For further study, Princeton's Quicksort material discusses randomized and three-way partitioning, while MIT's randomized algorithms lecture notes develop the role of randomized pivots and expected running time. Use their analysis alongside the explicit counter definitions here; changing a partition scheme changes its exact operation counts.