Start learning free

LESSON 282 OF 410 · DIVIDE AND CONQUER

Quick Sort in Java with O(n log n) Time Complexity

Practice O(n log n) Read the full explanation

In this video lesson we will study how the Quick Sort algorithm works, we will analyze its time and space complexities and then we will implement it:


Problem - Quick Sort:

Given an array of integers nums, sort it in ascending order using Quick Sort

Example 1:

Input: nums = [3, 1, 3, 2, 5, 4]
Output: [1, 2, 3, 3, 4, 5]

Note:

Your algorithm should run in O(n log n) time on average and use O(log n) extra space on average (for the recursion).


STEP 1 OF 18
    Ctrl + Enter to check

    JAVA · DIVIDE AND CONQUER

    Quick Sort in Java, explained

    Understanding the Problem

    The core challenge of the Quick Sort algorithm is to efficiently sort an array of integers in ascending order. Quick Sort is a divide-and-conquer algorithm that works by selecting a 'pivot' element from the array and partitioning the other elements into two sub-arrays, according to whether they are less than or greater than the pivot. The sub-arrays are then sorted recursively.

    Quick Sort is significant due to its average-case time complexity of O(n log n), making it one of the fastest sorting algorithms for large datasets. However, its worst-case time complexity is O(n^2), which can occur if the pivot elements are not chosen wisely.

    Potential pitfalls include choosing a poor pivot, which can lead to unbalanced partitions and degrade performance. Misunderstanding the partitioning process can also lead to incorrect implementations.

    Approach

    To solve the problem, we can follow these steps:

    1. Choose a pivot element from the array.
    2. Partition the array into two sub-arrays: elements less than the pivot and elements greater than the pivot.
    3. Recursively apply the above steps to the sub-arrays.
    4. Combine the sorted sub-arrays and the pivot to get the final sorted array.

    A naive solution might involve selecting the first element as the pivot, but this can lead to poor performance on already sorted arrays. A better approach is to use the median-of-three method or random pivot selection to improve performance.

    Algorithm

    Here is a step-by-step breakdown of the Quick Sort algorithm:

    1. Partitioning: Choose a pivot and rearrange the array so that all elements less than the pivot come before it, and all elements greater come after it.
    2. Recursive Sorting: Recursively apply the partitioning process to the sub-arrays.
    3. Combining: Combine the sorted sub-arrays and the pivot to form the final sorted array.

    Code Implementation

    public class QuickSort {
        // Main function to sort an array using Quick Sort
        public static void quickSort(int[] nums, int low, int high) {
            if (low < high) {
                // Partition the array and get the pivot index
                int pi = partition(nums, low, high);
                
                // Recursively sort the sub-arrays
                quickSort(nums, low, pi - 1);
                quickSort(nums, pi + 1, high);
            }
        }
    
        // Function to partition the array and return the pivot index
        private static int partition(int[] nums, int low, int high) {
            // Choose the pivot element
            int pivot = nums[high];
            int i = (low - 1); // Index of smaller element
    
            for (int j = low; j < high; j++) {
                // If current element is smaller than or equal to pivot
                if (nums[j] <= pivot) {
                    i++;
                    // Swap nums[i] and nums[j]
                    int temp = nums[i];
                    nums[i] = nums[j];
                    nums[j] = temp;
                }
            }
    
            // Swap nums[i + 1] and nums[high] (or pivot)
            int temp = nums[i + 1];
            nums[i + 1] = nums[high];
            nums[high] = temp;
    
            return i + 1;
        }
    
        // Main method to test the Quick Sort algorithm
        public static void main(String[] args) {
            int[] nums = {3, 1, 3, 2, 5, 4};
            quickSort(nums, 0, nums.length - 1);
            System.out.println(Arrays.toString(nums)); // Output: [1, 2, 3, 3, 4, 5]
        }
    }
    

    Complexity Analysis

    The time complexity of Quick Sort is:

    • Best Case: O(n log n) - This occurs when the pivot divides the array into two nearly equal halves.
    • Average Case: O(n log n) - This is the expected time complexity for a random pivot.
    • Worst Case: O(n^2) - This occurs when the pivot is the smallest or largest element, leading to unbalanced partitions.

    The space complexity is O(log n) due to the recursive stack space used during the sorting process.

    Edge Cases

    Potential edge cases include:

    • Empty array: The algorithm should handle this gracefully without errors.
    • Array with one element: The array is already sorted.
    • Array with all identical elements: The algorithm should still function correctly and return the same array.

    To test these edge cases, we can use the following examples:

    int[] emptyArray = {};
    int[] singleElementArray = {1};
    int[] identicalElementsArray = {2, 2, 2, 2, 2};
    

    Testing

    To test the Quick Sort implementation comprehensively, we should use a variety of test cases, including:

    • Small arrays with a few elements.
    • Large arrays with thousands of elements.
    • Arrays with random elements.
    • Arrays with sorted and reverse-sorted elements.

    We can use JUnit or another testing framework to automate these tests and ensure the correctness of our implementation.

    Thinking and Problem-Solving Tips

    When approaching sorting problems like Quick Sort, consider the following tips:

    • Understand the problem requirements and constraints.
    • Break down the problem into smaller, manageable steps.
    • Consider different pivot selection strategies to optimize performance.
    • Practice implementing the algorithm by hand to solidify your understanding.

    To improve problem-solving skills, practice solving similar problems and study different sorting algorithms and their trade-offs.

    Conclusion

    Quick Sort is a powerful and efficient sorting algorithm with an average-case time complexity of O(n log n). Understanding its partitioning process and pivot selection strategies is crucial for optimizing performance. By practicing and implementing Quick Sort, you can enhance your problem-solving skills and gain a deeper understanding of sorting algorithms.

    Additional Resources

    For further reading and practice problems related to Quick Sort, consider the following resources:

    Keep going with JavaAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free