Introduction
Sorting is a method of arranging items in a list in a specific order (numerical or alphabetic). Sorting and searching are frequently combined. Many sorting algorithms have been developed throughout the years, with Quicksort being one of the most efficient.
Quicksort uses the divide-and-overcome strategy to create the desired list of elements. This indicates that the algorithm breaks down the problem into smaller subproblems until it can be solved quickly.
The QuickSort algorithm is one of the most extensively used and popular algorithms in any programming language. If you're a JavaScript developer, you're probably already familiar with sort(), which is included in the language. Quicksort can be done in one of two ways: iteratively or recursively. However, in this case, the recursive approach is more natural.
Quicksort's good judgment is categorized into three categories:
Pick an element from the array. This element is referred to as the pivot. This element is almost always the first or last element in the array.
Rearrange the array's components so that every factor to the left of the pivot is less than the pivot and every element to the right of the pivot is larger. This technique is known as partitioning. If an element is miles equal to the pivot, it doesn't matter which side it's on.
Repeat steps 1–3 for the pivot's left and right sides, one at a time, until the array is complete.
Quicksort, how does it work?
- First, locate the "pivot" element in the array first.
- Begin with the first element of the array with the left pointer.
- Initialize the right pointer at the array's last element.
- Move the left pointer to the right if the element pointing with the left pointer is less than the pivot element (add 1 to the left index). Continue until the left side element equals or exceeds the pivot element.
- If the pivot detail is larger than the detail pointed with the correct pointer, the right pointer must be adjusted to the left (subtract 1 to the right index). Keep going until the right side detail is the same as, or less than, the pivot detail.
- Swap the elements in those suggestions' positions if the left pointer is less than or equal to the right pointer.
- The left pointer should be accelerated while the right pointer should be slowed down.
- If the index of the left pointer is still smaller than the index of the right pointer, repeat the operation; otherwise, return the index of the left pointer.
Quicksort implementation in JavaScript
As we'll see, the partitioning section is the backbone of this collection of rules. This section is the same whether we employ a recursive or iterative strategy.
Let's write the following code to partition() an array with that in mind:
function partition(arr, start, end){
// Taking the last element as the pivot
const pivotValue = arr[end];
let pivotIndex = start;
for (let i = start; i < end; i++) {
if (arr[i] < pivotValue) {
// Swapping elements
[arr[i], arr[pivotIndex]] = [arr[pivotIndex], arr[i]];
// Moving to next element
pivotIndex++;
}
}
// Putting the pivot value in the middle
[arr[pivotIndex], arr[end]] = [arr[end], arr[pivotIndex]]
return pivotIndex;
};
In this case, the pivot is the final piece. The pivotIndex variable is used to maintain music from the "center" function, in which all items on the left are less than the pivotValue and all ones on the right are greater. As the final step, the pivot, which is the last element in our case, is exchanged with the pivotIndex. As a result, our pivot element may eventually end up in the "middle." All components to the left of the pivot are substantially less than the pivot, while all elements to the right of the pivot are more than or identical to the pivot.
Recursive Implementation
Now that we have the partition() function, we must recursively deconstruct this annoyance and utilize partitioning good judgment to finish the other steps:
function quickSortRecursive(arr, start, end) {
// Base case or terminating case
if (start >= end) {
return;
}
// Returns pivotIndex
let index = partition(arr, start, end);
// Recursively apply the same logic to the left and right subarrays
quickSort(arr, start, index - 1);
quickSort(arr, index + 1, end);
}
In this feature, we start by separating the array. The left and right subarrays are then partitioned separately. This approach is continued until the method is overridden with an array that is not empty or has more than one entry. This is owing to the fact that empty arrays and arrays with only one element are treated as unattended.
Let's put this code to the test on our one-of-a-kind scenario:
array = [7, -2, 4, 1, 6, 5, 0, -4, 2]
quickSortRecursive(array, 0, array.length - 1)
console.log(array)
The output would look like this:
-4,-2,0,1,2,4,5,6,7
Iterative Implementation
As previously stated, the recursive technique to Quicksort is far more intuitive. However, iteratively improving Quicksort is a common interview topic for software engineers. Using a stack to imitate recursive calls is the first component that concerns mind with maximal recursive-to-iterative conversions. This is done so that we can apply some of our previously learned recursive good judgment in an iterative manner.
We must keep track of the number of unsorted subarrays that we still have. One approach is to keep "pairs" of components on a stack that represent the beginning and end of an unsorted subarray.
Arrays provide the push() and pop() operations, despite the fact that there is no special stack information shape in JavaScript. We'll have to manually confirm the top of the stack with stack[stack.length - 1] because they don't use the peek() feature. The partition function will remain the same as it was when the recursive technique was used. Let's have a look at how the Quicksort phase is written:
function quickSortIterative(arr) {
// Creating an array that we'll use as a stack, using the push() and pop() functions
stack = [];
// Adding the entire initial array as an "unsorted subarray"
stack.push(0);
stack.push(arr.length - 1);
// There isn't an explicit peek() function
// The loop repeats as long as we have unsorted subarrays
while(stack[stack.length - 1] >= 0){
// Extracting the top unsorted subarray
end = stack.pop();
start = stack.pop();
pivotIndex = partition(arr, start, end);
// If there are unsorted elements to the "left" of the pivot,
// we add that subarray to the stack so we can sort it later
if (pivotIndex - 1 > start){
stack.push(start);
stack.push(pivotIndex - 1);
}
// If there are unsorted elements to the "right" of the pivot,
// we add that subarray to the stack so we can sort it later
if (pivotIndex + 1 < end){
stack.push(pivotIndex + 1);
stack.push(end);
}
}
Let's put this code to the test on our example:
ourArray = [7, -2, 4, 1, 6, 5, 0, -4, 2]
quickSortIterative(ourArray)
console.log(ourArray)
Output should look like this:
-4,-2,0,1,2,4,5,6,7
Note that when it comes to sorting algorithms, it's usually best to visualize them in action. It just allows us to "see" them move.
Conclusion
We looked at sorting and the Quicksort set of rules in this post, went over the theory behind Quicksort, and then utilized JavaScript to create it recursively and iteratively.
References: