From 5eb1195c61915bd5f4a48ad63c74015b64a72c32 Mon Sep 17 00:00:00 2001 From: Oleksii Trekhleb Date: Tue, 21 Aug 2018 16:55:26 +0300 Subject: [PATCH] Add backtracking solution for finding the power-set of a set. --- README.md | 3 +- src/algorithms/sets/power-set/README.md | 55 ++++++++++++++++++- .../power-set/__test__/btPowerSet.test.js | 21 +++++++ .../power-set/__test__/bwPowerSet.test.js | 21 +++++++ .../sets/power-set/__test__/powerSet.test.js | 24 -------- src/algorithms/sets/power-set/btPowerSet.js | 34 ++++++++++++ .../power-set/{powerSet.js => bwPowerSet.js} | 10 +++- 7 files changed, 139 insertions(+), 29 deletions(-) create mode 100644 src/algorithms/sets/power-set/__test__/btPowerSet.test.js create mode 100644 src/algorithms/sets/power-set/__test__/bwPowerSet.test.js delete mode 100644 src/algorithms/sets/power-set/__test__/powerSet.test.js create mode 100644 src/algorithms/sets/power-set/btPowerSet.js rename src/algorithms/sets/power-set/{powerSet.js => bwPowerSet.js} (79%) diff --git a/README.md b/README.md index 968a5160..f7621796 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ a set of rules that precisely define a sequence of operations. * **Sets** * `B` [Cartesian Product](src/algorithms/sets/cartesian-product) - product of multiple sets * `B` [Fisher–Yates Shuffle](src/algorithms/sets/fisher-yates) - random permutation of a finite sequence - * `A` [Power Set](src/algorithms/sets/power-set) - all subsets of a set + * `A` [Power Set](src/algorithms/sets/power-set) - all subsets of a set (bitwise and backtracking solutions) * `A` [Permutations](src/algorithms/sets/permutations) (with and without repetitions) * `A` [Combinations](src/algorithms/sets/combinations) (with and without repetitions) * `A` [Longest Common Subsequence](src/algorithms/sets/longest-common-subsequence) (LCS) @@ -190,6 +190,7 @@ if it satisfies all conditions, and only then continue generating subsequent sol different path of finding a solution. Normally the DFS traversal of state-space is being used. * `B` [Jump Game](src/algorithms/uncategorized/jump-game) * `B` [Unique Paths](src/algorithms/uncategorized/unique-paths) + * `B` [Power Set](src/algorithms/sets/power-set) - all subsets of a set * `A` [Hamiltonian Cycle](src/algorithms/graph/hamiltonian-cycle) - Visit every vertex exactly once * `A` [N-Queens Problem](src/algorithms/uncategorized/n-queens) * `A` [Knight's Tour](src/algorithms/uncategorized/knight-tour) diff --git a/src/algorithms/sets/power-set/README.md b/src/algorithms/sets/power-set/README.md index 5551ea35..6681db01 100644 --- a/src/algorithms/sets/power-set/README.md +++ b/src/algorithms/sets/power-set/README.md @@ -1,11 +1,62 @@ # Power Set -Power set of a set A is the set of all of the subsets of A. +Power set of a set `S` is the set of all of the subsets of `S`, including the +empty set and `S` itself. Power set of set `S` is denoted as `P(S)`. -Eg. for `{x, y, z}`, the subsets are : `{{}, {x}, {y}, {z}, {x, y}, {x, z}, {y, z}, {x, y, z}}` +For example for `{x, y, z}`, the subsets +are: + +```text +{ + {}, // (also denoted empty set ∅ or the null set) + {x}, + {y}, + {z}, + {x, y}, + {x, z}, + {y, z}, + {x, y, z} +} +``` ![Power Set](https://www.mathsisfun.com/sets/images/power-set.svg) +Here is how we may illustrate the elements of the power set of the set `{x, y, z}` ordered with respect to +inclusion: + +![](https://upload.wikimedia.org/wikipedia/commons/e/ea/Hasse_diagram_of_powerset_of_3.svg) + +**Number of Subsets** + +If `S` is a finite set with `|S| = n` elements, then the number of subsets +of `S` is `|P(S)| = 2^n`. This fact, which is the motivation for the +notation `2^S`, may be demonstrated simply as follows: + +> First, order the elements of `S` in any manner. We write any subset of `S` in +the format `{γ1, γ2, ..., γn}` where `γi , 1 ≤ i ≤ n`, can take the value +of `0` or `1`. If `γi = 1`, the `i`-th element of `S` is in the subset; +otherwise, the `i`-th element is not in the subset. Clearly the number of +distinct subsets that can be constructed this way is `2^n` as `γi ∈ {0, 1}`. + +## Algorithms + +### Bitwise Solution + +Each number in binary representation in a range from `0` to `2^n` does exactly +what we need: it shows by its bits (`0` or `1`) whether to include related +element from the set or not. For example, for the set `{1, 2, 3}` the binary +number of `0b010` would mean that we need to include only `2` to the current set. + +> See [bwPowerSet.js](./bwPowerSet.js) file for bitwise solution. + +### Backtracking Solution + +In backtracking approach we're constantly trying to add next element of the set +to the subset, memorizing it and then removing it and try the same with the next +element. + +> See [btPowerSet.js](./btPowerSet.js) file for backtracking solution. + ## References * [Wikipedia](https://en.wikipedia.org/wiki/Power_set) diff --git a/src/algorithms/sets/power-set/__test__/btPowerSet.test.js b/src/algorithms/sets/power-set/__test__/btPowerSet.test.js new file mode 100644 index 00000000..acfb1730 --- /dev/null +++ b/src/algorithms/sets/power-set/__test__/btPowerSet.test.js @@ -0,0 +1,21 @@ +import btPowerSet from '../btPowerSet'; + +describe('btPowerSet', () => { + it('should calculate power set of given set using backtracking approach', () => { + expect(btPowerSet([1])).toEqual([ + [], + [1], + ]); + + expect(btPowerSet([1, 2, 3])).toEqual([ + [], + [1], + [1, 2], + [1, 2, 3], + [1, 3], + [2], + [2, 3], + [3], + ]); + }); +}); diff --git a/src/algorithms/sets/power-set/__test__/bwPowerSet.test.js b/src/algorithms/sets/power-set/__test__/bwPowerSet.test.js new file mode 100644 index 00000000..e2ed1793 --- /dev/null +++ b/src/algorithms/sets/power-set/__test__/bwPowerSet.test.js @@ -0,0 +1,21 @@ +import bwPowerSet from '../bwPowerSet'; + +describe('bwPowerSet', () => { + it('should calculate power set of given set using bitwise approach', () => { + expect(bwPowerSet([1])).toEqual([ + [], + [1], + ]); + + expect(bwPowerSet([1, 2, 3])).toEqual([ + [], + [1], + [2], + [1, 2], + [3], + [1, 3], + [2, 3], + [1, 2, 3], + ]); + }); +}); diff --git a/src/algorithms/sets/power-set/__test__/powerSet.test.js b/src/algorithms/sets/power-set/__test__/powerSet.test.js deleted file mode 100644 index 26195081..00000000 --- a/src/algorithms/sets/power-set/__test__/powerSet.test.js +++ /dev/null @@ -1,24 +0,0 @@ -import powerSet from '../powerSet'; - -describe('powerSet', () => { - it('should calculate power set of given set', () => { - const powerSets1 = powerSet([1]); - const powerSets2 = powerSet([1, 2, 3]); - - expect(powerSets1).toEqual([ - [], - [1], - ]); - - expect(powerSets2).toEqual([ - [], - [1], - [2], - [1, 2], - [3], - [1, 3], - [2, 3], - [1, 2, 3], - ]); - }); -}); diff --git a/src/algorithms/sets/power-set/btPowerSet.js b/src/algorithms/sets/power-set/btPowerSet.js new file mode 100644 index 00000000..1cf2d196 --- /dev/null +++ b/src/algorithms/sets/power-set/btPowerSet.js @@ -0,0 +1,34 @@ +/** + * @param {*[]} originalSet - Original set of elements we're forming power-set of. + * @param {*[][]} allSubsets - All subsets that have been formed so far. + * @param {*[]} currentSubSet - Current subset that we're forming at the moment. + * @param {number} startAt - The position of in original set we're starting to form current subset. + * @return {*[][]} - All subsets of original set. + */ +function btPowerSetRecursive(originalSet, allSubsets = [[]], currentSubSet = [], startAt = 0) { + // In order to avoid duplication we need to start from next element every time we're forming a + // subset. If we will start from zero then we'll have duplicates like {3, 3, 3}. + for (let position = startAt; position < originalSet.length; position += 1) { + // Let's push current element to the subset. + currentSubSet.push(originalSet[position]); + // Current subset is already valid so let's memorize it. + allSubsets.push([...currentSubSet]); + // Let's try to form all other subsets for the current subset. + btPowerSetRecursive(originalSet, allSubsets, currentSubSet, position + 1); + // BACKTRACK. Exclude last element from the subset and try the next one. + currentSubSet.pop(); + } + + // Return all subsets of a set. + return allSubsets; +} + +/** + * Find power-set of a set using BACKTRACKING approach. + * + * @param {*[]} originalSet + * @return {*[][]} + */ +export default function btPowerSet(originalSet) { + return btPowerSetRecursive(originalSet); +} diff --git a/src/algorithms/sets/power-set/powerSet.js b/src/algorithms/sets/power-set/bwPowerSet.js similarity index 79% rename from src/algorithms/sets/power-set/powerSet.js rename to src/algorithms/sets/power-set/bwPowerSet.js index 8619f32e..479cf984 100644 --- a/src/algorithms/sets/power-set/powerSet.js +++ b/src/algorithms/sets/power-set/bwPowerSet.js @@ -1,3 +1,9 @@ +/** + * Find power-set of a set using BITWISE approach. + * + * @param {*[]} originalSet + * @return {*[][]} + */ export default function powerSet(originalSet) { const subSets = []; @@ -7,8 +13,8 @@ export default function powerSet(originalSet) { const numberOfCombinations = 2 ** originalSet.length; // Each number in binary representation in a range from 0 to 2^n does exactly what we need: - // it shoes by its bits (0 or 1) whether to include related element from the set or not. - // For example, for the set {1, 2, 3} the binary number of 010 would mean that we need to + // it shows by its bits (0 or 1) whether to include related element from the set or not. + // For example, for the set {1, 2, 3} the binary number of 0b010 would mean that we need to // include only "2" to the current set. for (let combinationIndex = 0; combinationIndex < numberOfCombinations; combinationIndex += 1) { const subSet = [];