Перестановки и комбинации
Описание
Когда порядок элементов не важен, это комбинация (combination).
Когда порядок элементов важен, это перестановка (permutation).
Перестановки
Перестановки без повторений
Перестановка (permutation) - это перестановка элементов упорядоченного списка S
во взаимно однозначное соответствие самому S
.
Пример перестановок строки ABC
:
ABC ACB BAC BCA CBA CAB
Еще один пример - первые три человека в гонке: вы не можете одновременно быть и первым, и вторым.
Количество вариантов:
n * (n-1) * (n -2) * ... * 1 = n! (факториал `n`)
Реализация
// algorithms/sets/permutations/without-repetitions.js
export default function withoutRepetitions(set) {
if (set.length === 1) {
return [set]
}
const result = []
const subset = withoutRepetitions(set.slice(1))
const first = set[0]
for (let i = 0; i < subset.length; i++) {
const smaller = subset[i]
for (let j = 0; j < smaller.length + 1; j++) {
const permutation = [...smaller.slice(0, j), first, ...smaller.slice(j)]
result.push(permutation)
}
}
return result
}
Тестирование
// algorithms/sets/permutations/__tests__/without-repetitions.test.js
import withoutRepetitions from '../without-repetitions'
import factorial from '../../../math/factorial'
describe('withoutRepetitions', () => {
it('должен переставлять элементы множеств без повторений', () => {
const permutations1 = withoutRepetitions(['A'])
expect(permutations1).toEqual([['A']])
const permutations2 = withoutRepetitions(['A', 'B'])
expect(permutations2.length).toBe(2)
expect(permutations2).toEqual([
['A', 'B'],
['B', 'A'],
])
const permutations6 = withoutRepetitions(['A', 'A'])
expect(permutations6.length).toBe(2)
expect(permutations6).toEqual([
['A', 'A'],
['A', 'A'],
])
const permutations3 = withoutRepetitions(['A', 'B', 'C'])
expect(permutations3.length).toBe(factorial(3))
expect(permutations3).toEqual([
['A', 'B', 'C'],
['B', 'A', 'C'],
['B', 'C', 'A'],
['A', 'C', 'B'],
['C', 'A', 'B'],
['C', 'B', 'A'],
])
const permutations4 = withoutRepetitions(['A', 'B', 'C', 'D'])
expect(permutations4.length).toBe(factorial(4))
expect(permutations4).toEqual([
['A', 'B', 'C', 'D'],
['B', 'A', 'C', 'D'],
['B', 'C', 'A', 'D'],
['B', 'C', 'D', 'A'],
['A', 'C', 'B', 'D'],
['C', 'A', 'B', 'D'],
['C', 'B', 'A', 'D'],
['C', 'B', 'D', 'A'],
['A', 'C', 'D', 'B'],
['C', 'A', 'D', 'B'],
['C', 'D', 'A', 'B'],
['C', 'D', 'B', 'A'],
['A', 'B', 'D', 'C'],
['B', 'A', 'D', 'C'],
['B', 'D', 'A', 'C'],
['B', 'D', 'C', 'A'],
['A', 'D', 'B', 'C'],
['D', 'A', 'B', 'C'],
['D', 'B', 'A', 'C'],
['D', 'B', 'C', 'A'],
['A', 'D', 'C', 'B'],
['D', 'A', 'C', 'B'],
['D', 'C', 'A', 'B'],
['D', 'C', 'B', 'A'],
])
const permutations5 = withoutRepetitions(['A', 'B', 'C', 'D', 'E', 'F'])
expect(permutations5.length).toBe(factorial(6))
})
})
Перестановки с повторениями
Когда разрешены дубликаты, мы говорим о перестановках с повторениями.
Примером такой перестановки может быть код замка 333
.
Количество вариантов:
n * n * n ... (r раз) = n^r (экспонента `r`)
Реализация
// algorithms/sets/permutations/with-repetitions.js
export default function withRepetitions(set, length = set.length) {
if (length === 1) {
return set.map((i) => [i])
}
const result = []
const subset = withRepetitions(set, length - 1)
for (const i of set) {
for (const j of subset) {
result.push([i, ...j])
}
}
return result
}
Тестирование
// algorithms/sets/permutations/__tests__/with-repetitions.test.js
import withRepetitions from '../with-repetitions'
describe('withRepetitions', () => {
it('должен переставлять элементы множеств с повторениями', () => {
const permutations1 = withRepetitions(['A'])
expect(permutations1).toEqual([['A']])
const permutations2 = withRepetitions(['A', 'B'])
expect(permutations2).toEqual([
['A', 'A'],
['A', 'B'],
['B', 'A'],
['B', 'B'],
])
const permutations3 = withRepetitions(['A', 'B', 'C'])
expect(permutations3).toEqual([
['A', 'A', 'A'],
['A', 'A', 'B'],
['A', 'A', 'C'],
['A', 'B', 'A'],
['A', 'B', 'B'],
['A', 'B', 'C'],
['A', 'C', 'A'],
['A', 'C', 'B'],
['A', 'C', 'C'],
['B', 'A', 'A'],
['B', 'A', 'B'],
['B', 'A', 'C'],
['B', 'B', 'A'],
['B', 'B', 'B'],
['B', 'B', 'C'],
['B', 'C', 'A'],
['B', 'C', 'B'],
['B', 'C', 'C'],
['C', 'A', 'A'],
['C', 'A', 'B'],
['C', 'A', 'C'],
['C', 'B', 'A'],
['C', 'B', 'B'],
['C', 'B', 'C'],
['C', 'C', 'A'],
['C', 'C', 'B'],
['C', 'C', 'C'],
])
const permutations4 = withRepetitions(['A', 'B', 'C', 'D'])
expect(permutations4.length).toBe(4 * 4 * 4 * 4)
})
})
Запускаем тесты:
npm run test ./algorithms/sets/permutations
Результат:

Комбинации
Комбинации без повторений
Так работают лотереи. Числа пишутся одно за другим. Побеждает счастливая комбинация, независимо от порядка чисел.
Комбинация чисел без повторений:
[2, 14, 15, 27, 30, 33]
Количество вариантов:

Здесь n
- количество вариантов для выбора, а r
- выбранное нами количество, без повторений, порядок не важен.
Такая комбинация также называется биномиальным коэффициентом.
Реализация
// algorithms/sets/combinations/without-repetitions.js
export default function withoutRepetitions(set, length) {
if (length === 1) {
return set.map((i) => [i])
}
const result = []
set.forEach((i, idx) => {
const subset = withoutRepetitions(set.slice(idx + 1), length - 1)
for (const j of subset) {
result.push([i, ...j])
}
})
return result
}
Тестирование
// algorithms/sets/combinations/__tests__/without-repetitions.test.js
import combineWithoutRepetitions from '../without-repetitions'
import factorial from '../../../math/factorial'
describe('combineWithoutRepetitions', () => {
it('должен комбинировать элементы множеств без повторов', () => {
expect(combineWithoutRepetitions(['A', 'B'], 3)).toEqual([])
expect(combineWithoutRepetitions(['A', 'B'], 1)).toEqual([['A'], ['B']])
expect(combineWithoutRepetitions(['A'], 1)).toEqual([['A']])
expect(combineWithoutRepetitions(['A', 'B'], 2)).toEqual([['A', 'B']])
expect(combineWithoutRepetitions(['A', 'B', 'C'], 2)).toEqual([
['A', 'B'],
['A', 'C'],
['B', 'C'],
])
expect(combineWithoutRepetitions(['A', 'B', 'C'], 3)).toEqual([
['A', 'B', 'C'],
])
expect(combineWithoutRepetitions(['A', 'B', 'C', 'D'], 3)).toEqual([
['A', 'B', 'C'],
['A', 'B', 'D'],
['A', 'C', 'D'],
['B', 'C', 'D'],
])
expect(combineWithoutRepetitions(['A', 'B', 'C', 'D', 'E'], 3)).toEqual([
['A', 'B', 'C'],
['A', 'B', 'D'],
['A', 'B', 'E'],
['A', 'C', 'D'],
['A', 'C', 'E'],
['A', 'D', 'E'],
['B', 'C', 'D'],
['B', 'C', 'E'],
['B', 'D', 'E'],
['C', 'D', 'E'],
])
const combinationOptions = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']
const combinationSlotsNumber = 4
const combinations = combineWithoutRepetitions(
combinationOptions,
combinationSlotsNumber,
)
const n = combinationOptions.length
const r = combinationSlotsNumber
const expectedNumberOfCombinations =
factorial(n) / (factorial(r) * factorial(n - r))
expect(combinations.length).toBe(expectedNumberOfCombinations)
})
})
Комбинации с повторениями
Примером такой комбинации являются монеты в кармане: (5, 5, 5, 10, 10)
.
Еще один пример - пять вкусов мороженного: banana
, chocolate
, lemon
, strawberry
и vanilla
.
Мы можем выбрать 3. Сколько у нас вариантов?
Используем символы для вкусов - { b, c, l, s, v }
. Примеры вариантов:
{ c, c, c }
{ b, l, v }
{ b, v, v }
Количество вариантов:

Здесь n
- количество вариантов для выбора, дубликаты разрешены, порядок не важен.
Реализация
// algorithms/sets/combinations/with-repetitions.js
export default function withRepetitions(set, length) {
if (length === 1) {
return set.map((i) => [i])
}
const result = []
set.forEach((i, idx) => {
const subset = withRepetitions(set.slice(idx), length - 1)
for (const j of subset) {
result.push([i, ...j])
}
})
return result
}
Тестирование
// algorithms/sets/combinations/__tests__/with-repetitions.test.js
import withRepetitions from '../with-repetitions'
import factorial from '../../../math/factorial'
describe('withRepetitions', () => {
it('должен комбинировать элементы множеств с повторами', () => {
expect(withRepetitions(['A'], 1)).toEqual([['A']])
expect(withRepetitions(['A', 'B'], 1)).toEqual([['A'], ['B']])
expect(withRepetitions(['A', 'B'], 2)).toEqual([
['A', 'A'],
['A', 'B'],
['B', 'B'],
])
expect(withRepetitions(['A', 'B'], 3)).toEqual([
['A', 'A', 'A'],
['A', 'A', 'B'],
['A', 'B', 'B'],
['B', 'B', 'B'],
])
expect(withRepetitions(['A', 'B', 'C'], 2)).toEqual([
['A', 'A'],
['A', 'B'],
['A', 'C'],
['B', 'B'],
['B', 'C'],
['C', 'C'],
])
expect(withRepetitions(['A', 'B', 'C'], 3)).toEqual([
['A', 'A', 'A'],
['A', 'A', 'B'],
['A', 'A', 'C'],
['A', 'B', 'B'],
['A', 'B', 'C'],
['A', 'C', 'C'],
['B', 'B', 'B'],
['B', 'B', 'C'],
['B', 'C', 'C'],
['C', 'C', 'C'],
])
const combinationOptions = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']
const combinationSlotsNumber = 4
const combinations = withRepetitions(
combinationOptions,
combinationSlotsNumber,
)
const n = combinationOptions.length
const r = combinationSlotsNumber
const expectedNumberOfCombinations =
factorial(r + n - 1) / (factorial(r) * factorial(n - 1))
expect(combinations.length).toBe(expectedNumberOfCombinations)
})
})
Запускаем тесты:
npm run test ./algorithms/sets/combinations

Шпаргалки



