QuickSort Tail Call Optimization (Reducing worst case space to Log n )

30 May, 2022
Prerequisite : Tail Call Elimination
In QuickSort, partition function is in-place, but we need extra space for recursive function calls. A simple implementation of QuickSort makes two calls to itself and in worst case requires O(n) space on function call stack. 
The worst case happens when the selected pivot always divides the array such that one part has 0 elements and other part has n-1 elements. For example, in below code, if we choose last element as pivot, we get worst case for sorted arrays (See this for visualization)
C
Java
Python3
C#
Javascript
/* A Simple implementation of QuickSort that makes two   two recursive calls. */void quickSort(int arr[], int low, int high){    if (low < high)    {        /* pi is partitioning index, arr[p] is now           at right place */        int pi = partition(arr, low, high);          // Separately sort elements before        // partition and after partition        quickSort(arr, low, pi - 1);        quickSort(arr, pi + 1, high);    }}// See below link for complete running code// http://geeksquiz.com/quick-sort/
// A Simple implementation of QuickSort that// makes two recursive calls.static void quickSort(int arr[], int low, int high){    if (low < high)    {                 // pi is partitioning index, arr[p] is        // now at right place        int pi = partition(arr, low, high);                 // Separately sort elements before        // partition and after partition        quickSort(arr, low, pi - 1);        quickSort(arr, pi + 1, high);    }} // This code is contributed by rutvik_56
# Python3 program for the above approachdef quickSort(arr, low, high):         if (low < high):             # pi is partitioning index, arr[p] is now        # at right place        pi = partition(arr, low, high)           # Separately sort elements before        # partition and after partition        quickSort(arr, low, pi - 1)        quickSort(arr, pi + 1, high) # This code is contributed by sanjoy_62
// A Simple implementation of QuickSort that// makes two recursive calls.static void quickSort(int []arr, int low, int high){    if (low < high)    {                 // pi is partitioning index, arr[p] is        // now at right place        int pi = partition(arr, low, high);                 // Separately sort elements before        // partition and after partition        quickSort(arr, low, pi - 1);        quickSort(arr, pi + 1, high);    }} // This code is contributed by pratham76.
<script>// A Simple implementation of QuickSort that// makes two recursive calls.function quickSort(arr , low , high){    if (low < high)    {                 // pi is partitioning index, arr[p] is        // now at right place        var pi = partition(arr, low, high);                 // Separately sort elements before        // partition and after partition        quickSort(arr, low, pi - 1);        quickSort(arr, pi + 1, high);    }} // This code is contributed by umadevi9616.</script>
Can we reduce the auxiliary space for function call stack? We can limit the auxiliary space to O(Log n). The idea is based on tail call elimination. As seen in the previous post, we can convert the code so that it makes one recursive call. For example, in the below code, we have converted the above code to use a while loop and have reduced the number of recursive calls.
C
Java
Python3
C#
Javascript
/* QuickSort after tail call elimination using while loop */void quickSort(int arr[], int low, int high){    while (low < high)    {        /* pi is partitioning index, arr[p] is now           at right place */        int pi = partition(arr, low, high);         // Separately sort elements before        // partition and after partition        quickSort(arr, low, pi - 1);         low = pi+1;    }}// See below link for complete running code// https://ide.geeksforgeeks.org/qrlM31
/* QuickSort after tail call elimination using while loop */static void quickSort(int arr[], int low, int high){    while (low < high)    {        /* pi is partitioning index, arr[p] is now           at right place */        int pi = partition(arr, low, high);         // Separately sort elements before        // partition and after partition        quickSort(arr, low, pi - 1);         low = pi+1;    }}// See below link for complete running code// https://ide.geeksforgeeks.org/qrlM31 // This code is contributed by gauravrajput1
# QuickSort after tail call elimination using while loop '''def quickSort(arr, low, high):    while (low < high):         # pi is partitioning index, arr[p] is now        #  at right place '''        pi = partition(arr, low, high)         # Separately sort elements before        # partition and after partition        quickSort(arr, low, pi - 1)         low = pi+1 # See below link for complete running code# https:#ide.geeksforgeeks.org/qrlM31 # This code is contributed by gauravrajput1
/* QuickSort after tail call elimination using while loop */static void quickSort(int []arr, int low, int high){    while (low < high)    {        /* pi is partitioning index, arr[p] is now           at right place */        int pi = partition(arr, low, high);         // Separately sort elements before        // partition and after partition        quickSort(arr, low, pi - 1);         low = pi+1;    }}// See below link for complete running code// https://ide.geeksforgeeks.org/qrlM31  // This code contributed by gauravrajput1
<script>/* QuickSort after tail call elimination using while loop */function quickSort(arr , low , high){    while (low < high)    {        /* pi is partitioning index, arr[p] is now           at right place */        var pi = partition(arr, low, high);         // Separately sort elements before        // partition and after partition        quickSort(arr, low, pi - 1);         low = pi+1;    }}// See below link for complete running code// https://ide.geeksforgeeks.org/qrlM31 // This code is contributed by gauravrajput1</script>
Although we have reduced number of recursive calls, the above code can still use O(n) auxiliary space in worst case. In worst case, it is possible that array is divided in a way that the first part always has n-1 elements. For example, this may happen when last element is choses as pivot and array is sorted in increasing order. 
We can optimize the above code to make a recursive call only for the smaller part after partition. Below is implementation of this idea.
Further Optimization :  
C++
C
Java
Python3
C#
Javascript
// C++ program of th above approach#include <bits/stdc++.h>using namespace std; void quickSort(int arr[], int low, int high){  while (low < high)  {    /* pi is partitioning index, arr[p] is now           at right place */    int pi = partition(arr, low, high);     // If left part is smaller, then recur for left    // part and handle right part iteratively    if (pi - low < high - pi)    {      quickSort(arr, low, pi - 1);      low = pi + 1;    }     // Else recur for right part    else    {      quickSort(arr, pi + 1, high);      high = pi - 1;    }  }} // This code is contributed by code_hunt.
/* This QuickSort requires O(Log n) auxiliary space in   worst case. */void quickSort(int arr[], int low, int high){    while (low < high)    {        /* pi is partitioning index, arr[p] is now           at right place */        int pi = partition(arr, low, high);         // If left part is smaller, then recur for left        // part and handle right part iteratively        if (pi - low < high - pi)        {            quickSort(arr, low, pi - 1);            low = pi + 1;        }         // Else recur for right part        else        {            quickSort(arr, pi + 1, high);            high = pi - 1;        }    }}// See below link for complete running code// https://ide.geeksforgeeks.org/LHxwPk
/* This QuickSort requires O(Log n) auxiliary space in   worst case. */static void quickSort(int arr[], int low, int high){    while (low < high)    {        /* pi is partitioning index, arr[p] is now           at right place */        int pi = partition(arr, low, high);         // If left part is smaller, then recur for left        // part and handle right part iteratively        if (pi - low < high - pi)        {            quickSort(arr, low, pi - 1);            low = pi + 1;        }         // Else recur for right part        else        {            quickSort(arr, pi + 1, high);            high = pi - 1;        }    }}// See below link for complete running code// https://ide.geeksforgeeks.org/LHxwPk // This code is contributed by gauravrajput1
''' This QuickSort requires O(Log n) auxiliary space in   worst case. '''def quickSort(arr, low, high){    while (low < high):        ''' pi is partitioning index, arr[p] is now           at right place '''        pi = partition(arr, low, high);         # If left part is smaller, then recur for left        # part and handle right part iteratively        if (pi - low < high - pi):            quickSort(arr, low, pi - 1);            low = pi + 1;                 # Else recur for right part        else:            quickSort(arr, pi + 1, high);            high = pi - 1; # See below link for complete running code# https:#ide.geeksforgeeks.org/LHxwPk # This code is contributed by gauravrajput1
/* This QuickSort requires O(Log n) auxiliary space in   worst case. */static void quickSort(int []arr, int low, int high){    while (low < high)    {        /* pi is partitioning index, arr[p] is now           at right place */        int pi = partition(arr, low, high);         // If left part is smaller, then recur for left        // part and handle right part iteratively        if (pi - low < high - pi)        {            quickSort(arr, low, pi - 1);            low = pi + 1;        }         // Else recur for right part        else        {            quickSort(arr, pi + 1, high);            high = pi - 1;        }    }}// See below link for complete running code// https://ide.geeksforgeeks.org/LHxwPk  // This code is contributed by gauravrajput1
<script>/* This QuickSort requires O(Log n) auxiliary space in   worst case. */function quickSort(arr , low , high){    while (low < high)    {        /* pi is partitioning index, arr[p] is now           at right place */        var pi = partition(arr, low, high);         // If left part is smaller, then recur for left        // part and handle right part iteratively        if (pi - low < high - pi)        {            quickSort(arr, low, pi - 1);            low = pi + 1;        }         // Else recur for right part        else        {            quickSort(arr, pi + 1, high);            high = pi - 1;        }    }}// See below link for complete running code// https://ide.geeksforgeeks.org/LHxwPk  // This code contributed by gauravrajput1</script>
In the above code, if left part becomes smaller, then we make recursive call for left part. Else for the right part. In worst case (for space), when both parts are of equal sizes in all recursive calls, we use O(Log n) extra space. 
Reference: http://www.cs.nthu.edu.tw/~wkhon/algo08-tutorials/tutorial2b.pdf
This article is contributed by Dheeraj Jain. Please write comments if you find anything incorrect, or you want to share more information about the topic discussed above 
rutvik_56
pratham76
sanjoy_62
umadevi9616
GauravRajput1
rajdaveomns1
code_hunt
Quick Sort
Sorting
Sorting
Writing code in comment?
Please use ide.geeksforgeeks.org,
generate link and share the link here.
Chocolate Distribution Problem
Longest Common Prefix using Sorting
Sort a nearly sorted (or K sorted) array
Maximize difference between the Sum of the two halves of the Array after removal of N elements
Segregate 0s and 1s in an array
Quickselect Algorithm
Sorting in Java
Quick Sort vs Merge Sort
Find whether an array is subset of another array
Stability in sorting algorithms