1

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

j0can
  • 21
  • 6

2 Answers2

2

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"
}
rcgldr
  • 27,407
  • 3
  • 36
  • 61
  • what is the use of while(1), and how would i come across this functions without using breaks. – j0can Mar 12 '21 at 12:29
  • @joexyz - the while(1) ... breaks are to used to make up for Python's lack of a pre-increment (++) or pre-decrement (--) operator and also lack of a do ... while loop. – rcgldr Mar 12 '21 at 19:18
  • Could you give me an example of a similar code but without while(break)?, would be very educational by the understanding that python lack pre incr and pre dec. – j0can Mar 15 '21 at 15:32
  • 1
    @joexyz - take a look at the code in [this answer](https://stackoverflow.com/questions/66531479/66534116#66534116) – rcgldr Mar 15 '21 at 17:16
0

Inside your inplace_quick_sort_nonrecursive function, the way you're calculating pivot doesn't make any sense. When you partition the subarray A[start, end] into three parts according to the value of the pivot(which can be chosen randomly or intentionally, like initially here you're choosing the last element as pivot):
A[start, p − 1], A[p], and A[p + 1...end], where p is where the pivot is placed at. The left and right part of the pivot satisfies the following conditions:
• A[i] ≤ A[p], i ∈ [start, p − 1],
• A[i] > A[p], i ∈ [p + 1, end]

Use this for partitioning:

def partition(array, lo, hi):
    pivot = array[hi]
    i = lo - 1
    for j in range(lo, hi):
        if array[j] < pivot:
            i += 1
            temp = array[i]
            array[i] = array[j]
            array[j] = temp
    temp = array[i + 1]
    array[i + 1] = array[hi]
    array[hi] = temp
    return i + 1

Read about Lomuto's Partition method to better understand the code.

notnotparas
  • 177
  • 1
  • 11
  • this does not answer the question, "How do i make the bottom function [`inplace_quicksort_non_recursive`] non-recursive"? – Will Ness Oct 31 '21 at 18:39