The worst-case time complexity of a typical implementation of QuickSort is O(n2). The worst case occurs when the picked pivot is always an extreme (smallest or largest) element. This happens when the input array is sorted or reverses sorted and either the first or last element is picked as a pivot.
Although randomized QuickSort works well even when the array is sorted, there is still possible that the randomly picked element is always extreme. Can the worst case be reduced to O(nLogn)?
The answer is yes, we can achieve O(nLogn) worst case. The idea is based on the fact that the median element of an unsorted array can be found in linear time. So we find the median first, then partition the array around the median element.
Following is a C++ implementation based on the above idea. Most of the functions in below program are copied from K’th Smallest/Largest Element in Unsorted Array | Set 3 (Worst Case Linear Time)
/* A worst case O(nLogn) implementation of quicksort */ #include <algorithm> #include <climits> #include <cstring> #include <iostream> using namespace std;
// Following functions are taken from http://goo.gl/ih05BF int partition( int arr[], int l, int r, int k);
int kthSmallest( int arr[], int l, int r, int k);
/* A O(nLogn) time complexity function for sorting arr[l..h] */
void quickSort( int arr[], int l, int h)
{ if (l < h) {
// Find size of current subarray
int n = h - l + 1;
// Find median of arr[].
int med = kthSmallest(arr, l, h, n / 2);
// Partition the array around median
int p = partition(arr, l, h, med);
// Recur for left and right of partition
quickSort(arr, l, p - 1);
quickSort(arr, p + 1, h);
}
} // A simple function to find median of arr[]. This is // called only for an array of size 5 in this program. int findMedian( int arr[], int n)
{ sort(arr, arr + n); // Sort the array
return arr[n / 2]; // Return middle element
} // Returns k'th smallest element in arr[l..r] in worst case // linear time. ASSUMPTION: ALL ELEMENTS IN ARR[] ARE // DISTINCT int kthSmallest( int arr[], int l, int r, int k)
{ // If k is smaller than number of elements in array
if (k > 0 && k <= r - l + 1) {
int n
= r - l + 1; // Number of elements in arr[l..r]
// Divide arr[] in groups of size 5, calculate
// median of every group and store it in median[]
// array.
int i,
median[(n + 4) / 5]; // There will be
// floor((n+4)/5) groups;
for (i = 0; i < n / 5; i++)
median[i] = findMedian(arr + l + i * 5, 5);
if (i * 5
< n) // For last group with less than 5 elements
{
median[i] = findMedian(arr + l + i * 5, n % 5);
i++;
}
// Find median of all medians using recursive call.
// If median[] has only one element, then no need
// of recursive call
int medOfMed = (i == 1) ? median[i - 1]
: kthSmallest(median, 0,
i - 1, i / 2);
// Partition the array around a random element and
// get position of pivot element in sorted array
int pos = partition(arr, l, r, medOfMed);
// If position is same as k
if (pos - l == k - 1)
return arr[pos];
if (pos - l
> k - 1) // If position is more, recur for left
return kthSmallest(arr, l, pos - 1, k);
// Else recur for right subarray
return kthSmallest(arr, pos + 1, r,
k - pos + l - 1);
}
// If k is more than number of elements in array
return INT_MAX;
} void swap( int * a, int * b)
{ int temp = *a;
*a = *b;
*b = temp;
} // It searches for x in arr[l..r], and partitions the array // around x. int partition( int arr[], int l, int r, int x)
{ // Search for x in arr[l..r] and move it to end
int i;
for (i = l; i < r; i++)
if (arr[i] == x)
break ;
swap(&arr[i], &arr[r]);
// Standard partition algorithm
i = l;
for ( int j = l; j <= r - 1; j++) {
if (arr[j] <= x) {
swap(&arr[i], &arr[j]);
i++;
}
}
swap(&arr[i], &arr[r]);
return i;
} /* Function to print an array */ void printArray( int arr[], int size)
{ int i;
for (i = 0; i < size; i++)
cout << arr[i] << " " ;
cout << endl;
} // Driver program to test above functions int main()
{ int arr[] = { 1000, 10, 7, 8, 9, 30, 900, 1, 5, 6, 20 };
int n = sizeof (arr) / sizeof (arr[0]);
quickSort(arr, 0, n - 1);
cout << "Sorted array is\n" ;
printArray(arr, n);
return 0;
} |
import java.util.Arrays;
class QuickSort {
// Function to find the median of five elements
static int findMedian( int arr[], int i, int n)
{
Arrays.sort(arr, i, i + n);
return arr[i + n / 2 ];
}
// Function to partition the array around the median of
// medians
static int partition( int arr[], int l, int r, int x)
{
for ( int i = l; i < r; i++)
if (arr[i] == x)
swap(arr, i, r);
int i = l;
for ( int j = l; j <= r - 1 ; j++) {
if (arr[j] <= x) {
swap(arr, i, j);
i++;
}
}
swap(arr, i, r);
return i;
}
// Function to swap two elements in the array
static void swap( int arr[], int a, int b)
{
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
// Function to find the kth smallest element using
// median of medians
static int kthSmallest( int arr[], int l, int r, int k)
{
if (k > 0 && k <= r - l + 1 ) {
int n = r - l + 1 ;
int i, median[] = new int [(n + 4 ) / 5 ];
for (i = 0 ; i < n / 5 ; i++)
median[i] = findMedian(arr, l + i * 5 , 5 );
if (i * 5 < n) {
median[i]
= findMedian(arr, l + i * 5 , n % 5 );
i++;
}
int medOfMed = (i == 1 )
? median[i - 1 ]
: kthSmallest(median, 0 ,
i - 1 , i / 2 );
int pos = partition(arr, l, r, medOfMed);
if (pos - l == k - 1 )
return arr[pos];
if (pos - l > k - 1 )
return kthSmallest(arr, l, pos - 1 , k);
return kthSmallest(arr, pos + 1 , r,
k - pos + l - 1 );
}
return Integer.MAX_VALUE;
}
// Function to perform quicksort on the array
static void quickSort( int arr[], int l, int h)
{
if (l < h) {
int n = h - l + 1 ;
int med = kthSmallest(arr, l, h, n / 2 );
int p = partition(arr, l, h, med);
quickSort(arr, l, p - 1 );
quickSort(arr, p + 1 , h);
}
}
// Function to print an array
static void printArray( int arr[])
{
for ( int i : arr)
System.out.print(i + " " );
System.out.println();
}
// Driver program to test above functions
public static void main(String args[])
{
int arr[]
= { 1000 , 10 , 7 , 8 , 9 , 30 , 900 , 1 , 5 , 6 , 20 };
int n = arr.length;
quickSort(arr, 0 , n - 1 );
System.out.println( "Sorted array is" );
printArray(arr);
}
} |
def partition(arr, l, r, x):
# Search for x in arr[l..r] and move it to end
i = l
for i in range (l, r):
if arr[i] = = x:
break
arr[i], arr[r] = arr[r], arr[i]
# Standard partition algorithm
i = l
for j in range (l, r):
if arr[j] < = x:
arr[i], arr[j] = arr[j], arr[i]
i + = 1
arr[i], arr[r] = arr[r], arr[i]
return i
def findMedian(arr, n):
arr.sort() # Sort the array
return arr[n / / 2 ] # Return middle element
def kthSmallest(arr, l, r, k):
if 0 < k < = r - l + 1 :
n = r - l + 1 # Number of elements in arr[l..r]
# Divide arr[] in groups of size 5, calculate
# median of every group and store it in median[]
median = [ 0 ] * ((n + 4 ) / / 5 )
i = 0
for i in range (n / / 5 ):
median[i] = findMedian(arr[l + i * 5 :l + i * 5 + 5 ], 5 )
if i * 5 < n:
median[i] = findMedian(arr[l + i * 5 :l + i * 5 + n % 5 ], n % 5 )
i + = 1
# Find median of all medians using recursive call.
# If median[] has only one element, then no need
# of recursive call
medOfMed = median[i - 1 ] if i = = 1 else kthSmallest(median, 0 , i - 1 , i / / 2 )
# Partition the array around a random element and
# get the position of the pivot element in the sorted array
pos = partition(arr, l, r, medOfMed)
# If the position is the same as k
if pos - l = = k - 1 :
return arr[pos]
if pos - l > k - 1 : # If the position is more, recur for the left
return kthSmallest(arr, l, pos - 1 , k)
# Else, recur for the right subarray
return kthSmallest(arr, pos + 1 , r, k - pos + l - 1 )
# If k is more than the number of elements in the array
return float ( 'inf' )
def quickSort(arr, l, h):
if l < h:
# Find the size of the current subarray
n = h - l + 1
# Find the median of arr[]
med = kthSmallest(arr, l, h, n / / 2 )
# Partition the array around the median
p = partition(arr, l, h, med)
# Recur for the left and right of the partition
quickSort(arr, l, p - 1 )
quickSort(arr, p + 1 , h)
def printArray(arr):
for i in arr:
print (i),
# Driver program to test above functions arr = [ 1000 , 10 , 7 , 8 , 9 , 30 , 900 , 1 , 5 , 6 , 20 ]
n = len (arr)
quickSort(arr, 0 , n - 1 )
print ( "Sorted array is" )
printArray(arr) |
using System;
class Program {
// Function to calculate sum of absolute differences
static int CalculateSum( int [] arr, int n, int x)
{
int sum = 0;
// Traverse to find sum of absolute differences
for ( int i = 0; i < n; i++) {
sum += Math.Abs(arr[i] - x);
}
return sum;
}
// A O(nLogn) time complexity function for sorting
// arr[l..h]
static void QuickSort( int [] arr, int l, int h)
{
if (l < h) {
// Find size of current subarray
int n = h - l + 1;
// Find median of arr[].
int med = KthSmallest(arr, l, h, n / 2);
// Partition the array around median
int p = Partition(arr, l, h, med);
// Recur for left and right of partition
QuickSort(arr, l, p - 1);
QuickSort(arr, p + 1, h);
}
}
// A simple function to find median of arr[]. This is
// called only for an array of size 5 in this program.
static int FindMedian( int [] arr, int n)
{
Array.Sort(arr); // Sort the array
return arr[n / 2]; // Return middle element
}
// Returns k'th smallest element in arr[l..r] in worst
// case linear time. ASSUMPTION: ALL ELEMENTS IN ARR[]
// ARE DISTINCT
static int KthSmallest( int [] arr, int l, int r, int k)
{
// If k is smaller than the number of elements in
// the array
if (k > 0 && k <= r - l + 1) {
int n = r - l
+ 1; // Number of elements in arr[l..r]
// Divide arr[] into groups of size 5, calculate
// the median of every group and store it in
// median[]
int i, count = 0;
int [] median
= new int [(n + 4)
/ 5]; // There will be
// floor((n+4)/5) groups
for (i = 0; i < n / 5; i++) {
median[i] = FindMedian(arr, 5);
count++;
}
if (i * 5 < n) // For the last group with less
// than 5 elements
{
median[i] = FindMedian(arr, n % 5);
count++;
i++;
}
// Find the median of all medians using a
// recursive call. If median[] has only one
// element, then no need for a recursive call
int medOfMed
= (count == 1)
? median[0]
: KthSmallest(median, 0, count - 1,
count / 2);
// Partition the array around a random element
// and get the position of the pivot element in
// the sorted array
int pos = Partition(arr, l, r, medOfMed);
// If the position is the same as k
if (pos - l == k - 1)
return arr[pos];
if (pos - l
> k - 1) // If the position is more, recur
// for the left subarray
return KthSmallest(arr, l, pos - 1, k);
// Else, recur for the right subarray
return KthSmallest(arr, pos + 1, r,
k - pos + l - 1);
}
// If k is more than the number of elements in the
// array
return int .MaxValue;
}
static void Swap( ref int a, ref int b)
{
int temp = a;
a = b;
b = temp;
}
// It searches for x in arr[l..r] and partitions the
// array around x.
static int Partition( int [] arr, int l, int r, int x)
{
// Search for x in arr[l..r] and move it to the end
int i;
for (i = l; i < r; i++) {
if (arr[i] == x)
break ;
}
Swap( ref arr[i], ref arr[r]);
// Standard partition algorithm
i = l;
for ( int j = l; j <= r - 1; j++) {
if (arr[j] <= x) {
Swap( ref arr[i], ref arr[j]);
i++;
}
}
Swap( ref arr[i], ref arr[r]);
return i;
}
// Function to print an array
static void PrintArray( int [] arr, int size)
{
for ( int i = 0; i < size; i++)
Console.Write(arr[i] + " " );
Console.WriteLine();
}
// Driver program to test the above functions
static void Main()
{
int [] arr
= { 1000, 10, 7, 8, 9, 30, 900, 1, 5, 6, 20 };
int n = arr.Length;
QuickSort(arr, 0, n - 1);
Console.WriteLine( "Sorted array is" );
PrintArray(arr, n);
}
} |
// JavaScript Program for the above approach /* A worst case O(nLogn) implementation of quicksort */ // It searches for x in arr[l..r], and partitions the array // around x. function partition(arr, l, r, k) {
// Search for x in arr[l..r] and move it to end
let i;
for (i = l; i < r; i++) {
if (arr[i] === k) {
break ;
}
}
swap(arr, i, r);
// Standard partition algorithm
i = l;
for (let j = l; j <= r - 1; j++) {
if (arr[j] <= k) {
swap(arr, i, j);
i++;
}
}
swap(arr, i, r);
return i;
} // Returns k'th smallest element in arr[l..r] in worst case // linear time. ASSUMPTION: ALL ELEMENTS IN ARR[] ARE // DISTINCT function kthSmallest(arr, l, r, k) {
// If k is smaller than number of elements in array
if (k > 0 && k <= r - l + 1) {
let n = r - l + 1;
// Divide arr[] in groups of size 5, calculate
// median of every group and store it in median[]
// array.
let i = 0;
// There will be floor((n+4)/5) groups;
let median = new Array(Math.floor((n + 4) / 5));
for (i = 0; i < Math.floor(n / 5); i++) {
median[i] = findMedian(arr, l + i * 5, 5);
}
if (i * 5 < n) {
median[i] = findMedian(arr, l + i * 5, n % 5);
i++;
}
// Find median of all medians using recursive call.
// If median[] has only one element, then no need
// of recursive call
let medOfMed = (i === 1) ? median[i - 1] : kthSmallest(median, 0, i - 1, Math.floor(i / 2));
// Partition the array around a random element and
// get position of pivot element in sorted array
let pos = partition(arr, l, r, medOfMed);
// If position is same as k
if (pos - l === k - 1) {
return arr[pos];
}
if (pos - l > k - 1) {
return kthSmallest(arr, l, pos - 1, k);
}
// Else recur for right subarray
return kthSmallest(arr, pos + 1, r, k - pos + l - 1);
}
// If k is more than number of elements in array
return Infinity;
} // A simple function to find median of arr[]. This is // called only for an array of size 5 in this program. function findMedian(arr, start, size) {
let tempArr = new Array(size);
for (let i = 0; i < size; i++) {
tempArr[i] = arr[start + i];
}
tempArr.sort((a, b) => a - b);
let flr = Math.floor(size / 2);
return tempArr[flr];
} function quickSort(arr, l, h) {
if (l < h) {
// Find size of current subarray
let n = h - l + 1;
// Find median of arr[].
let med = kthSmallest(arr, l, h, Math.floor(n / 2));
// Partition the array around median
let p = partition(arr, l, h, med);
// Recur for left and right of partition
quickSort(arr, l, p - 1);
quickSort(arr, p + 1, h);
}
} function swap(arr, i, j) {
let temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
} // Function to print an array function printArray(arr) {
console.log(arr.join(' '));
} // Driver Code let arr = [1000, 10, 7, 8, 9, 30, 900, 1, 5, 6, 20]; quickSort(arr, 0, arr.length - 1); console.log(' Sorted array is:');
printArray(arr); // This code is contributed by codebraxnzt |
Sorted array is 1 5 6 7 8 9 10 20 30 900 1000
// C++ code for the above approach #include <iostream> using namespace std;
void quickSort( int array[], int left, int right);
int partition( int array[], int left, int right);
void swap( int array[], int i, int j);
void quickSort( int array[], int left, int right) {
if (left < right) {
int pivotIndex = partition(array, left, right);
quickSort(array, left, pivotIndex - 1);
quickSort(array, pivotIndex + 1, right);
}
} // It searches for x in array[left..right], and partitions the array around x. int partition( int array[], int left, int right) {
int pivot = array[right];
int i = left - 1;
for ( int j = left; j < right; j++) {
if (array[j] <= pivot) {
i++;
swap(array, i, j);
}
}
swap(array, i + 1, right);
return i + 1;
} void swap( int array[], int i, int j) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
} // Driver code to check the above written functions int main() {
int array[] = {5, 2, 9, 1, 5, 6};
int n = sizeof (array)/ sizeof (array[0]);
quickSort(array, 0, n-1);
for ( int i = 0; i < n; i++) {
cout << array[i] << " " ;
}
return 0;
} |
/*package whatever //do not write package name here */ import java.io.*;
class GFG {
public static void quickSort( int [] array, int left, int right) {
if (left < right) {
int pivotIndex = partition(array, left, right);
quickSort(array, left, pivotIndex - 1 );
quickSort(array, pivotIndex + 1 , right);
}
}
// It searches for x in array[left..right], and partitions the array around x.
private static int partition( int [] array, int left, int right) {
int pivot = array[right];
int i = left - 1 ;
for ( int j = left; j < right; j++) {
if (array[j] <= pivot) {
i++;
swap(array, i, j);
}
}
swap(array, i + 1 , right);
return i + 1 ;
}
private static void swap( int [] array, int i, int j) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
// Driver code to check the above written functions
public static void main(String[] args) {
int [] array = { 5 , 2 , 9 , 1 , 5 , 6 };
quickSort(array, 0 , array.length - 1 );
for ( int i : array) {
System.out.print(i + " " );
}
}
} |
def quickSort(array, left, right):
# Recursive function to sort the array using quicksort algorithm
if left < right:
# Partition the array and get the pivot index
pivotIndex = partition(array, left, right)
# Sort elements to the left of pivot index
quickSort(array, left, pivotIndex - 1 )
# Sort elements to the right of pivot index
quickSort(array, pivotIndex + 1 , right)
# It searches for x in array[left..right], and partitions the array around x. def partition(array, left, right):
# Select the rightmost element as pivot
pivot = array[right]
# Initialize i as left-1
i = left - 1
# Traverse the array from left to right
for j in range (left, right):
if array[j] < = pivot:
# Increment i and swap the current element with i
i + = 1
array[i], array[j] = array[j], array[i]
# Swap the pivot with the element at i+1
array[i + 1 ], array[right] = array[right], array[i + 1 ]
# Return the pivot index
return i + 1
# Driver code to check the above written functions if __name__ = = '__main__' :
# Initialize an array to be sorted
array = [ 5 , 2 , 9 , 1 , 5 , 6 ]
# Sort the array using quicksort algorithm
quickSort(array, 0 , len (array) - 1 )
# Print the sorted array
for i in array:
print (i, end = ' ' )
|
// C# program for the above approach using System;
class GFG {
public static void QuickSort( int [] array, int left, int right) {
if (left < right) {
int pivotIndex = Partition(array, left, right);
QuickSort(array, left, pivotIndex - 1);
QuickSort(array, pivotIndex + 1, right);
}
}
// It searches for x in array[left..right], and partitions the array around x.
private static int Partition( int [] array, int left, int right) {
int pivot = array[right];
int i = left - 1;
for ( int j = left; j < right; j++) {
if (array[j] <= pivot) {
i++;
Swap(array, i, j);
}
}
Swap(array, i + 1, right);
return i + 1;
}
private static void Swap( int [] array, int i, int j) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
// Driver code to check the above written functions
static void Main( string [] args) {
int [] array = { 5, 2, 9, 1, 5, 6 };
QuickSort(array, 0, array.Length - 1);
foreach ( int i in array) {
Console.Write(i + " " );
}
}
} // This code is contributed by rishabmalhdijo |
// Javascript code for the above approach function quickSort(array, left, right) {
// Recursive function to sort the array using quicksort algorithm
if (left < right) {
// Partition the array and get the pivot index
let pivotIndex = partition(array, left, right);
// Sort elements to the left of pivot index
quickSort(array, left, pivotIndex - 1);
// Sort elements to the right of pivot index
quickSort(array, pivotIndex + 1, right);
}
} // It searches for x in array[left..right], and partitions the array around x. function partition(array, left, right) {
// Select the rightmost element as pivot
let pivot = array[right];
// Initialize i as left-1
let i = left - 1;
// Traverse the array from left to right
for (let j = left; j < right; j++) {
if (array[j] <= pivot) {
// Increment i and swap the current element with i
i++;
[array[i], array[j]] = [array[j], array[i]];
}
}
// Swap the pivot with the element at i+1
[array[i + 1], array[right]] = [array[right], array[i + 1]];
// Return the pivot index
return i + 1;
} // Driver code to check the above written functions let array = [5, 2, 9, 1, 5, 6]; // Sort the array using quicksort algorithm quickSort(array, 0, array.length - 1); // Print the sorted array console.log(array.join( ' ' ));
// This code is contributed by adityashatmfh |
Output
1 2 5 5 6 9
Auxiliary Space: O(n*logn) where n is size of array.
How is QuickSort implemented in practice – is above approach used?
Although worst case time complexity of the above approach is O(nLogn), it is never used in practical implementations. The hidden constants in this approach are high compared to normal Quicksort. Following are some techniques used in practical implementations of QuickSort.
1) Randomly picking up to make worst case less likely to occur (Randomized QuickSort)
2) Calling insertion sort for small sized arrays to reduce recursive calls.
3) QuickSort is tail recursive, so tail call optimizations is done.
So the approach discussed above is more of a theoretical approach with O(nLogn) worst case time complexity.
This article is compiled by Shivam.