← All reports

HW3 — Java Report: Randomized Quicksort

Language / runtime

Random pivot / RNG

A single java.util.Random instance (RNG, default no-arg constructor — seeded from system entropy/nanoTime internally by the JDK) is used for both:

External code

No external code used — original implementation based on CLRS RANDOMIZED-QUICKSORT / RANDOMIZED-PARTITION pseudocode (Cormen, Leiserson, Rivest & Stein, Introduction to Algorithms, Ch. 5). No sorting-library calls (Arrays.sort/Collections.sort) are used anywhere in java/Main.java.

Results

Full results (also in HW3/results/java.csv):

n time_seconds
1,000 0.000642300
2,000 0.000275884
4,000 0.000465054
8,000 0.000868781
16,000 0.000935647
32,000 0.001790130
64,000 0.003738452
128,000 0.008408203
256,000 0.018320994
512,000 0.042214384
1,024,000 0.104890228
2,048,000 0.211256140
4,096,000 0.373476923
8,192,000 0.839406327
16,384,000 1.532307679
32,768,000 3.322192756
65,536,000 7.808886121

16 iterations ran. The loop stopped after n = 65,536,000 because that iteration's elapsed time (7.808886121 s) exceeded the 4.0 s cutoff (the next-n-would-exceed-cap and 25-iteration conditions were not yet reached). Total wall-clock time for the whole run (compile excluded, JVM startup plus all 16 sort iterations, time java Main) was ~15.2 seconds.

Growth analysis

The observed growth is consistent with the expected O(n log n) behavior. For an O(n log n) algorithm, doubling n should roughly double the running time, scaled up slightly by the log(2n)/log(n) factor (e.g. around 2.1x-2.4x for n in the millions, converging toward 2x asymptotically). From n = 32,000 onward the measured per-doubling ratios are consistently in the ~1.8x-2.5x range (e.g. 32,768,000 -> 65,536,000 is a 2.35x increase for a 2x increase in n, and 512,000 -> 1,024,000 is a 2.49x increase), matching that expectation well, and the timings scale smoothly and monotonically for all n >= 32,000.

The smallest n values (1,000 through 16,000) show the expected anomalies rather than clean O(n log n) scaling: n=1000 (0.64 ms) is actually slower than n=2000 (0.28 ms), and n=8000 -> n=16000 shows almost no increase (0.87 ms -> 0.94 ms). This is the classic JIT warmup effect — the JVM starts in interpreted/C1 mode and only promotes hot methods (partition, randomizedPartition, randomizedQuicksort, nextInt) to optimized C2-compiled native code after enough invocations, which for such small arrays happens mid-run rather than before the first timed call. At these sub-millisecond durations, System.nanoTime() measurements are also more susceptible to OS scheduling jitter and background JVM activity (e.g. class loading, GC bookkeeping) than to the algorithm's actual asymptotic cost, so the first several data points are noisy by nature. By n = 32,000 and beyond, JIT compilation has stabilized and the measured growth tracks the theoretical O(n log n) curve closely, with no GC-pause anomalies visible in the larger-n timings despite arrays up to 65,536,000 ints (~262 MB) being allocated and sorted each iteration.