From 152971f801d67d1ff66bcce50a04c19e36436941 Mon Sep 17 00:00:00 2001
From: yavorski <yaforski@gmail.com>
Date: Thu, 27 Sep 2018 21:58:56 +0300
Subject: [PATCH] Add iterative quick sort

---
 .../sorting/quick-sort/QuickSortIterative.js  | 96 +++++++++++++++++++
 .../__test__/QuickSortIterative.test.js       | 74 ++++++++++++++
 2 files changed, 170 insertions(+)
 create mode 100644 src/algorithms/sorting/quick-sort/QuickSortIterative.js
 create mode 100644 src/algorithms/sorting/quick-sort/__test__/QuickSortIterative.test.js

diff --git a/src/algorithms/sorting/quick-sort/QuickSortIterative.js b/src/algorithms/sorting/quick-sort/QuickSortIterative.js
new file mode 100644
index 0000000000..46bcc8b63f
--- /dev/null
+++ b/src/algorithms/sorting/quick-sort/QuickSortIterative.js
@@ -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();
+    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;
+  }
+}
diff --git a/src/algorithms/sorting/quick-sort/__test__/QuickSortIterative.test.js b/src/algorithms/sorting/quick-sort/__test__/QuickSortIterative.test.js
new file mode 100644
index 0000000000..b68427a82a
--- /dev/null
+++ b/src/algorithms/sorting/quick-sort/__test__/QuickSortIterative.test.js
@@ -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);
+  });
+});