- •Анализ алгоритмов (Лекции 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 Производящие функции
Глава 8 Теорема о сложности рекурсивных программ
Теорема. Пусть a, b, c неотрицательные постоянные. Решение рекуррентного уравнения
,
где n – степень числа c, имеет вид:
Доказательство.
Если n – степень числа c, то T(n) =, где .
Если a<c, то сходиться и, следовательно, T(n) = O(n).
Если a=c, то каждый член этого ряда равен единице, а всего в нем О(log n) членов. Поэтому T(n) = O(nlog n).
Если a>c, то , что составляет , или .
Из теоремы вытекает, что разбиение задачи размера n ( за линейное время) на две подзадачи размера n/2 дает алгоритм сложности O(nlog n).
Из теоремы вытекает, что разбиение задач размера n (за линейное время) на две подзадачи размера n/2 дает алгоритм сложности O(n log n). Если бы подзадач было 3,4,8, то получился бы алгоритм сложности nlog3,n2, n3 соответственно.
С другой стороны, разбиение задачи на 4 подзадачи размера n/4 дает алгоритм сложности O(n log n), а на 9 и 16 – порядка nlog3, n2 соответственно. Поэтому асимптотически более быстрый алгоритм умножения целых чисел (см. ниже) можно было бы получить, если бы удалось так разбить исходные целые числа на 4 части, чтобы суметь выразить исходное умножение через 8 или менее умножений. Другой тип рекуррентных соотношений возникает в случае, когда работа по разбиению задачи не пропорциональна ее размеру.
Если n не является степенью числа c, то ,обычно, можно вложить задачу размера n в задачу размера n’, где n’ – наименьшая степень с, большая или равная n. Поэтому порядки роста, указанные в теореме, сохраняются для любого n. на практике часто можно разработать рекурсивные алгоритмы, разбивающие задачи произвольного размера на c равных частей, где с велико, насколько возможно. Эти алгоритмы, как правило, эффективней (на постоянный множитель) тех, которые получаются путем представления размера входа в виде ближайшей сверху степени числа c.
Пример 1. Нахождение наибольшего и наименьшего элементов.
Вход: множество S из n элементов, где n степень числа 2, n2.
Выход: наибольший и наименьший элементы множества S.
Метод: К множеству S применяется рекурсивная процедура MAXMIN. Она имеет один аргумент, представляющий собой множество S, такое, что |S|=2k при некотором k1, и вырабатывает пару (а, b), где а – наибольший и b – наименьший элемент в S.
Procedure MAXMIN(S);
-
if |S|=2 then
begin
-
пусть S={a,b};
-
return (MAX(a,b),MIN(a,b))
end
else
begin
-
Разбить S на два равных полмножества S1 и S2;
-
(MAX1,MIN1):=MAXMIN(S1);
-
(MAX2,MIN2):=MAXMIN(S2);
-
return (MAX(MAX1,MAX2),MIN((MIN1,MIN2)
end.
Заметим, что сравнение элементов множества S происходит только на шаге 3, где сравниваются два элемента множества S, из которых оно состоит, и на шаге 7, где сравниваются MAX1 с MAX2 и MIN1 с MIN2. Пусть T(n) – число сравнений элементов множества S, которые надо произвести в процедуре MAXMIN, чтобы найти наибольший и наименьший элементы n-элементного множества.
Ясно, что Т(2)=1. Если n>2, то T(n) – общее число сравнений, произведенных в двух вызовах процедуры MAXMIN (стоки 5 и6),работающих на множествах размера n/2 и еще два сравнения в строке 7. таким образом
(1)
Решение рекуррентных уравнений (1) служит функция T(n)=n-2. Легко проверить, что эта функция удовлетворяет (1) при n=2, и если она удовлетворяет (1) при n=m, то
T(2m)=2 (-2)+2=(2m)-2,
т.е. она удовлетворяет (1) при n=2m. Таким образом, индукцией доказано, что T(n)= -2 удовлетворяет (1), если n степень 2.
Докажем, что для одновременного нахождения наибольшего и наименьшего элементов n-элементного множества надо сделать не менее -2 сравнений его элементов.
Различные стадии работы такого алгоритма над n-элементным множеством опишем четверкой (a,b,c,d), где a элементов вообще не сравнивались, b элементов участвовали в сравнениях и всегда были большими, c элементов участвовали в сравнениях и всегда были меньшими, d элементов участвовали в сравнениях и были как большими так меньшими. Начальная конфигурация характеризуется – (n,0,0,0); конечная – (0,1,1,n-2). Из конфигурации (a,b,c,d) можно перейти либо в себя, либо в
(a-2, b+1, c+1, d), если а2;
(a-1, b, c+1, d) или (a-1,b+1,c,d), если а1;
(a, b-1, c d+1), если b2;
(a, b, c-1, d+1), если c2.
Отсюда следует, что требуется +b+c-2 сравнений, чтобы из (a, b, c, d) получить (0, 1, 1,a+b+c+d-2).
Пример 2. Умножение двух n-разрядных двоичных чисел.
Традиционный метод требует О(n2) , битовых операций. Рассмотрим метод, в котором достаточно битовых операций порядка .
Пусть x и y – два n-разрядных двоичных чисел. Снова будем считать для простоты, что n есть степень 2. Разобьем
y = y =
Если рассматривать каждую из этих частей как (n/2)-разрядные число, то можно представить произведение z=xy в виде
z=(a2n/2+b)(c2n/2+d)=ac2n+(ad+bc)2n/2+bd (1)
Равенство (1) дает способ вычисления произведения xy с помощью четырех умножений (n/2)-разрядных чисел и нескольких сложений и сдвигов (умножение на степень 2). Произведение z чисел x и y также можно вычислить по следующей программе:
begin
u:=(a+b)(c+d); u:=(a+b)(c+d);
v
(2)
w:=bd;
z:= v2n+(u-v-w)2n/2+w
end
На время забудем, что из-за переноса a+b и c+d могут иметь n/2+1 разрядов, и предположим, что они состоят из n/2 разрядов. Наша схема для умножения двух n-разрядных чисел требует только трех умножении (n/2)-разрядных чисел и нескольких сложений и сдвигов. Для вычисления произведений u,v и w можно применить эту программу рекурсивно. Сложение и сдвиги занимают О(n) времени. Следовательно, временная сложность умножения двух n-разрядных чисел ограничена сверху функцией
(3)
где k – постоянная, отражающая сложение и сдвиги в выражениях, входящих в (2).
Решение рекуррентных уравнений (3) ограничено сверху функцией
3k3.
В самом деле, покажем, что T(n)=3k-2kn. Применим индукцию по n.
n=1 – тривиально.
Если функция T(n)=3k-2kn удовлетворяет (3) при n=m, то
T(2m)=3T(m)+2km=3[3k-2km]+2km=3k-2k(2m).
Так, что она удовлетворяет (3) и при n=2m. Отсюда следует, что T(n)3k.
Замечание. Попытка использовать в индукции 3k вместо 3-2кn не проходит.
Для завершения описания алгоритма умножения мы должны учесть, что числа a+b и c+d, вообще говоря, имеют n/2+1 разрядов, и поэтому произведение (a+b)(c+d) нельзя вычислить непосредственно применением нашего алгоритма к задаче размера n/2. вместо этого надо записать a+b в виде а12n/2+b1, где а1 равно 0 или 1. Аналогично запишем c+d с12n/2+d1/ тогда произведение (a+b)(c+d) можно представить в виде
а1c12n+(a1d1+b1 c1)2n/2+b1d1. (4)
Слагаемое b1d1 вычисляется с помощью рекурсивного применения нашего алгоритма умножения к задаче размера n/2. остальные умножения в (4) можно сделать за время О(n), поскольку они содержит в качестве одного из аргументов либо единственный бит а1 или с1, либо степень числа 2.
Упражнение. Решите следующие рекуррентные уравнения, считая, что T(1)=1:
-
T(n)=aT(n-1)+ bn
-
T(n)=aT(n/2)+ bnlog2n
-
T(n)=aT(n-1)+ bnc
-
T(n)=aT(n/2)+ bnc