Add backtracking solution for finding the power-set of a set.

This commit is contained in:
Oleksii Trekhleb 2018-08-21 16:55:26 +03:00
parent bffacf0707
commit 5eb1195c61
7 changed files with 139 additions and 29 deletions

View File

@ -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` [FisherYates 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)

View File

@ -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)

View File

@ -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],
]);
});
});

View File

@ -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],
]);
});
});

View File

@ -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],
]);
});
});

View File

@ -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);
}

View File

@ -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 = [];