Problem

Break the quicksort

sorting divide and conquer security contests

A service accepts lists of numbers from users and sorts them with this function: quicksort with Hoare’s partition, where the pivot is the middle element of the piece. The function returns the number of comparisons made:

def quicksort(a):
    count = 0
    stack = [(0, len(a) - 1)]
    while stack:
        lo, hi = stack.pop()
        if hi <= lo:
            continue
        p = (lo + hi) // 2
        a[lo], a[p] = a[p], a[lo]
        x, i, j = a[lo], lo - 1, hi + 1
        while True:
            i += 1
            count += 1
            while a[i] < x:
                i += 1
                count += 1
            j -= 1
            count += 1
            while x < a[j]:
                j -= 1
                count += 1
            if i >= j:
                break
            a[i], a[j] = a[j], a[i]
        stack.append((j + 1, hi))
        stack.append((lo, j))
    return count

On random data it needs on the order of n log₂ n comparisons, and on already sorted data too. The owners of the service are sure it will always be so. Show that they are wrong: construct a permutation of the numbers from 1 to n on which the function makes at least ⌊n²/4⌋ comparisons.

Input

One number n (1 ≤ n ≤ 1000).

Output

n distinct numbers from 1 to n separated by spaces. If several permutations fit, print any.

Examples

Example 1

Input
8
Output
5 3 7 1 2 4 6 8

Limits

Time · Python
4000 ms
Time · Go
1000 ms
Memory
256 MB
Judging
10 tests

Walkthrough

Open it step by step: each step gives away a little more. You can jump straight to the solution, but a problem solved with one hint stays with you longer.

1Hint 1

Sorted and reversed inputs do not work here: the middle element of a sorted piece is its median, and the partition comes out perfect. You need a permutation where the pivot is almost the smallest of its piece time after time.

2Hint 2

There is no need to derive a formula for such a permutation. You can run the very same sort on “unknown” numbers and invent their values as you go: answer each comparison in the way that is worst for the sort, but without contradictions.

3Hint 3

Let an element that has not yet been compared with anything be “gas”: it has no value yet and is larger than all the known ones. When two gases are compared, one of them has to be given the next value up. Which one is it better to give it to: the one that seems to be serving as the pivot?

4The idea

This is Douglas McIlroy’s killer adversary (the paper “A Killer Adversary for Quicksort”, 1999). All elements start as gas. A frozen element is always smaller than gas. When two gases meet, one of them freezes, receiving the next smallest value, and the one to choose is the “pivot candidate”: the gas that most recently survived a comparison. The pivot takes part in every comparison of the partition, so it freezes first and ends up smaller than all the other gases. The partition cuts off one or two elements from the piece, and the recursion goes on the order of n deep.

The adversary’s answers never contradict each other: frozen values never change, gas is larger than any frozen element, and a comparison of two gases always begins with one of them freezing. At the end one element remains gas, and it gets the largest value: the result is a permutation. If this permutation is now sorted for real, the function makes exactly the same comparisons as with the adversary’s answers.

5Walkthrough

The reference solution is the same function as in the statement, only instead of a[i] < x it calls the adversary’s less(a[i], x), and the array holds element numbers whose values are stored separately. The sort moves the numbers, the values stay where they are, and once it finishes value[k] + 1 is exactly the number to put at place k.

The result has a wide margin: for n = 1000, 250,000 comparisons are needed, and the adversary’s permutation forces 501,498, almost n²/2, more than bubble sort. For the first example, n = 8, it is 42 comparisons instead of the 16 required.

The most valuable thing here is that the adversary knows nothing about how the sort works: neither how it chooses the pivot nor how it partitions. It only answers comparisons. That is why the same code breaks both the first-element choice and the median of three. Only randomness protects against it: if the pivot is chosen at random and the adversary does not see the flips, a permutation built in advance works like ordinary random data. More in the article on quicksort.

5+Solution
6Complexity

The adversary runs the attacked sort itself, so it costs as much as that sort’s worst case: O(n²) comparisons, half a million for n = 1000, fractions of a second even in Python.

7Common mistakes
  • Sorted or reversed input. It ruins a sort whose pivot is the first element, but here the pivot is the middle one, and such input is its best case.
  • The adversary’s values without the plus one. The adversary hands out values from 0 to n − 1, but a permutation of the numbers from 1 to n is needed.
  • A different sort in the simulation. The adversary builds an input against the function that is run. The order in which pieces are visited does not matter, since the set of comparisons does not depend on it, but a different partition or a different pivot choice will give a permutation that is deadly for a different sort.

Algorithms behind it