diff --git a/README.md b/README.md index 83f48ee64..72679b39a 100644 --- a/README.md +++ b/README.md @@ -258,13 +258,12 @@ $ java -cp classes com.williamfiset.algorithms.search.BinarySearch - [Bit manipulations](src/main/java/com/williamfiset/algorithms/other/BitManipulations.java) **- O(1)** - [List permutations](src/main/java/com/williamfiset/algorithms/other/Permutations.java) **- O(n!)** -- [:movie_camera:](https://www.youtube.com/watch?v=RnlHPR0lyOE) [Power set (set of all subsets)](src/main/java/com/williamfiset/algorithms/other/PowerSet.java) **- O(2n)** +- [:movie_camera:](https://www.youtube.com/watch?v=RnlHPR0lyOE) [Power set (set of all subsets)](src/main/java/com/williamfiset/algorithms/other/PowerSet.java) **- O(n · 2n)** - [Set combinations](src/main/java/com/williamfiset/algorithms/other/Combinations.java) **- O(n choose r)** - [Set combinations with repetition](src/main/java/com/williamfiset/algorithms/other/CombinationsWithRepetition.java) **- O((n+r-1) choose r)** - [Sliding Window Minimum/Maximum](src/main/java/com/williamfiset/algorithms/other/SlidingWindowMaximum.java) **- O(1)** - [Square Root Decomposition](src/main/java/com/williamfiset/algorithms/other/SquareRootDecomposition.java) **- O(1) point updates, O(√n) range queries** - [Unique set combinations](src/main/java/com/williamfiset/algorithms/other/UniqueCombinations.java) **- O(n choose r)** -- [Lazy Range Adder](src/main/java/com/williamfiset/algorithms/other/LazyRangeAdder.java) **- O(1) range updates, O(n) to finalize all updates** # Search algorithms @@ -284,6 +283,7 @@ $ java -cp classes com.williamfiset.algorithms.search.BinarySearch - [Quicksort (in-place, Hoare partitioning)](src/main/java/com/williamfiset/algorithms/sorting/QuickSort.java) **- Θ(nlog(n))** - [Quicksort3 (Dutch National Flag algorithm)](src/main/java/com/williamfiset/algorithms/sorting/QuickSort3.java) **- Θ(nlog(n))** - [Selection sort](src/main/java/com/williamfiset/algorithms/sorting/SelectionSort.java) **- O(n2)** +- [Tim sort](src/main/java/com/williamfiset/algorithms/sorting/TimSort.java) **- O(nlog(n))** - [Radix sort](src/main/java/com/williamfiset/algorithms/sorting/RadixSort.java) **- O(n\*w)** # String algorithms diff --git a/src/main/java/com/williamfiset/algorithms/other/BUILD b/src/main/java/com/williamfiset/algorithms/other/BUILD index 77c05d3da..3a7586729 100644 --- a/src/main/java/com/williamfiset/algorithms/other/BUILD +++ b/src/main/java/com/williamfiset/algorithms/other/BUILD @@ -21,13 +21,6 @@ java_binary( runtime_deps = [":other"], ) -# bazel run //src/main/java/com/williamfiset/algorithms/other:LazyRangeAdder -java_binary( - name = "LazyRangeAdder", - main_class = "com.williamfiset.algorithms.other.LazyRangeAdder", - runtime_deps = [":other"], -) - # bazel run //src/main/java/com/williamfiset/algorithms/other:Permutations java_binary( name = "Permutations", diff --git a/src/main/java/com/williamfiset/algorithms/other/LazyRangeAdder.java b/src/main/java/com/williamfiset/algorithms/other/LazyRangeAdder.java deleted file mode 100644 index 33eaacfab..000000000 --- a/src/main/java/com/williamfiset/algorithms/other/LazyRangeAdder.java +++ /dev/null @@ -1,71 +0,0 @@ -/** - * The LazyRangerAdder is a handy class for performing addition range updates of constant values on - * an array. This range adder is especially useful for offline algorithms which know all range - * updates ahead of time. - * - *
Time complexity to update O(1) but time complexity to finalize all additions is O(n) - * - * @author Atharva Thorve, aaathorve@gmail.com - */ -package com.williamfiset.algorithms.other; - -public class LazyRangeAdder { - - // The number of elements in the input array. - private int n; - - // The original input array - private int[] array; - - // The difference array with the deltas between values, size n+1 - private int[] differenceArray; - - // Initialize an instance of a LazyRangeAdder on some input values - public LazyRangeAdder(int[] array) { - this.array = array; - this.n = array.length; - - differenceArray = new int[n + 1]; - differenceArray[0] = array[0]; - for (int i = 1; i < n; i++) { - differenceArray[i] = array[i] - array[i - 1]; - } - } - - // Add `x` to the range [l, r] inclusive - public void add(int l, int r, int x) { - differenceArray[l] += x; - differenceArray[r + 1] -= x; - } - - // IMPORTANT: Make certain to call this method once all the additions - // have been made with add(l, r, x) - public void done() { - for (int i = 0; i < n; i++) { - if (i == 0) { - array[i] = differenceArray[i]; - } else { - array[i] = differenceArray[i] + array[i - 1]; - } - } - } - - public static void main(String[] args) { - // Array to be updated - int[] array = {10, 4, 6, 13, 8, 15, 17, 22}; - LazyRangeAdder lazyRangeAdder = new LazyRangeAdder(array); - - // After below add(l, r, x), the - // elements should become [10, 14, 16, 23, 18, 15, 17, 22] - lazyRangeAdder.add(1, 4, 10); - lazyRangeAdder.done(); - System.out.println(java.util.Arrays.toString(array)); - - // After below add(l, r, x), the - // elements should become [22, 26, 28, 30, 25, 22, 24, 34] - lazyRangeAdder.add(3, 6, -5); - lazyRangeAdder.add(0, 7, 12); - lazyRangeAdder.done(); - System.out.println(java.util.Arrays.toString(array)); - } -} diff --git a/src/main/java/com/williamfiset/algorithms/other/PowerSet.java b/src/main/java/com/williamfiset/algorithms/other/PowerSet.java index 5ed95c8b5..ff8e6b7f0 100644 --- a/src/main/java/com/williamfiset/algorithms/other/PowerSet.java +++ b/src/main/java/com/williamfiset/algorithms/other/PowerSet.java @@ -1,84 +1,75 @@ /** - * This code snippet shows how to generate the powerset of a set which is the set of all subsets of - * a set. There are two common ways of doing this which are to use the binary representation of - * numbers on a computer or to do it recursively. Both methods are shown here, pick your flavor! + * Generates the power set of a set, which is the set of all subsets. * - *
Time Complexity: O( 2^n )
+ * Two approaches are provided: an iterative method using binary representation of numbers, and a
+ * recursive backtracking method. Both produce the same result.
+ *
+ * Time Complexity: O(n * 2^n)
*
* @author William Fiset, william.alexandre.fiset@gmail.com
*/
package com.williamfiset.algorithms.other;
-public class PowerSet {
-
- // Use the fact that numbers represented in binary can be
- // used to generate all the subsets of an array
- static void powerSetUsingBinary(int[] set) {
+import java.util.*;
- final int N = set.length;
- final int MAX_VAL = 1 << N;
+public class PowerSet {
- for (int subset = 0; subset < MAX_VAL; subset++) {
- System.out.print("{ ");
- for (int i = 0; i < N; i++) {
- int mask = 1 << i;
- if ((subset & mask) == mask) System.out.print(set[i] + " ");
+ /**
+ * Generates the power set using binary representation. Each integer from 0 to 2^n - 1 represents
+ * a subset, where bit i indicates whether element i is included.
+ */
+ public static > powerSetBinary(List
> result = new ArrayList<>(total);
+
+ for (int mask = 0; mask < total; mask++) {
+ List
> powerSetRecursive(List
> result = new ArrayList<>();
+ recurse(0, set, new ArrayList<>(), result);
+ return result;
+ }
- // Backtrack and don't include this element
- used[at] = false;
- powerSetRecursive(at + 1, set, used);
+ private static
> result) {
+ if (at == set.size()) {
+ // Snapshot the current subset — must copy since 'current' is mutated during backtracking
+ result.add(new ArrayList<>(current));
+ return;
}
+ // Include set[at] and explore all subsets of the remaining elements
+ current.add(set.get(at));
+ recurse(at + 1, set, current, result);
+
+ // Backtrack: undo the inclusion of set[at], then explore without it
+ current.remove(current.size() - 1);
+ recurse(at + 1, set, current, result);
}
public static void main(String[] args) {
+ List
> powerSet) {
+ return powerSet.stream()
+ .map(subset -> {
+ List
> result = PowerSet.powerSetBinary(List.of());
+ assertThat(result).hasSize(1);
+ assertThat(result.get(0)).isEmpty();
+ }
+
+ @Test
+ public void emptySet_recursive() {
+ List
> result = PowerSet.powerSetRecursive(List.of());
+ assertThat(result).hasSize(1);
+ assertThat(result.get(0)).isEmpty();
+ }
+
+ @Test
+ public void singleElement_binary() {
+ List
> result = PowerSet.powerSetBinary(List.of(42));
+ assertThat(normalize(result)).containsExactly("[]", "[42]");
+ }
+
+ @Test
+ public void singleElement_recursive() {
+ List
> result = PowerSet.powerSetRecursive(List.of(42));
+ assertThat(normalize(result)).containsExactly("[]", "[42]");
+ }
+
+ @Test
+ public void threeElements_binary() {
+ List
> result = PowerSet.powerSetBinary(List.of(1, 2, 3));
+ assertThat(result).hasSize(8);
+ assertThat(normalize(result))
+ .containsExactly("[]", "[1]", "[2]", "[3]", "[1, 2]", "[1, 3]", "[2, 3]", "[1, 2, 3]");
+ }
+
+ @Test
+ public void threeElements_recursive() {
+ List
> result = PowerSet.powerSetRecursive(List.of(1, 2, 3));
+ assertThat(result).hasSize(8);
+ assertThat(normalize(result))
+ .containsExactly("[]", "[1]", "[2]", "[3]", "[1, 2]", "[1, 3]", "[2, 3]", "[1, 2, 3]");
+ }
+
+ @Test
+ public void bothMethodsProduceSameSubsets() {
+ List
> result = PowerSet.powerSetBinary(List.of("a", "b"));
+ assertThat(normalize(result)).containsExactly("[]", "[a]", "[b]", "[a, b]");
+ }
+}
diff --git a/src/test/java/com/williamfiset/algorithms/sorting/BUILD b/src/test/java/com/williamfiset/algorithms/sorting/BUILD
index 51be23a74..092e69fe2 100644
--- a/src/test/java/com/williamfiset/algorithms/sorting/BUILD
+++ b/src/test/java/com/williamfiset/algorithms/sorting/BUILD
@@ -127,6 +127,16 @@ java_test(
deps = TEST_DEPS,
)
+java_test(
+ name = "TimSortTest",
+ srcs = ["TimSortTest.java"],
+ main_class = "org.junit.platform.console.ConsoleLauncher",
+ use_testrunner = False,
+ args = ["--select-class=com.williamfiset.algorithms.sorting.TimSortTest"],
+ runtime_deps = JUNIT5_RUNTIME_DEPS,
+ deps = TEST_DEPS,
+)
+
java_test(
name = "SortingTest",
srcs = ["SortingTest.java"],
diff --git a/src/test/java/com/williamfiset/algorithms/sorting/SortingTest.java b/src/test/java/com/williamfiset/algorithms/sorting/SortingTest.java
index 94a5765f6..3ff9b497f 100644
--- a/src/test/java/com/williamfiset/algorithms/sorting/SortingTest.java
+++ b/src/test/java/com/williamfiset/algorithms/sorting/SortingTest.java
@@ -25,7 +25,8 @@ enum SortingAlgorithm {
QUICK_SORT(new QuickSort()),
QUICK_SORT3(new QuickSort3()),
RADIX_SORT(new RadixSort()),
- SELECTION_SORT(new SelectionSort());
+ SELECTION_SORT(new SelectionSort()),
+ TIM_SORT(new TimSort());
private InplaceSort algorithm;
@@ -49,7 +50,8 @@ public InplaceSort getSortingAlgorithm() {
SortingAlgorithm.QUICK_SORT,
SortingAlgorithm.QUICK_SORT3,
SortingAlgorithm.RADIX_SORT,
- SortingAlgorithm.SELECTION_SORT);
+ SortingAlgorithm.SELECTION_SORT,
+ SortingAlgorithm.TIM_SORT);
@Test
public void verifySortingAlgorithms_smallPositiveIntegersOnly() {
diff --git a/src/test/java/com/williamfiset/algorithms/sorting/TimSortTest.java b/src/test/java/com/williamfiset/algorithms/sorting/TimSortTest.java
new file mode 100644
index 000000000..f31c56d51
--- /dev/null
+++ b/src/test/java/com/williamfiset/algorithms/sorting/TimSortTest.java
@@ -0,0 +1,103 @@
+package com.williamfiset.algorithms.sorting;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.williamfiset.algorithms.utils.TestUtils;
+import java.util.Arrays;
+import org.junit.jupiter.api.*;
+
+public class TimSortTest {
+
+ private final TimSort sorter = new TimSort();
+
+ @Test
+ public void testEmptyArray() {
+ int[] array = {};
+ sorter.sort(array);
+ assertThat(array).isEqualTo(new int[] {});
+ }
+
+ @Test
+ public void testSingleElement() {
+ int[] array = {42};
+ sorter.sort(array);
+ assertThat(array).isEqualTo(new int[] {42});
+ }
+
+ @Test
+ public void testAlreadySorted() {
+ int[] array = {1, 2, 3, 4, 5};
+ sorter.sort(array);
+ assertThat(array).isEqualTo(new int[] {1, 2, 3, 4, 5});
+ }
+
+ @Test
+ public void testReverseSorted() {
+ int[] array = {5, 4, 3, 2, 1};
+ sorter.sort(array);
+ assertThat(array).isEqualTo(new int[] {1, 2, 3, 4, 5});
+ }
+
+ @Test
+ public void testWithDuplicates() {
+ int[] array = {10, 4, 6, 4, 8, -13, 2, 3};
+ sorter.sort(array);
+ assertThat(array).isEqualTo(new int[] {-13, 2, 3, 4, 4, 6, 8, 10});
+ }
+
+ @Test
+ public void testAllSameElements() {
+ int[] array = {3, 3, 3, 3, 3};
+ sorter.sort(array);
+ assertThat(array).isEqualTo(new int[] {3, 3, 3, 3, 3});
+ }
+
+ @Test
+ public void testNegativeNumbers() {
+ int[] array = {-3, -1, -4, -1, -5};
+ sorter.sort(array);
+ assertThat(array).isEqualTo(new int[] {-5, -4, -3, -1, -1});
+ }
+
+ @Test
+ public void testMixedPositiveAndNegative() {
+ int[] array = {3, -2, 0, 7, -5, 1};
+ sorter.sort(array);
+ assertThat(array).isEqualTo(new int[] {-5, -2, 0, 1, 3, 7});
+ }
+
+ @Test
+ public void testTwoElements() {
+ int[] array = {9, 1};
+ sorter.sort(array);
+ assertThat(array).isEqualTo(new int[] {1, 9});
+ }
+
+ @Test
+ public void testSmallerThanMinRun() {
+ int[] array = {5, 3, 8, 1, 9, 2, 7};
+ sorter.sort(array);
+ assertThat(array).isEqualTo(new int[] {1, 2, 3, 5, 7, 8, 9});
+ }
+
+ @Test
+ public void testLargerThanMinRun() {
+ // 100 elements to exercise the merge phase
+ int[] array = TestUtils.randomIntegerArray(100, -50, 51);
+ int[] expected = array.clone();
+ Arrays.sort(expected);
+ sorter.sort(array);
+ assertThat(array).isEqualTo(expected);
+ }
+
+ @Test
+ public void testRandomized() {
+ for (int size = 0; size < 500; size++) {
+ int[] values = TestUtils.randomIntegerArray(size, -50, 51);
+ int[] expected = values.clone();
+ Arrays.sort(expected);
+ sorter.sort(values);
+ assertThat(values).isEqualTo(expected);
+ }
+ }
+}