5
\$\begingroup\$

I have implemented an iterative in-place quicksort algorithm in Python, which by default selects the pivot as the last element:

def quicksort(array):
    """
    Sorts an array of integers using the quick-sort algorithm.

    :param array: the array to be sorted
    :type array: list<int>
    :return: the sorted array
    :rtype: list<int>
    """
    indexes_stack = list()
    idx = (0, len(array) - 1)
    indexes_stack.append(idx)
    for idx in indexes_stack:
        elem_idx = idx[0]
        pivot_idx = idx[1]
        while pivot_idx > elem_idx:
            pivot = array[pivot_idx]
            elem = array[elem_idx]
            if elem > pivot:
                array[pivot_idx] = elem
                array[elem_idx] = array[pivot_idx - 1]
                array[pivot_idx - 1] = pivot
                pivot_idx -= 1
            else:
                elem_idx += 1

        boundar_low = idx[0]
        boundar_high = idx[1]
        if pivot_idx > 1 and boundar_low < pivot_idx - 1:
            indexes_stack.append((boundar_low, pivot_idx - 1))
        if pivot_idx < len(array) -1 and pivot_idx + 1 < boundar_high:
            indexes_stack.append((pivot_idx + 1, boundar_high))

    return array

# Test the sorting function
counter = 0
while counter < 1000:
    test = [int(100 * random.random()) for i in xrange(15)]
    assert (quicksort(list(test)) == sorted(test)), test
    counter += 1

I've tested it with various inputs and it seems to be correct, but I wonder if it can become more efficient or clean.

\$\endgroup\$
1
  • \$\begingroup\$ Thanks for catching the bug, I re-written function and the testing code to fix it. \$\endgroup\$
    – Vasilis
    Oct 25, 2017 at 21:40

1 Answer 1

3
\$\begingroup\$

The implied any aspect of the code posted is fair game for feedback and criticism justifies asking on CR even without an(y) explicit question. I'm taken against guessing what any piece of code is there for: Why another python quicksort? doesn't get answered in the code.
While the docstring looks charming, the parameter to quicksort() is over-specified: everything needed is "subscripting" to get&set items and comparable items. (Numeric items could be deemed handy for picking as a pivot the mean value of several.)
The code mentions neither iterative nor in-place:
quicksort() should be specified to return its (ordered) parameter.

clean, like beauty, lies in the eye of the beholder - to mine, both are close regarding code. I like python for its ability to express things with minimum ado.
(I don't quite like elem_idx&pivot_idx - sometimes use up&down.)

In demonstration code, it seems to have grown conventional to factor out partitioning the current sequence from quicksort().

True? The code doesn't use indexes_stack as a stack. As far as in-place means o(n) space (in addition to output is where input used to be), the implementation presented is not in-place.

More efficient? Sure -

  • Space: handle the smallest partition first (with impeding out-of-memory, this is a correctness issue)
  • Time:

    • choice of pivot is pivotal, using a value from either end of the partition disastrous for pre-ordered input.
    • Hoare partition scheme instead of Lomuto - three-way if lots of repeated values are to be expected
    • don't bother with less than two items, better yet:
      use a different algorithm for small partitions
    • don't push the top-priority partition to immediately pop it

In stead of further ranting (keeping elem, elem_idx & pivot_idx for recognition value):

# non-recursive quicksort - re-inventing the wheel finger exercise 

# docs.python.org tutorial:
# To add an item to the top of the stack, use append()
class stack(list):
    ''' push() to add an item to the top of the stack '''
    push = list.append


# non-recursive quicksort
def quicksort(items):
    """
    Sort a sequence using the quick-sort algorithm.

    :param items: the sequence to be sorted
    :return: items, sorted
    """
    nItems = len(items)
    if nItems < 2:
        return items
    todo = stack([(0, nItems - 1)])
    while todo:
        elem_idx, pivot_idx = low, high = todo.pop()
        elem = items[elem_idx]
        pivot = items[pivot_idx]
        while pivot_idx > elem_idx:
            if elem > pivot:
                items[pivot_idx] = elem
                pivot_idx -= 1
                items[elem_idx] = elem = items[pivot_idx]
            else:
                elem_idx += 1
                elem = items[elem_idx]
        items[pivot_idx] = pivot

        lsize = pivot_idx - low
        hsize = high - pivot_idx
        if lsize <= hsize:
            if 1 < lsize:
                todo.push((pivot_idx + 1, high))
                todo.push((low, pivot_idx - 1))
        else:
            todo.push((low, pivot_idx - 1))
        if 1 < hsize:
            todo.push((pivot_idx + 1, high))
    return items


if __name__ == '__main__':
# run the sorting function
    from random import randint
    for _ in range(99):
        test = [str(randint(0, 100)) for _ in range(randint(15, 99))]
        assert (quicksort(test[:]) == sorted(test)), test
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.