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++
#include <iostream>
int partition( int arr[], int low, int high) {
int pivot = arr[high];
int i = low - 1;
for ( int j = low; j < high; j++) {
if (arr[j] <= pivot) {
i++;
std::swap(arr[i], arr[j]);
}
}
std::swap(arr[i + 1], arr[high]);
return i + 1;
}
void quickSort( int arr[], int low, int high) {
if (low < high) {
int pi = partition(arr, low, high);
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
int main() {
int arr[] = {12, 11, 13, 5, 6, 7};
int n = sizeof (arr) / sizeof (arr[0]);
std::cout << "Original array: " ;
for ( int i = 0; i < n; i++) {
std::cout << arr[i] << " " ;
}
std::cout << std::endl;
quickSort(arr, 0, n - 1);
std::cout << "Sorted array: " ;
for ( int i = 0; i < n; i++) {
std::cout << arr[i] << " " ;
}
std::cout << std::endl;
return 0;
}
|
C
void quickSort( int arr[], int low, int high)
{
if (low < high)
{
int pi = partition(arr, low, high);
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
|
Java
static void quickSort( int arr[], int low, int high)
{
if (low < high)
{
int pi = partition(arr, low, high);
quickSort(arr, low, pi - 1 );
quickSort(arr, pi + 1 , high);
}
}
|
Python3
def quickSort(arr, low, high):
if (low < high):
pi = partition(arr, low, high)
quickSort(arr, low, pi - 1 )
quickSort(arr, pi + 1 , high)
|
C#
static void quickSort( int []arr, int low, int high)
{
if (low < high)
{
int pi = partition(arr, low, high);
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
|
Javascript
<script>
function quickSort(arr , low , high)
{
if (low < high)
{
var pi = partition(arr, low, high);
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
</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++
#include <iostream>
using namespace std;
void swap( int * a, int * b)
{
int t = *a;
*a = *b;
*b = t;
}
int partition( int arr[], int low, int high)
{
int pivot = arr[high];
int i = (low - 1);
for ( int j = low; j <= high- 1; j++)
{
if (arr[j] <= pivot)
{
i++;
swap(&arr[i], &arr[j]);
}
}
swap(&arr[i + 1], &arr[high]);
return (i + 1);
}
void quickSort( int arr[], int low, int high)
{
if (low < high)
{
int pi = partition(arr, low, high);
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
void printArray( int arr[], int size)
{
for ( int i = 0; i < size; i++)
cout << arr[i] << " " ;
cout << endl;
}
int main()
{
int arr[] = {10, 7, 8, 9, 1, 5};
int n = sizeof (arr)/ sizeof (arr[0]);
quickSort(arr, 0, n - 1);
cout << "Sorted array: \n" ;
printArray(arr, n);
return 0;
}
|
C
void quickSort( int arr[], int low, int high)
{
while (low < high)
{
int pi = partition(arr, low, high);
quickSort(arr, low, pi - 1);
low = pi+1;
}
}
|
Java
static void quickSort( int arr[], int low, int high)
{
while (low < high)
{
int pi = partition(arr, low, high);
quickSort(arr, low, pi - 1 );
low = pi+ 1 ;
}
}
|
Python3
def quickSort(arr, low, high):
while (low < high):
pi = partition(arr, low, high)
quickSort(arr, low, pi - 1 )
low = pi + 1
|
C#
static void quickSort( int []arr, int low, int high)
{
while (low < high)
{
int pi = partition(arr, low, high);
quickSort(arr, low, pi - 1);
low = pi+1;
}
}
|
Javascript
<script>
function quickSort(arr , low , high)
{
while (low < high)
{
var pi = partition(arr, low, high);
quickSort(arr, low, pi - 1);
low = pi+1;
}
}
</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++
#include <bits/stdc++.h>
using namespace std;
void quickSort( int arr[], int low, int high)
{
while (low < high)
{
int pi = partition(arr, low, high);
if (pi - low < high - pi)
{
quickSort(arr, low, pi - 1);
low = pi + 1;
}
else
{
quickSort(arr, pi + 1, high);
high = pi - 1;
}
}
}
|
C
void quickSort( int arr[], int low, int high)
{
while (low < high)
{
int pi = partition(arr, low, high);
if (pi - low < high - pi)
{
quickSort(arr, low, pi - 1);
low = pi + 1;
}
else
{
quickSort(arr, pi + 1, high);
high = pi - 1;
}
}
}
|
Java
static void quickSort( int arr[], int low, int high)
{
while (low < high)
{
int pi = partition(arr, low, high);
if (pi - low < high - pi)
{
quickSort(arr, low, pi - 1 );
low = pi + 1 ;
}
else
{
quickSort(arr, pi + 1 , high);
high = pi - 1 ;
}
}
}
|
Python3
def quickSort(arr, low, high)
{
while (low < high):
pi = partition(arr, low, high);
if (pi - low < high - pi):
quickSort(arr, low, pi - 1 );
low = pi + 1 ;
else :
quickSort(arr, pi + 1 , high);
high = pi - 1 ;
|
C#
static void quickSort( int []arr, int low, int high)
{
while (low < high)
{
int pi = partition(arr, low, high);
if (pi - low < high - pi)
{
quickSort(arr, low, pi - 1);
low = pi + 1;
}
else
{
quickSort(arr, pi + 1, high);
high = pi - 1;
}
}
}
|
Javascript
<script>
function quickSort(arr , low , high)
{
while (low < high)
{
var pi = partition(arr, low, high);
if (pi - low < high - pi)
{
quickSort(arr, low, pi - 1);
low = pi + 1;
}
else
{
quickSort(arr, pi + 1, high);
high = pi - 1;
}
}
}
</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
Feeling lost in the world of random DSA topics, wasting time without progress? It's time for a change! Join our DSA course, where we'll guide you on an exciting journey to master DSA efficiently and on schedule.
Ready to dive in? Explore our Free Demo Content and join our DSA course, trusted by over 100,000 geeks!
Last Updated :
28 Oct, 2023
Like Article
Save Article