Home ProgrammingJava QuickSort In Java: Algorithm, Implementation, Examples

# QuickSort In Java: Algorithm, Implementation, Examples

## The Ultimate Guide to QuickSort in Java: From Basics to Advanced Optimization

QuickSort is a popular and efficient sorting algorithm that is widely used in Java to sort an array of elements in ascending or descending order. In this article, we will explore the QuickSort algorithm in detail, including how it works, how to implement it in Java, and its advantages and disadvantages. We will also discuss different ways to choose the pivot element and compare the different methods of implementing QuickSort in Java. Whether you are a beginner learning about sorting algorithms or an experienced developer looking to improve your skills, this article will provide valuable insights and information about QuickSort in Java.

## What is QuickSort

QuickSort is a sorting algorithm that uses the divide-and-conquer approach to sort an array of elements in ascending or descending order. It is a fast and efficient algorithm, with a time complexity of O(n log n) in the average and worst case scenarios. QuickSort is a popular choice for sorting large datasets, as it can handle large volumes of data efficiently.

This algorithm  works by selecting a pivot element and partitioning the array into two subarrays, one with elements less than the pivot and the other with elements greater than the pivot. The QuickSort algorithm is then applied recursively to the subarrays until they are sorted. In Java, QuickSort can be implemented using the recursive or iterative method.

QuickSort Algorithm

Let’s take a closer look at the quicksort algorithm, as well as the choice of the optimal pivot.

### How QuickSort Works

QuickSort works by selecting a pivot element and partitioning the array into two subarrays, one with elements less than the pivot and the other with elements greater than the pivot. The QuickSort algorithm is then applied recursively to the subarrays until they are sorted.

For example, consider the following unsorted array:

[5, 3, 8, 2, 9, 1]

We can select the pivot element as the last element of the array, i.e. 1. The array is then partitioned into two subarrays:

• Subarray 1: [3, 2, 1] (elements less than the pivot)
• Subarray 2: [5, 8, 9] (elements greater than the pivot)

The QuickSort algorithm is then applied recursively to the subarrays until they are sorted. The final sorted array would be:

[1, 2, 3, 5, 8, 9]

Thus, we have come to a step-by-step algorithm for quick sorting.

### Step by step QuickSort Algorithm

1. Select a pivot element from the array.
2. Partition the array into two subarrays, one with elements less than the pivot and the other with elements greater than the pivot.
3. Recursively apply the QuickSort algorithm to the subarrays until they are sorted.
4. Return the sorted array.

How the quicksort algorithm works

### Choosing the optimal pivot

The choice of pivot element is an important factor in the efficiency of the QuickSort algorithm. A good pivot is one that is close to the median value of the array, as it will result in the two subarrays having a similar size and reducing the time complexity. There are several ways to choose the pivot, such as:

• Choosing the first element of the array as the pivot.
• Choosing the last element of the array as the pivot.
• Choosing a random element of the array as the pivot.
• Choosing the median value of the array as the pivot.

## Implementing QuickSort in Java

There are several ways to implement the QuickSort algorithm in Java. Consider Recursive and Iterative Methods.

## Recursive QuickSort in Java

```public class QuickSort {
public static void quickSort(int[] arr, int low, int high) {
if (low < high) {
int partitionIndex = partition(arr, low, high);
quickSort(arr, low, partitionIndex-1);
quickSort(arr, partitionIndex+1, high);
}
}
public static 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++;
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
int temp = arr[i+1];
arr[i+1] = arr[high];
arr[high] = temp;
return i+1;
}
public static void main(String[] args) {
int[] arr = {5, 3, 2, 8, 9, 1};
quickSort(arr, 0, arr.length-1);
for (int i : arr) {
System.out.print(i + " ");
}
}
}```

Output: 1 2 3 5 8 9

In this code, the `quickSort` method is the recursive function that sorts the array using the divide-and-conquer approach. The `partition` method is used to partition the array into two subarrays, one with elements less than the pivot and the other with elements greater than the pivot. The `quickSort` method is called recursively on the subarrays until they are sorted. The final sorted array is printed to the console.

This code first initializes an array of integers with the following elements: [5, 3, 2, 8, 9, 1]. The `quickSort` method is then called with the array and the indices of the first and last elements of the array as arguments. The `partition` method is called to partition the array into two subarrays based on the pivot element, which is the last element of the array in this case. The `quickSort` method is then called recursively on the two subarrays until they are sorted. The final sorted array is printed to the console as: 1 2 3 5 8 9.

## Iterative QuickSort in Java

Iterative QuickSort is a variation of the QuickSort algorithm that uses an iterative approach instead of a recursive one to sort an array of elements. It is a fast and efficient algorithm, with a time complexity of O(n log n) in the average and worst case scenarios.

Here is an example of Iterative QuickSort in Java:

```public class QuickSort {
public static void quickSort(int[] arr, int low, int high) {
Stack<Integer> stack = new Stack<>();
stack.push(low);
stack.push(high);
while (!stack.isEmpty()) {
int h = stack.pop();
int l = stack.pop();
if (h <= l) {
continue;
}
int p = partition(arr, l, h);
stack.push(l);
stack.push(p-1);
stack.push(p+1);
stack.push(h);
}
}
public static 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++;
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
int temp = arr[i+1];
arr[i+1] = arr[high];
arr[high] = temp;
return i+1;
}
public static void main(String[] args) {
int[] arr = {5, 3, 2, 8, 9, 1};
quickSort(arr, 0, arr.length-1);
for (int i : arr) {
System.out.print(i + " ");
}
}
}```

Output: 1 2 3 5 8 9

In the above code, we have defined the `quickSort` and `partition` methods to implement the Iterative QuickSort algorithm. The `quickSort` method takes an array, a low index, and a high index as input and sorts the array using the iterative approach. The `partition` method selects a pivot element and partitions the array into two subarrays, one with elements less than the pivot and the other with elements greater than the pivot. The `quickSort` method is then applied recursively to the subarrays until they are sorted.

To test the Iterative QuickSort algorithm, we have initialized an array `arr` with the following unsorted elements:

[5, 3, 2, 8, 9, 1]

We have then called the `quickSort` method on the array, passing in the low and high indices as arguments. The sorted array is then printed to the console.

The output of the program is:

1 2 3 5 8 9

As you can see, the Iterative QuickSort algorithm has correctly sorted the array in ascending order.

There are several advantages to using the Iterative QuickSort algorithm over the recursive version. One of the main advantages is that it requires less stack space and fewer function calls, making it more efficient and less prone to stack overflow errors. It is also easier to understand and implement, as it uses a simple loop structure instead of recursive function calls.

However, there are also some disadvantages to using the Iterative QuickSort algorithm. One of the main disadvantages is that it requires more memory to store the stack data structure, which may not be practical for very large datasets. It is also not a stable sorting algorithm, meaning that it does not preserve the relative order of elements with equal values.

## Quicksort complexity

Quicksort has a time complexity of O(n log n) in the average and worst case scenarios, making it suitable for sorting large datasets. However, Quicksort also has a space complexity of O(log n) in the worst case, which can be a limiting factor in some scenarios. In this section, we will explore the complexities of Quicksort in more detail, including its time complexity, space complexity, and other factors that can impact its performance.

## Time complexity

The time complexity of Quicksort is O(n log n) in the average and worst case scenarios, which means that it scales linearly with the size of the input data. This makes Quicksort one of the most efficient sorting algorithms available, as it can sort large datasets in a relatively short amount of time.

The time complexity of Quicksort is based on the number of comparisons and swaps that it needs to perform to sort the array. In the average case, Quicksort performs approximately n log n comparisons to sort an array of n elements, which is why it has a time complexity of O(n log n).

In the worst case, Quicksort may need to perform n^2 comparisons to sort an array, which is the same as bubble sort and insertion sort. However, the probability of this happening is very low, as it depends on the pivot selection strategy used by Quicksort.

## Space complexity

The space complexity of Quicksort is O(log n) in the worst case, which means that it requires additional space proportional to the logarithm of the size of the input data. This space is used to store the recursive function calls and the pivot elements during the sorting process.

Quicksort is an in-place sorting algorithm, which means that it does not require any additional space to sort the array. However, in the worst case, Quicksort may require additional space proportional to the logarithm of the size of the array to store the recursive function calls and pivot elements. This can be a limiting factor in some scenarios, such as when sorting large datasets with limited memory.

## Other factors

There are several other factors that can impact the performance of Quicksort, including the pivot selection strategy, the initial order of the elements, and the presence of duplicate values.

The pivot selection strategy is a key factor in the performance of Quicksort, as it determines how the array is divided into subarrays during the sorting process. A good pivot selection strategy can reduce the time complexity of Quicksort by ensuring that the subarrays are as balanced as possible. On the other hand, a poor pivot selection strategy can increase the time complexity of Quicksort to n^2 in the worst case.

The initial order of the elements can also impact the performance of Quicksort. If the elements are already sorted or nearly sorted, Quicksort may require more comparisons and swaps to sort the array, as it is not an adaptive sorting algorithm. On the other hand, if the elements are randomly ordered, Quicksort is more efficient, as it can divide the array into more balanced subarrays.

The presence of duplicate values can also impact the performance of Quicksort, as it may require more comparisons to sort the array if there are many duplicate values. However, the impact of duplicate values on the performance of Quicksort is generally low, as it has a time complexity of O(n log n) in the average case, regardless of the presence of duplicate values.

## Pros and cons of QuickSort

Pros:

• QuickSort is a fast and efficient algorithm, with a time complexity of O(n log n) in the average and worst case scenarios.
• QuickSort is a popular choice for sorting large datasets, as it can handle large volumes of data efficiently.
• QuickSort is easy to implement and understand.

Cons:

• QuickSort is not a stable sorting algorithm, meaning that it does not preserve the relative order of elements with equal values.
• In the worst case scenario, when the pivot is the smallest or largest element of the array, QuickSort has a time complexity of O(n^2).
• QuickSort is not suitable for sorting small arrays or arrays with a few unique elements, as its overhead may be greater than the time it takes to sort the array.

## QuickSort and MergeSort

QuickSort and MergeSort are both popular and efficient sorting algorithms that are widely used in computer science. Both algorithms have a time complexity of O(n log n) in the average and worst case scenarios, making them suitable for sorting large datasets. However, they have some differences that make them more suitable for different types of data and scenarios.

One of the main differences between QuickSort and MergeSort is their stability. QuickSort is not a stable sorting algorithm, meaning that it does not preserve the relative order of elements with equal values. MergeSort, on the other hand, is a stable sorting algorithm, meaning that it preserves the relative order of elements with equal values. This makes MergeSort a good choice for sorting data with many duplicate values, while QuickSort is more suitable for sorting data with few unique values.

Another difference between QuickSort and MergeSort is their in-place property. QuickSort is an in-place sorting algorithm, meaning that it does not require any additional space to sort the array. MergeSort, on the other hand, is not an in-place sorting algorithm, as it requires additional space to merge the sorted subarrays. This makes QuickSort more efficient in terms of space complexity, but it may be slower on partially sorted arrays, as it requires more swaps to rearrange the elements. MergeSort, on the other hand, is not adaptive and requires the same amount of time to sort any array, but it requires more memory to store the subarrays.

Here is a comparison of QuickSort and MergeSort, collected in one table:

Comparison options QuickSort MergeSort
Time complexity O(n log n) in the average and worst case scenarios O(n log n) in all cases
Space complexity O(log n) for the iterative version, O(n) for the recursive version  O(n)
Stability Not stable Stable
In-place Yes No