Search code examples
pythonpivotquicksortnon-recursive

Non-recursive Quicksort


How do i make the bottom function non-recursive, ive tried but by creating new functions which is not the point in this problem. The first function is given and the inplace_quicksort_non_recursive is created by me.

import random

def inplace_quick_sort(S, a, b):
  """Sort the list from S[a] to S[b] inclusive using the quick-sort algorithm."""
  if a >= b: return                                      # range is trivially sorted
  pivot = S[b]                                           # last element of range is pivot
  left = a                                               # will scan rightward
  right = b-1                                            # will scan leftward
  while left <= right:
    # scan until reaching value equal or larger than pivot (or right marker)
    while left <= right and S[left] < pivot:
      left += 1
    # scan until reaching value equal or smaller than pivot (or left marker)
    while left <= right and pivot < S[right]:
      right -= 1
    if left <= right:                                    # scans did not strictly cross
      S[left], S[right] = S[right], S[left]              # swap values
      left, right = left + 1, right - 1                  # shrink range

  # put pivot into its final place (currently marked by left index)
  S[left], S[b] = S[b], S[left]
  # make recursive calls
  inplace_quick_sort(S, a, left - 1)
  inplace_quick_sort(S, left + 1, b)
  return left
  

def inplace_quick_sort_nonrecursive(S):
    stack = []                # create a stack for storing sublist start and end index
    a = 0                             # get the starting and ending index of a given list
    b = len(S) - 1
    pivot = S[b]
 
    stack.append((a, b))              # push the start and end index of the array into the stack

    while len(stack) > 0:                      # loop till stack is empty
      a, b = stack.pop()              # remove top pair from the list and get sublist starting and ending indices
      pivot = inplace_quick_sort(S, a, b)           # rearrange elements across pivot
      if pivot - 1 > a:               # push sublist indices containing elements that are less than the current pivot to stack
        stack.append((a, pivot - 1))
      if pivot + 1 < b:               # push sublist indices containing elements that are more than the current pivot to stack
        stack.append((pivot + 1, b))


origList = random.sample(range(100), 100)
origList2 = random.sample(range(100), 100)
origList.extend(origList2)
inplace_quick_sort_nonrecursive(origList)

errorIndices = []
for i in range(100):
  ind1 = 2*i
  ind2 = ind1+1
  if origList[ind1] != i:
    errorIndices.append(ind1)
  if origList[ind2] != i:
    errorIndices.append(ind2)
if len(errorIndices) == 0:
  print("PASSED")
else:
  print("Error in indices: " + str(errorIndices))

What do i need to create so the bottom function becomes non-recursive


Solution

  • The question is using a variation of Hoare partition scheme (but with issues). Example with classic Hoare partition scheme. Note Hoare splits partition into elements <= pivot and elements >= pivot; the pivot and elements == pivot can end up anywhere, so Hoare only splits partition into 2 parts (the pivot is not put into place and cannot be excluded from later partition steps).

    import random
    from time import time
    
    def qsort(a):
        if len(a) < 2:                      # if nothing to sort, return
            return
        stack = []                          # initialize stack
        stack.append([0, len(a)-1])
        while len(stack) > 0:               # loop till stack empty
            lo, hi = stack.pop()            # pop lo, hi indexes
            p = a[(lo + hi) // 2]           # pivot, any a[] except a[hi]
            i = lo - 1                      # Hoare partition
            j = hi + 1
            while(1):
                while(1):                   #  while(a[++i] < p)
                    i += 1
                    if(a[i] >= p):
                        break
                while(1):                   #  while(a[--j] < p)
                    j -= 1
                    if(a[j] <= p):
                        break
                if(i >= j):                 #  if indexes met or crossed, break
                    break
                a[i],a[j] = a[j],a[i]       #  else swap elements
            if(j > lo):                     # push indexes onto stack
                stack.append([lo, j])
            j += 1
            if(hi > j):
                stack.append([j, hi])
    
    # test sort
    a = [random.randint(0, 2147483647) for r in range(512*1024)]
    s = time()
    qsort(a)
    e = time()
    print e - s
    
    # check to see if data was sorted
    f = 0
    for i in range (1 ,len(a)):
        if(a[i-1] > a[i]):
            f = 1
            break
    if(f == 0):
        print("sorted")
    else:
        print("error")
    

    In response to comment, a simple example in C++.

    void QuickSort(uint64_t arr[], size_t lo, size_t hi)
    {
    uint64_t * stk = new size_t [hi+1-lo];  // allocate "stack"
    size_t stki = 0;                        // stack index
    
        stk[stki+1] = hi;                   // init stack
        stk[stki+0] = lo;
        stki += 2;
        while(stki != 0){
            stki -= 2;
            lo = stk[stki+0];
            hi = stk[stki+1];
            if(lo >= hi)
                continue;
            uint64_t pivot = arr[lo + (hi - lo) / 2];
            size_t i = lo - 1;
            size_t j = hi + 1;
            while (1)
            {
                while (arr[++i] < pivot);
                while (arr[--j] > pivot);
                if (i >= j)
                    break;
                std::swap(arr[i], arr[j]);
            }
            stk[stki+3] = hi;
            stk[stki+2] = j+1;
            stk[stki+1] = j;
            stk[stki+0] = lo;
            stki += 4;
        }
        delete [] stk;              // free "stack"
    }
    

    The original version of quicksort limited stack space to 2*ceil(log2(size)) by pushing indexes of larger partition onto stack, and looping on smaller partition. Hoare used the term "nest" instead of stack in his original paper.

    void QuickSort(uint64_t arr[], size_t lo, size_t hi)
    {
        if(lo >= hi)return;                 // if less than 2 elements, return
                                            // allocate stack
        size_t s = 2*(size_t)(ceil(log2(hi+1-lo)));
        uint64_t * stk = new size_t [s];
        s = 0;                              // stack index
        stk[s++] = hi;                      // init stack
        stk[s++] = lo;
        while(s != 0){                      // while more partitions
            lo = stk[--s];
            hi = stk[--s];
            while(lo < hi){                 // while partion size > 1
                uint64_t pivot = arr[lo+(hi-lo)/2];
                size_t i = lo-1;            // Hoare parttion
                size_t j = hi+1;
                while (1)
                {
                    while (arr[++i] < pivot);
                    while (arr[--j] > pivot);
                    if (i >= j)
                        break;
                    std::swap(arr[i], arr[j]);
                }
                i = j++;                    // i = last left, j = first right
                if(i - lo > hi - j){        // push larger partition indexes to stack,
                    if (lo < i) {           //  loop on smaller
                        stk[s++] = i;
                        stk[s++] = lo;
                    }
                    lo = j;
                    continue;
                } else {
                    if (j < hi) {
                        stk[s++] = hi;
                        stk[s++] = j;
                    }
                    hi = i;
                    continue;
                }
            }
        }
        delete [] stk;                      // free "stack"
    }