Open In App

Stable QuickSort

Improve
Improve
Like Article
Like
Save
Share
Report

A sorting algorithm is said to be stable if it maintains the relative order of records in the case of equality of keys.

Input : (1, 5), (3, 2) (1, 2) (5, 4) (6, 4) 
We need to sort key-value pairs in the increasing order of keys of first digit 

There are two possible solution for the two pairs where the key is same i.e. (1, 5) and (1, 2) as shown below: 
OUTPUT1: (1, 5), (1, 2), (3, 2), (5, 4), (6, 4) 
OUTPUT2: (1, 2), (1, 5), (3, 2), (5, 4), (6, 4) 

A stable algorithm produces first output. You can see that (1, 5) comes before (1, 2) in the sorted order, which was the original order i.e. in the given input, (1, 5) comes before (1, 2).
Second output can only be produced by an unstable algorithm. You can see that in the second output, the (1, 2) comes before (1, 5) which was not the case in the original input.

Some sorting algorithms are stable by nature like Insertion sort, Merge Sort, Bubble Sort, etc. And some sorting algorithms are not, like Heap Sort, Quick Sort, etc. 
QuickSort is an unstable algorithm because we do swapping of elements according to pivot’s position (without considering their original positions).

How to make QuickSort stable?

Quicksort can be stable but it typically isn’t implemented that way. Making it stable either requires order N storage (as in a naive implementation) or a bit of extra logic for an in-place version. 
In below implementation, we use extra space. 

The idea is to make two separate lists: 

  1. First list contains items smaller than pivot. 
  2. Second list contains items greater than pivot.

Implementation:

C++




// C++ code to implement Stable QuickSort.
// The code uses middle element as pivot.
#include <bits/stdc++.h>
using namespace std;
 
    vector<int> quickSort(vector<int> ar)
    {
     
        // Base case
        if(ar.size() <= 1)
        {
            return ar;
        }
        else
        {
         
            // Let us choose middle element a pivot           
            int mid = ar.size() / 2;
            int pivat = ar[mid];
             
        // key element is used to break the array
            // into 2 halves according to their values
            vector<int> smaller ;
            vector<int> greater ;
             
            // Put greater elements in greater list,
        // smaller elements in smaller list. Also,
        // compare positions to decide where to put.       
        for(int ind = 0; ind < ar.size(); ind++)
        {
            int val = ar[ind];
            if( ind != mid )
            {
                if( val < pivat )
                {
                    smaller.push_back(val);
                }
                else if(val > pivat)
                {
                    greater.push_back(val);
                }
                else
                {
                     
                    // If value is same, then considering
                    // position to decide the list.               
                    if(ind < mid)
                    {
                        smaller.push_back(val);
                    }
                    else
                    {
                        greater.push_back(val);
                    }
                }
            }
        }
             
        vector<int> ans;           
        vector<int> sa1 = quickSort(smaller);
        vector<int> sa2 = quickSort(greater);
             
        // add all elements of smaller list into ans list
        for(int val1 : sa1)
                ans.push_back(val1);
                 
        // add pivat element into ans list
        ans.push_back(pivat);
             
        // add all elements of greater list into ans list
        for(int val2 : sa2)
                ans.push_back(val2);
             
        // return ans list
        return ans;       
        }
    }
     
    // Driver code
    int main()
    {   
        int ar[] = {10, 7, 8, 9, 1, 5};
        vector<int> al;
        al.push_back(10);
        al.push_back(7);
        al.push_back(8);
        al.push_back(9);
        al.push_back(1);
        al.push_back(5);   
        vector<int> sortedAr = quickSort(al);
         
        for(int x:sortedAr)
        cout<<x<<" ";
    }
 
// This code is contributed by Pushpesh Raj


Java




// Java code to implement Stable QuickSort.
// The code uses middle element as pivot.
import java.util.*;
class GFG
{
    public static ArrayList<Integer> quickSort(ArrayList<Integer> ar)
    {
       
        // Base case
        if(ar.size() <= 1)
        {
            return ar;
        }
        else
        {
           
            // Let us choose middle element a pivot            
            int mid = ar.size() / 2;
            int pivat = ar.get(mid);
             
           // key element is used to break the array
            // into 2 halves according to their values
            ArrayList<Integer> smaller = new ArrayList<>();
            ArrayList<Integer> greater = new ArrayList<>();
             
            // Put greater elements in greater list,
           // smaller elements in smaller list. Also,
           // compare positions to decide where to put.        
           for(int ind = 0; ind < ar.size(); ind++)
           {
               int val = ar.get(ind);
               if( ind != mid )
               {
                   if( val < pivat )
                   {
                       smaller.add(val);
                   }
                   else if(val > pivat)
                   {
                       greater.add(val);
                   }
                   else
                   {
                      
                       // If value is same, then considering
                       // position to decide the list.                   
                       if(ind < mid)
                       {
                           smaller.add(val);
                       }
                       else
                       {
                           greater.add(val);
                       }
                   }
               }
           }
            
           ArrayList<Integer> ans = new ArrayList<Integer>();              
           ArrayList<Integer> sa1 = quickSort(smaller);
           ArrayList<Integer> sa2 = quickSort(greater);
            
           // add all elements of smaller list into ans list
           for(Integer val1 : sa1)
                ans.add(val1);
                 
           // add pivat element into ans list   
           ans.add(pivat);
            
           // add all elements of greater list into ans list
           for(Integer val2 : sa2)
                ans.add(val2);
            
           // return ans list
           return ans;        
        }
    }
     
    // Driver code 
    public static void main(String args[])
    {      
        int ar[] = {10, 7, 8, 9, 1, 5};
        ArrayList<Integer> al = new ArrayList<>();
        al.add(10);
        al.add(7);
        al.add(8);
        al.add(9);
        al.add(1);
        al.add(5);       
        ArrayList<Integer> sortedAr = quickSort(al);
        System.out.println(sortedAr);
    }
}
 
// This code is contributed by Naresh Saharan


Python3




# Python code to implement Stable QuickSort.
# The code uses middle element as pivot.
def quickSort(ar):
      
    # Base case
    if len(ar) <= 1:
        return ar
 
    # Let us choose middle element a pivot
    else:
        mid = len(ar)//2
        pivot = ar[mid]
 
        # key element is used to break the array
        # into 2 halves according to their values
        smaller,greater = [],[]
  
        # Put greater elements in greater list,
        # smaller elements in smaller list. Also,
        # compare positions to decide where to put.
        for indx, val in enumerate(ar):
            if indx != mid:
                if val < pivot:
                    smaller.append(val)
                elif val > pivot:
                    greater.append(val)
 
                # If value is same, then considering
                # position to decide the list.
                else:
                    if indx < mid:
                        smaller.append(val)
                    else:
                        greater.append(val)
        return quickSort(smaller)+[pivot]+quickSort(greater)
     
# Driver code to test above
ar = [10, 7, 8, 9, 1, 5]
sortedAr = quickSort(ar)
print(sortedAr)


C#




// Include namespace system
using System;
using System.Collections.Generic;
 
public class GFG
{
    public static List<int> quickSort(List<int> ar)
    {
        // Base case
        if (ar.Count <= 1)
        {
            return ar;
        }
        else
        {
           
            // Let us choose middle element a pivot            
            var mid = (int)(ar.Count / 2);
            var pivat = ar[mid];
           
            // key element is used to break the array
            // into 2 halves according to their values
            var smaller = new List<int>();
            var greater = new List<int>();
           
            // Put greater elements in greater list,
            // smaller elements in smaller list. Also,
            // compare positions to decide where to put.
            for (int ind = 0; ind < ar.Count; ind++)
            {
                var val = ar[ind];
                if (ind != mid)
                {
                    if (val < pivat)
                    {
                        smaller.Add(val);
                    }
                    else if (val > pivat)
                    {
                        greater.Add(val);
                    }
                    else
                    {
                        // If value is same, then considering
                        // position to decide the list.
                        if (ind < mid)
                        {
                            smaller.Add(val);
                        }
                        else
                        {
                            greater.Add(val);
                        }
                    }
                }
            }
            var ans = new List<int>();
            var sa1 = GFG.quickSort(smaller);
            var sa2 = GFG.quickSort(greater);
           
            // add all elements of smaller list into ans list
            foreach (int val1 in sa1)
            {                ans.Add(val1);
            }
           
            // add pivat element into ans list   
            ans.Add(pivat);
           
            // add all elements of greater list into ans list
            foreach (int val2 in sa2)
            {                ans.Add(val2);
            }
           
            // return ans list
            return ans;
        }
    }
   
    // Driver code
    public static void Main(String[] args)
    {
        int[] ar = {10, 7, 8, 9, 1, 5};
        var al = new List<int>();
        al.Add(10);
        al.Add(7);
        al.Add(8);
        al.Add(9);
        al.Add(1);
        al.Add(5);
        var sortedAr = GFG.quickSort(al);
        Console.WriteLine(string.Join(", ",sortedAr));
    }
}
 
// This code is contributed by aadityaburujwale.


Javascript




function quickSort(ar)
    {
     
        // Base case
        if (ar.length <= 1)
        {
            return ar;
        }
        else
        {
            // Let us choose middle element a pivot            
            var mid = parseInt(ar.length / 2);
            var pivat = ar[mid];
             
            // key element is used to break the array
            // into 2 halves according to their values
            var smaller = new Array();
            var greater = new Array();
            var ind = 0;
             
            // Put greater elements in greater list,
            // smaller elements in smaller list. Also,
            // compare positions to decide where to put.
            for (ind; ind < ar.length; ind++)
            {
                var val = ar[ind];
                if (ind != mid)
                {
                    if (val < pivat)
                    {
                        (smaller.push(val) > 0);
                    }
                    else if (val > pivat)
                    {
                        (greater.push(val) > 0);
                    }
                    else
                    {
                        // If value is same, then considering
                        // position to decide the list.
                        if (ind < mid)
                        {
                            (smaller.push(val) > 0);
                        }
                        else
                        {
                            (greater.push(val) > 0);
                        }
                    }
                }
            }
            var ans = new Array();
            var sa1 = quickSort(smaller);
            var sa2 = quickSort(greater);
             
            // add all elements of smaller list into ans list
            for ( const  val1 of sa1) {(ans.push(val1) > 0);}
            // add pivat element into ans list   
            (ans.push(pivat) > 0);
             
            // add all elements of greater list into ans list
            for ( const  val2 of sa2) {(ans.push(val2) > 0);}
             
            // return ans list
            return ans;
        }
    }
     
        var ar = [10, 7, 8, 9, 1, 5];
        var al = new Array();
        (al.push(10) > 0);
        (al.push(7) > 0);
        (al.push(8) > 0);
        (al.push(9) > 0);
        (al.push(1) > 0);
        (al.push(5) > 0);
        var sortedAr = quickSort(al);
        console.log(sortedAr);
         
// This code is contributed by saurabhdalal0001


Output

[1, 5, 7, 8, 9, 10]

In above code, we have intentionally used middle element as a pivot to demonstrate how to consider a position as part of the comparison. Code simplifies a lot if we use last element as pivot. In the case of last element, we can always push equal elements in the smaller list.

Space Complexity:

Considering the best and average cases where the actual array will be partitioned into two sub arrays of equal sizes, i.e. the arrays smaller and greater will be of equal sizes, the recursive stack will take upto O(log n) space just like the unstable implementation of quick sort. log n will be the height of the recursion tree, at any time the maximum number of calls on the recursion stack can be O(log n). At each level of the recursion tree, extra space of “n” will be required (why? This can be easily found out by constructing the recursion tree and keeping in mind the extra space for the arrays smaller and greater). So, the total space required will be = Space used up by recursion stack + Space used up the extra arrays = O(log n) + n*O(log n) = O(nlogn). Please note that in the best and average cases, the common unstable implementation of quick sort only takes up O(log n) for the recursion stack.
In the worst case, the original array gets partitioned into two subarrays of sizes n-1 and 1. In such a case, the height of the recursion tree is “n” and at each level of recursion there will be extra space taken up by the arrays. It can be easily calculated that the worst-case space complexity comes out to be O(n2). Note that the worst-case space complexity for the common un-stable implementation is O(n).



Last Updated : 28 Dec, 2022
Like Article
Save Article
Previous
Next
Share your thoughts in the comments
Similar Reads