Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add iterative quick sort #215

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions src/algorithms/sorting/quick-sort/QuickSortIterative.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import Sort from '../Sort';
import Stack from '../../../data-structures/stack/Stack';

export default class QuickSortIterative extends Sort {
/**
* Iterative Quick Sort
*
* @param {*[]} originalArray - Not sorted array.
* @param {number} inputLowIndex
* @param {number} inputHighIndex
* @return {*[]} - Sorted array.
*/
sort(
originalArray,
inputLowIndex = 0,
inputHighIndex = originalArray.length - 1,
sortInPlace = true,
) {
// Copies array on in case we don't want to sort in place
const array = sortInPlace ? originalArray : [...originalArray];

// If array has less than or equal to one elements then it is already sorted.
if (array.length <= 1) {
return array;
}

/**
* The partitionArray() operates on the subarray between lowIndex and highIndex, inclusive.
* It arbitrarily chooses the last element in the subarray as the pivot.
* Then, it partially sorts the subarray into elements than are less than the pivot,
* and elements that are greater than or equal to the pivot.
* Each time partitionArray() is executed, the pivot element is in its final sorted position.
*
* @param {number} lowIndex
* @param {number} highIndex
* @return {number}
*/
const partitionArray = (lowIndex, highIndex) => {
/**
* Swaps two elements in array.
* @param {number} leftIndex
* @param {number} rightIndex
*/
const swap = (leftIndex, rightIndex) => {
const temp = array[leftIndex];
array[leftIndex] = array[rightIndex];
array[rightIndex] = temp;
};

const pivot = array[highIndex];
// visitingCallback is used for time-complexity analysis.
this.callbacks.visitingCallback(array[pivot]);

let partitionIndex = lowIndex;
for (let currentIndex = lowIndex; currentIndex < highIndex; currentIndex += 1) {
if (this.comparator.lessThan(array[currentIndex], pivot)) {
swap(partitionIndex, currentIndex);
partitionIndex += 1;
}
}

// The element at the partitionIndex is guaranteed to be greater than or equal to pivot.
// All elements to the left of partitionIndex are guaranteed to be less than pivot.
// Swapping the pivot with the partitionIndex therefore places the pivot in its
// final sorted position.
swap(partitionIndex, highIndex);

return partitionIndex;
};

/**
* Replace recursion with auxiliary stack
*/
const stack = new Stack();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice addition! Just to nitpick, it might be a little cleaner to store arrays on the stack. That way, each item in the stack corresponds to a single iteration.
e.g.

    let lowIndex = inputLowIndex;
    let highIndex = inputHighIndex;
    let partitionIndex;
    
    stack.push([ lowIndex, highIndex ]);

    while (!stack.isEmpty()) {
      [ lowIndex, highIndex ] = stack.pop();
      partitionIndex = partitionArray(lowIndex, highIndex);
      if (partitionIndex - 1 > lowIndex) {
        stack.push([ lowIndex, partitionIndex - 1 ]);
      }
      if (partitionIndex + 1 < highIndex) {
        stack.push([ partitionIndex + 1, highIndex ]);
      }
    }

stack.push(inputLowIndex);
stack.push(inputHighIndex);

while (!stack.isEmpty()) {
const highIndex = stack.pop();
const lowIndex = stack.pop();
const partitionIndex = partitionArray(lowIndex, highIndex);

if (partitionIndex - 1 > lowIndex) {
stack.push(lowIndex);
stack.push(partitionIndex - 1);
}

if (partitionIndex + 1 < highIndex) {
stack.push(partitionIndex + 1);
stack.push(highIndex);
}
}

return array;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import QuickSortIterative from '../QuickSortIterative';
import {
equalArr,
notSortedArr,
reverseArr,
sortedArr,
SortTester,
} from '../../SortTester';

// Complexity constants.
const SORTED_ARRAY_VISITING_COUNT = 19;
const NOT_SORTED_ARRAY_VISITING_COUNT = 19;
const REVERSE_SORTED_ARRAY_VISITING_COUNT = 19;
const EQUAL_ARRAY_VISITING_COUNT = 19;

describe('QuickSortIterative', () => {
it('should sort array', () => {
SortTester.testSort(QuickSortIterative);
});

it('should sort array with custom comparator', () => {
SortTester.testSortWithCustomComparator(QuickSortIterative);
});

it('should sort negative numbers', () => {
SortTester.testNegativeNumbersSort(QuickSortIterative);
});

it('should visit EQUAL array element specified number of times', () => {
SortTester.testAlgorithmTimeComplexity(
QuickSortIterative,
equalArr,
EQUAL_ARRAY_VISITING_COUNT,
);
});

it('should visit SORTED array element specified number of times', () => {
SortTester.testAlgorithmTimeComplexity(
QuickSortIterative,
sortedArr,
SORTED_ARRAY_VISITING_COUNT,
);
});

it('should visit NOT SORTED array element specified number of times', () => {
SortTester.testAlgorithmTimeComplexity(
QuickSortIterative,
notSortedArr,
NOT_SORTED_ARRAY_VISITING_COUNT,
);
});

it('should visit REVERSE SORTED array element specified number of times', () => {
SortTester.testAlgorithmTimeComplexity(
QuickSortIterative,
reverseArr,
REVERSE_SORTED_ARRAY_VISITING_COUNT,
);
});

it('should sort in place', () => {
const sorter = new QuickSortIterative();
const originalArray = [7, 5, 1, 42, 30, 24, 14];
const sortedArray = sorter.sort(originalArray);
expect(originalArray).toEqual(sortedArray);
});

it('should not modify original array', () => {
const sorter = new QuickSortIterative();
const originalArray = [7, 5, 1, 42, 30, 24, 14];
const sortedArray = sorter.sort(originalArray, 0, originalArray.length - 1, false);
expect(originalArray).not.toEqual(sortedArray);
});
});