Перебор 0-1 векторов
Для удобства, рассмотрим в качестве исследуемого комбинаторного объекта множество Bm наборов из m нулей и единиц. Для этого множества дадим ответы на поставленные вопросы.
Понятно, что множестве наборов из m нулей и единиц содержит ровно 2m элементов.
Для того, чтобы создать вычислительный процесс, при котором на каждом шаге будет формироваться новый, не встречавшийся ранее, элемент рассматриваемого множества, достаточно заметить, что существует взаимнооднозначное соответствие между числами из 0…2
m
1 и наборами 0-1 векторов. Т. е. достаточно первым взять число 0 и его двоичное представление 0, …, 0, а затем просто добавлять по единице, имитируя это на текущем наборе, пока мы не дойдем до набора из одних единиц.
Кроме рассмотренного способа перебора наборов, можно предложить другой
алгоритм, который на каждом шаге меняет значение только одной компоненты. Этот алгоритм основан на идее рекурсии.
Фиксируем нулевое значение m-й компоненты и перебираем все наборы длины m

1 для оставшихся компонент.
Меняем значение m-й компоненты с 0 на 1. Перебираем наборы длины m

1 в обратном порядке.
Приведем таблицу, показывающую процесс перебора наборов длины 4. Столбец it показывает номер текущей
итерации, а столбец k
it — номер компоненты, которая подлежит обновлению.
Также легко можно дать ответ на вопрос о том, как перенумеровать наборы множества. Действительно, текущий набор множества — это двоичное представление некоторого числа, которое и является номером нашего набора. Таким образом, мы каждому набору можем сопоставить его численный эквивалент.
Коды Грея
Ранее мы рассматривали рекурсивный алгоритм перебора 0-1 наборов. Этот алгоритм позволяет перебирать все наборы Bm так, чтобы каждый следующий набор отличался от предыдущего только в одном разряде. Построенная с помощью этого алгоритма последовательность наборов (приводится в таблице) называется кодом Грея. Вообще, n-разрядный код Грея — это упорядоченная (возможно, циклическая) последовательность, состоящая из 2n n-разрядных кодовых слов, каждое из которых отличается от соседнего в одном разряде.
Приведем еще один (на этот раз нерекуррентный) алгоритм генерации кодов Грея. Будем рассматривать бинарные коды Грея порядка n. Итак, на вход алгоритма подается единственное число n, которое указывает порядок кода Грея. По ходу выполнения алгоритма мы получим последовательность всех подмножеств n-элементного множества, в которой каждое последующее подмножество получается из предыдущего добавлением или удалением единственного элемента (наименьшим возможным изменением) — код Грея. При этом каждое подмножество будет представляться бинарной последовательностью B[1], …, B[n].
Пример 1
Gray-Generation(n)
1 for i := 1 to n do B[i] := 0;
2 i := 0;
3 repeat
4 write (B[i], …, B[n]);
5 i := i + 1; p := 1; j := i;
6 while j mod 2 = 0 do
7 begin
8 j := j/2; p := p + 1;
9 end;
10 if p

n then B[p] := 1

B[p];
11 until p > n
Последовательность полученных подмножеств можно проиллюстрировать на
графе (n-мерном кубе),
вершины которого соответствуют бинарным последовательностям длины n и две вершины которого соединены
ребром, если соответствующие последовательности отличаются лишь в одной позиции. Тогда эта последовательность соответствует гамильтонову пути в этом графе, т. е. пути, содержащему каждую вершину графа только один раз.
Перебор элементов прямого произведения множеств
Если мы рассматриваем два каких-либо множества A и B, то будем называть их прямым (декартовым) произведением (A×B) множеств A и B, множество всевозможных упорядоченных пар (i, j), где i

A, j

B. Мощность же множества, получившегося при декартовом произведении множеств, будет равна произведению мощностей этих множеств.
Более того, мы можем определить прямое произведение любого конечного числа множеств. Например, если рассматривать экземпляры только одного множества, состоящего из двух элементов 0 и 1, то прямое произведение m таких экземпляров будет представлять собой ранее расcмотренное множество Bm.
Итак, рассмотрим прямое произведение k множеств: M1×M2×…×Mk. Очевидно, что
|M
1×M
2×…×M
k| =

m
i,
где mi = |Mi| — количество элементов множества Mi.
Пусть множество M
i состоит из целых чисел от 0 до m
i
1, i

1:k. Тогда каждый элемент прямого произведения M
1×M
2×…×M
k можно представить как последовательность неотрицательных чисел r
1, r
2, …, r
k, где 0

r
i < m
i.
Также мы можем ввести систему счисления с основанием, которое для разных разрядов может быть различным. Примерами системы счисления с переменным основанием являются измерение времени, недесятичные меры для расстояний и весов, размещение в памяти многомерных
массивов. Тогда представим формулу для перехода от элемента прямого произведения к его номеру, как:
num(r1, r2, …, rk) = ∑i=1:k ri × ∏j=1:i−1 mj.
Перестановки
Перестановка n-элементного множества X — это взаимно однозначная функция f: X

X. Если для простоты принять X = {1, …, n}, то перестановкой можно назвать упорядоченный набор из n различных чисел, лежащих в промежутке от 1 до n (далее будет рассматриваться именно этот случай).
Обозначим множество всех перестановок множества X через S
n. Понятно, что |S
n| = n!, т. к. на первое место набора можно поставить любое число из n возможных, на второе — любое из n

1 оставшихся и т. д. до последней позиции, в которую мы помещаем последний оставшийся элемент: в результате, мы получим произведение, которое и будет являться
факториалом n.
Рассмотрим множество M
i из предыдущей части. Пусть каждое M
i будет представлять собой множество чисел от 0 до i

1. Обозначим через T
n произведение n таких множеств. Тогда если будет существовать взаимнооднозначное соответствие между S
n и T
n, то мы сможем, в частности, перенумеровать все перестановки S
n. Предъявим механизм перехода от перестановки r
1, …, r
n 
Sn к элементу t
1, …, t
n 
Tn. Для этого для любого i

1:n найдем номер позиции s значения i в перестановке (r
s = i) и примем в качестве ti количество элементов меньших i среди r
1, …, r
s−1. А при обратном переходе от элемента к перестановке, можно использовать то, что место любого элемента i (начиная с самого большого) на единицу больше, чем число элементов, предшествующих i, а само это число равно сумме t
i и числа элементов, бОльших i.
Кроме того, каждую перестановку f

S
n можно представить с помощью ориентированного графа с множеством вершин X = {1, …, n}, в котором ребро идет от x к у, когда f(x) = y. Легко можно показать, что такой граф состоит из некоторого числа элементарных циклов с различными множествами вершин, которые в сумме дают множество X. Т. е. каждую перестановку мы можем разложить на циклы. Например, рассмотрим перестановку f(1234567) = 7514236 и разобьем ее на циклы:
Генерирование перестановок
Введем некоторые обозначения для записи алгоритмов. Элементы перестановки будем запоминать в виде элементов массива P[1], …, P[n]. Обмен значениями переменных будем обозначать через P[i] :=: P[j]. Напомним, что мы рассматриваем только перестановки, заданные на множестве X = {1, …, n}.
На множестве Sn зададим лексикографический порядок:
(x1, …, xn) < (y1, …, yn)

существует k

1, такое что x
k 
y
k и x
l = y
l для каждого l < k.
Аналогично задается антилексикографический порядок:
(x
1, …, x
n) < (y
1, …, y
n)

существует k

n, такое что x
k > y
k и x
l = y
l для каждого l > k.
Теперь представим алгоритм генерации всех перестановок в антилексикографическом порядке. Заметим, что этот алгоритм является рекурсивным.
Пример 2
1 procedure REVERSE(m);
2 begin i := 1; j := m;
3 while i < j do
4 begin P[i] :=: P[j]; i := i + 1; j := j - 1;
5 end
6 end
7 procedure ANTYLEX(m);
8 begin
9 if m = 1 then
10 write(P[1], …, P[n])
11 else
12 for i := 1 to m do
13 begin ANTYLEX(m

1);
14 if i < m m then
15 begin P[i] :=: P[m]; REVERSE(m

1)
16 end
17 end
18 end;
19 begin
20 for i := 1 to n do P[i] :=: i;
21 ANTYLEX(n)
22 end
Среднее число
транспозиций, приходящихся на каждую перестановку, приблизительно равно 1.543, т. е. для порождения n! перестановок используется приблизительно 3.077n! сравнений.
Можно предложить более быстрый алгоритм, в котором каждая следующая перестановка образуется из предыдущей с помощью только одной транспозиции. Приведем алгоритм генерации перестановок за минимальное число транспозиций:
Пример 3
1 procedure B(m, i);
2 begin
3 if (m mod 2 = 0) and (m > 2) then
4 if i < m

1 then B := i
5 else B := m

2
6 else B := m

1
7 end;
8 procedure PERM(m);
9 begin
10 if m = 1 then
11 write(P[1], …, P[n])
12 else
13 for i := 1 to m do
14 begin PERM(m

1);
15 if i < m then P[B(m, i)] :=: P[m]
16 end
17 end;
18 begin
19 for i := 1 to n do P[i] :=: i;
20 PERM(n)
21 end
Однако, существует еще более быстрый алгоритм генерирования перестановок, он строит последовательность перестановок, в которой каждая следующая образуется из предыдущей с помощью однократной транспозиции соседних элементов. Обозначим через PR[i] булеву переменную, содержащую информацию о том, переносится ли элемент i вперед (PR[i] = true) или назад. Переменнная C[i] будет показывать, какую из возможных n

i+1 позиций элемент i занимает относительно элементов i+1, …, n на своем пути вперед и назад. Вот его код:
Пример 4
1 begin
2 for i := 1 to n do
3 begin P[i] := i; C[i] := 1; PR[i] := true;
4 end;
5 C[n] := 0;
6 write(P[1], …, P[n]);
7 i := 1;
8 while i < n do
9 begin i := 1; x := 0;
10 while C[i] = n

i + 1 do
11 begin PR[i] := not PR[i]; C[i] := 1;
12 if PR[i] then x := x + 1;
13 i := i + 1;
14 end;
15 if i < n then
16 begin
17 if PR[i] then k := C[i] + x;
18 else k := n

i + 1

C[i] + x;
19 P[k] :=: P[k + 1];
20 write(P[1], …, P[n]);
21 C[i] := C[i] + 1
22 end
23 end
24 end
Последовательность перестановок, полученная с помощью данного алгоритма имеет интересную
интерпретацию. Так, если рассмотреть граф, вешины которого соответствуют всем перестановкам и в котором две вершины, соответствующие перестановкам f и g, соединены ребром, если g образуется из f однократной транспозицией соседних элементов, то полученная последовательность является гамильтоновым путем в этом графе. На рисунке изображен граф последовательности для n = 3, 4.
Задача о минимуме скалярного произведения
Перстановки часто встречаются в самых различных областях математики и используются в разных задачах. Например, можно рассмотреть неалгебраическое использование перестановки в следующей экстремальной задаче: даны 2m чисел x1, …, xm и y1, …, ym, нужно найти такое разбиение чисел на пары (xi, yj), при котором значение суммы, составленной из произведений чисел каждой пары, будет наименьшим.
Несложно показать, что решение данной задачи достигается при сопоставлении возрастающей последовательности xi и убывающей последовательности yi, т. е. последовательностей, упорядоченных в противоположном порядке.
Размещения и сочетания
Размещение из n элементов по m — это упорядоченный (здесь нам важен порядок) набор из m различных чисел, лежащих в промежутке от 1 до n (т. е.

X). Другими словами
размещение — это множество с повторениями. Обозначим число его элементов через A
n,m.
Для того, чтобы вычислить значение An,m, нужно следовать тем же рассуждениям, что и в случае с перестановками. Тогда получим следующий результат:
A
n,m =
Перебор и нумерация размещений в целом схожи с соответствующими действиями с перестановками. Рассмотрение этих вопросов предлагается читателю в качестве полезного упражнения.
Сочетание из n элементов по m — это неупорядоченный набор (множество) из m различных чисел, принадлежащих множеству X. Обозначим число элементов множества сочетаний через Cn,m.
Т. к.
сочетание является неупорядоченным набором, то каждому такому набору соответствует m! размещений (т. е. упорядоченных набора тех же элементов). Тогда получаем, что
C
n,m =

Можно привести некоторые свойства сочетаний:
1.Cn,m = Cn,n−m
2.Cn,m + Cn,m+1 = Cn+1,m+1
3.Cn,m * Cm,k = Cn,k * Cn−k,m−k
Эти свойства следуют из самих определений и проверяются непосредственно.
Теперь приведем алгоритм генерации сочетаний в лексикографическом порядке:
Пример 5
1 begin
2 for i := 1 to k do A[i] := i;
3 p := k;
4 while p

1 do
5 begin write(A[1], …, A[k]);
6 if A[k] = n then p := p

1 else p := k;
7 if p

1 then
8 for i := k downto p do A[i] := A[p] + i

p + 1
9 end
10 end
Нумерацию сочетаний можно организовать, используя свойство 2) сочетаний, приведенное выше. Для этого с каждым сочетанием из n по m свяжем вектор из n нулей и единиц. В этом векторе будет m единиц, а числа сочетания будут задавать номера этих единиц. Тогда такому вектору можно, в свою очередь, сопоставить траекторию, в которой единицам соответствуют горизонтальный шаги, а нулям — вертикальные. Тогда формула для номера будет выгляеть, как
num(b[1:n], m) = { если b[n] = 0, то num(b[1:n

1], m); если b[n] = 1, то C
n−1,m + num(b[1:n

1], m

1)},
где b[1:n] — 0-1 вектор из n элементов.
Бином Ньютона
Если в узлах положительного квадранта прямоугольной координатной решетки поместить количество путей от начала координат до этих узлов и повернуть эту решетку так, чтобы начало координат было наверху, то мы получим треугольник Паскаля:
По этому треугольнику удобно считать число сочетаний для небольших параметров. Кроме того, с этой схемой связана формула бинома Ньютона:
(a + b)
n =

C
n,ka
kb
n−k
В этой формуле при раскрытии степени алгебраического двучлена коэффициенты при степенях совпадают с числом сочетаний. Исходя из формулы Ньютона значения Cn,m также еще называют биномиальными коэффициентами.
Разбиения множества
Разбиение n-элементного множества X на k блоков — это семейство Q = {B
1, …, B
k}, в котором B
1 
…

B
k = X, B
i 
B
j = Ø, Bi

Ø, для 1

i < j

k. Обозначим множество всех разбиений множества X на k блоков через Π
k(X), а через Π(X) — множество всех разбиений.
Довольно интересным является вопрос генерирования разбиений множества. Рассмотрим идею алгоритма, отвечающего на этот вопрос, описав ее в рекуррентной форме. Сначала сделаем ряд полезных замечаний. Можно показать, что разбиение Q множества {1, …, n} однозначно определяет разбиение Q
n−1 множества {1, …, n

1}, которое получается из Q после удаления элемента n (или пустого блока) из соответствующего блока. Также, если имеется разбиение W = {B
1, …, B
k} множества {1, …, n

1}, то можно отыскать все разбиения Q множества {1, …, n}, для которых Q
n−1 = W, т. е. такие разбиения:
B
1 
{n}, …, B
k,
…
B
1, …, B
k 
{n},
B1, …, Bk, {n}.
Обозначим такую последовательность через E. Тогда сформулируем несложный метод генерирования рабиений: если у нас есть список L
n−1 всех разбиений множества {1, …, n

1}, то список L
n разбиений множества {1, …, n} будем создавать, заменяя каждое разбиение Q в списке L
n−1 на соответствующую ему последовательность E. Проиллюстрируем построение списка L
n для n = 1, 2, 3.
Числа Стирлинга второго и первого рода
Число Стирлинга второго рода S(n, k) — это число разбиений n-элементного множества на k блоков, т. е.
S(n, k) = |Πk(X)|,
где множество X состоит из n элементов. Например, S(4, 2) = 7.
Любопытны некоторые тождества, связанные с числами Стирлинга второго рода:
S(n, n) = 1, для n

0,
S(n, 0) = 0, для n > 0,
S(n, k) = S(n

1, k

1) + kS(n

1, k), для 0 < k < n
x
n =

S(n, k)[x]
k, для n

0, где [x]
k = x(x

1)…(x

k+1).
Первые две формулы очевидны. Доказательство последних формул предлагается читателю в качестве несложного упражнения.
Число Белла Bn — это число всех разбиений n-элементного множества:
Bn = |Π(X)|, где |X| = n.
Рассмотрим несложное рекуррентное соотношение:
B
n+1 =

C
i,nB
i
Для доказательства достаточно заметить, что множество всех разбиений множества X = {1, …, n+1} можно разделить на различные классы в зависимости от блока B, который содержит элемент n+1, или в зависимости от множества X \ B. Далее для каждого множества X \ B из {1, …, n} имеется |Π(X \ B)| = B|X\B| разбиений множества X, которое содержит B в качестве блока. Группируя классы в зависимости от мощности множества X \ B, приходим к требуемому.
Числа Стирлинга первого рода s(n, k) — это коэффициенты при последовательных степенях переменной x в многочлене [x]k:
[x]
n =

s(n, k)x
k.
Для них определяются аналогичные рекуррентные соотношения, что и для чисел второго рода:
s(n, n) = 1, для n

0,
s(n, 0) = 0, для n > 0,
s(n, k) = s(n

1, k

1) + (n

1)s(n

1, k), для 0 < k < n.
Разбиения чисел
Разбиение числа (натурального) n на k слагаемых — это последовательность a1, …, ak, такая что
n = a
1 + … + a
k, a
1 
…

a
k > 0.
Существует довольно простой способ представления разбиения числа — диаграмма Феррерса. Для разбиения a1, …, ak она состоит из k строк, которые соответствуют слагаемым разбиения, где i-я строка состоит из ai точек. Сопряженное разбиение — это разбиение, получающееся при перемене ролями строк и столбцов в диаграмме Феррерса.
Заметим, что с помощью диаграммы Феррерса можно доказать много интересных свойств числа всех разбиений рассматриваемого числа.
Рассмотрим теперь несложный алгоритм генерирования всех разбиений числа. Обозначим через S[1], …, S[d] само разбиение числа, а через R[1], …, R[d] — информацию о том, сколько раз слагаемое S[i] появляется в разбиении.
Пример 6
1 begin
2 S[1] := n; R[1] := 1; d := 1;
3 write();
4 while S[1] > 1 do
5 begin sum := 0;
6 if S[d] = 1 then
7 begin sum := sum + R[d]; d := d

1;
8 end;
9 sum := sum + S[d]; R[d] := R[d]

1; l := S[d]

1;
10 if R[d] > 0 then d := d + 1;
11 S[d] := l; R[d] := sum div l;
12 l := sum mod l;
13 if l

0 then
14 begin d := d + 1; S[d] := l; R[d] := 1
15 end;
16 write();
17 end
18 end
Производящие функции
Производящая функция — это функция, которая позволяет определить явный вид общего члена рассматриваемой числовой последовательности. Так, пусть имеется последовательность {a
n}, для главного члена a которой нужно найти общую формулу. Тогда введем функцию A(x) =

a
nx
n. Если, используя свойства рассматриваемой последовательности, удастся решить составленные для A(x) уравнения, то можно будет получить искомые элементы последовательности.
Один из примеров применения производящих функций относится к числам Фибоначчи. Они являются решением рекуррентного уравнения Fn+1 = Fn + Fn−1, где F0 = F1 = 1.
Рассмотрим производящую функцию F(x) =

f
nx
n. Она удовлетворяет уравнению F(x) = 1 + x +

(F
k−2 + F
k−1)x
k = … = 1 + (x + x
2)F(x). Отсюда получаем, что F(x) = (1

x

x
2)
−1. Далее находим разложение 1

x

x
2 = (1

ax)(1

bx), где a = (1 +

5) ⁄ 2, b = (1

5) ⁄ 2. Далее, используя
метод неопределенных коэффициентов, получаем F(x) =

a
k+1
b
k+1)x
k ⁄ (a

b).
Интересно, что предел отношения fn+1 ⁄ fn равен a, т. е. «золотому сечению». Также существует связь между числами Фибоначчи и треугольником Паскаля: можно выбрать линии, пересекающие узлы треугольника, так, чтобы сумма всех чисел на одной линии соответствовала числу Фибоначчи.
Другим примером являются
числа Каталана. Они появляются в контексте следующей задачи: нужно найти число различных последовательных действий, чтобы вычислить сумму S
0, …, S
n, складывая любые два рядом стоящих числа и результат помещая на их место. Тогда если обозначить искомое число через с
n, то производящая функция будет выглядеть, как С(x) =

c
nx
n. Следуя аналогичным рассуждениям, как и в случае с числами Фибоначчи, получим, что С(x) =

C
2n,nx
n ⁄ (n+1).
Принцип включения и исключения
Для общего представления приведем иллюстрацию для трех множеств A, B и C:
Теорема
|

A
i| =

|A
i|

|A
i 
A
j| +

|A
i 
A
j 
A
k|

… + (

1)
n−1|A
1 
…

A
n|.
Доказательство. Будем доказывать теорему по индукции. База индукции очевидно верна. Тогда пусть для A
1, …, A
n−1 выполняется |

A
i| =

|A
i|

|A
i 
A
j| +

|A
i 
A
j 
A
k|

… + (

1)
n−2|A
1 
…

A
n−1|. Если мы применим эту формулу к сумме (A
1 
…

A
n−1)

A
n =

(A
i 
A
n), то получим |

A
i 
A
n| =

|A
i 
A
n|

|A
i 
A
j 
A
n| +

|A
i 
A
j 
A
k 
A
n|

… + (

1)
n−2|A
1 
…

A
n−1 
A
n|. Отсюда уже получаем |

A
i| = |(

A
i)

A
n| = |

A
i| + |A
n|

|

A
i 
A
n| =

|A
i|

|A
i 
A
j| +

|A
i 
A
j 
A
k|

… + (

1)
n−1|A
1 
…

A
n|.