Search code examples
algorithmsortingquicksortdata-partitioning

Lomuto's Partition, stable or not?


I tried to search on Web and in my algorithms book if the Lomuto's specific solution of QSort Partition is stable or not (I know that the Hoare's version is unstable) but i didn't find a precise answer.
So I've tried to make same examples and it seems stable. But I didn't demonstrate it. Could you help me? If it is not stable, could you find me an example of input?


Solution

  • I'm going to interpret "Quicksort with Lomuto's partition" as referring to the algorithm from here (slides 21–22).

    This algorithm is unstable on the array [a, b, c] where c < a = b.


    I found this counterexample by implementing the Quicksort algorithm in Python so that (like Python's built-in sort) it takes a key function. By supplying an appropriate key function, I can make the sort think that certain elements are identical, but I can still distinguish them. Then it's just a matter of trying lots of permutations and spotting instability. The code below certainly doesn't exhaust the possible tests (one might want to try more than two identical elements, or multiple sets of identical elements), but it was good enough in this case.

    def lomuto(A, key=lambda x:x):
        def partition(A, p, r):
            i = p - 1
            pivot = A[r]
            for j in range(p, r):
                if key(A[j]) <= key(pivot):
                    i += 1
                    A[i], A[j] = A[j], A[i]
            A[i+1], A[r] = A[r], A[i+1]
            return i + 1
    
        def quicksort(A, p, r):
            if p < r:
                q = partition(A, p, r)
                quicksort(A, p, q-1)
                quicksort(A, q+1, r)
    
        quicksort(A, 0, len(A) - 1)
    
    def test_stability(f, n):
        """Try to discover if the sorting function f is stable on n inputs;
    printing the first counterexample found, if any."""
        import itertools
        for i in range(n - 1):
            def order(P): return P.index((i, 0)) < P.index((i, 1))
            array = [(j, 0) for j in range(n - 1)] + [(i, 1)]
            for P in map(list, itertools.permutations(array)):
                Q = P[:] # take a copy
                f(Q, key=lambda x: x[0])
                if order(P) != order(Q):
                    print(P, '->', Q)
                    return
    
    >>> test_stability(lomuto, 3)
    [(1, 0), (1, 1), (0, 0)] -> [(0, 0), (1, 1), (1, 0)]