- •Анализ алгоритмов (Лекции 2000)
- •Глава 1. Модели вычислений.
- •Глава 3. Перестановки как абстрактный тип данных
- •3.1 Представление перестановок в естественной форме.
- •Var a : array [1..N] of Boolean;
- •Var I, j, k, m:1..N; a:array[1..N] of Boolean;
- •If a[I] then
- •Var I,j:1..N; s:boolean:
- •Var I,j,k:1..N: a:array[1..N] of boolean;
- •If a[I] then
- •3.2 Представление перестановок в циклической форме.
- •Procedure trsl(var f: tpe; var g: tpc);
- •Var I : 1..N; s : Boolean;
- •If a[I] then
- •Procedure u1(var f : tpe; var g : tpk);
- •Var r : real;
- •3.3 Представление перестановок в виде таблицы инверсий.
- •Var I : 0..N; s : Boolean;
- •Var I, j, k : integer;
- •Var c : array [0..N] of 0..1;
- •X : array [1..N] of real;
- •Function ч3 (var r :tpi) : Boolean;
- •Var k, I, j, s, : integer;
- •3.4 Задача о складывании карт.
- •Var I,a,b : integer;
- •Var z:integer;
- •Глава 4. Генерация перестановок
- •4.1Генерация перестановок в лексикографическом порядке.
- •Var р : array [0..N] of 0..N; { текущая перестановка}
- •Var p : array [1..N] of 1..N;
- •Генерация перестановок за минимальное число транспозиций
- •Var I,k : integer;
- •Var p:array [0..N1] of 1..N1;
- •I,j,k,t: integer;
- •Глава 5. Генерация подмножеств множества
- •5.1Генерация подмножеств в лексикографическом порядке.
- •5.2 Генерация подмножеств за счет их минимального изменения.
- •Var s : array [1..N] of 0..1;
- •I : integer;
- •Var s : array [1..N] of 0..1;
- •I,j,k,p : integer;
- •Var t : array [0..N] of 1..N1; {стек}
- •Var t : array [0..N1] of 1..N2;
- •I,p : integer;
- •5.3Генерация мультимножеств.
- •Глава 6. Генерация k-подмножеств
- •Var s : array[1..K] of 1..N;
- •I,p : integer;
- •6.1Генерация k-подмножеств заменой одного элемента.
- •Var I : integer;
- •Var I,m,h:integer;
- •Упражнение. Выполните приведенный алгоритм для деревьв
- •В режиме неполного вычисления
- •Глава 8 Теорема о сложности рекурсивных программ
- •Глава 9 Производящие функции
Var р : array [0..N] of 0..N; { текущая перестановка}
k : 0..n; j,r,m : 1..n;
begin
for k:=0 to n do p[k]:=k; {задание начальной перестановки}
k:=1;
while k<> 0 do
begin
for k:=1 to n do write(p[k]); writeln; {вывод перестановки}
k:=n-1; while p[k]>p[k+1] do k:=k-1; {поиcк k}
j:=n; while p[k]>p[j] do j:=j-1; {поиск j}
r:=p[k]; p[k]:=p[j]; p[j]:=r; {транспозиция рк и pj }
j:=n; m:= k+1;
while j>m do {инвертирование хвоста перестановки}
begin r:=p[j]; p[j]:=p[m]; p[m]:=r; j:=j-1; m:=m+1 end
end
end.
Комментарий. Нулевой элемент включен в массив р для того, чтобы обеспечить конец цикла {поиск k} после генерации последней перестановки.
Упражнение. Протестируйте приведенную выше программу при n=3.
Оценим временную вычислительную сложность приведенной программы. Обычно временная вычислительная сложность программ, представленных на языке высокого уровня, оценивается как порядок роста числа исполняемых операторов программы в зависимости от некоторого параметра исходных данных [3]. Однако, в алгоритмах генерации перестановок такой подход малоэффективен, так как в процессе работы любого алгоритма генерации всех перестановок порождается n! перестановок, т. е. временная вычислительная сложность всегда будет, по крайней мере, O(n!) - величина слишком быстро растущая. Любая ‘экономия’ в реализации будет сказываться только на коэффициенте пропорциональности при n!. Поэтому для того, чтобы удобнее было сравнивать различные алгоритмы генерации перестановок, обычно вводят другие критерии оценки вычислительной сложности. Здесь разумно ввести два критерия - количество транспозиций элементов перестановки, выполняемых в среднем при генерации одной перестановки, и аналогичное среднее числа сравнений элементов перестановки в операторах {поиск k} и {поиск j}.
Оценим их число. Пусть Tk - число транспозиций, выполняемых при вызове оператора LEC(n-k+1), т. е. Tk –число транспозиций, которые необходимо выполнить при генерации перестановок k-го порядка. Имеем
Tk=k*Tk-1+(k-1)*(1+(k-1)/2 )=
k*Tk-1+(k-1)*(k+1)/2, где = ‘целой части числа ’.
Первое слагаемое определяет число транспозиций при вызовах оператора LEC(n-k), а второе - число транспозиций, выполняемых в операторах {3} и {4}. Заметим, что T1=0.
Для решения этого рекуррентного уравнения сделаем замену переменной. Пусть Sk=Tk+(k+1)/2, тогда
S1=1, Sk=k*(Sk-1+k-1),
где k=0, если k нечетно, и k=1, если k четно.
Решение этого рекуррентного соотношения легко получается по индукции:
Sk=,
т. е.
Tk= - (k+1)/2.
Учитывая, что ch(1)1.543 и (k+1)/2(k!). получаем
Tkk!*ch(1),
т. е. на генерирование одной перестановки в лексикографическом порядке требуется в среднем ch(1)1.543 транспозиций.
Перейдем теперь к оценке числа сравнений элементов перестановки в операторах {поиск k} и {поиск j}; обозначим это число Сn.
Определим Сn как функцию от Сn-1. Отметим, что при генерации каждого из n блоков, определенных в Л2, требуется Cn-1 сравнений, а таких блоков n. Далее, при переходе от одного блока к другому оператор {поиск k} выполняется n-1 раз, а оператор {поиск J} при переходе от блока p к блоку p+1 (1p<n) - p раз, т. е.
Cn= nCn-1+(n-1)(n-1)+1+...+(n-1), C1=0
или
Cn= nCn-1+(n-1)(3n-2)/2 C1=0.
Пусть Dn=Cn+(3n+1)/2, тогда D1=2, Dn=nDn-1+3/2,
и по индукции получаем
Dn=n!(+)
или
учитывая, что е=, получаем Dnn!e-1).
Тогда, Cn n!e-1)-(3n+1)/2, учитывая, что (3n+1)/2=о(n!), получаем
Cn/n!(e-1)
Таким образом, на генерирование одной перестановки алгоритмом LEX в среднем выполняется (e-1) 3.077 сравнений.
Замечание. Применение методов рекурсивного программирования не требует выяснения того факта, как выглядит следующая перестановка после текущей в лексикографическом порядке. Легко построить соответствующую рекурсивную программу непосредственно на основе свойств Л1-Л3. Эта рекурсивная программа может быть написана так:
program LEX1 (output);
const n=...; {n порядок перестановок}