- •Лекция № 10
- •1. Рекурсивные подпрограммы
- •2. Косвенная рекурсия и опережающее описание
- •3. Рекурсивные структуры Список
- •80 25 17
- •4. Примеры решения задач с помощью рекурсии “Ханойская башня”
- •Двумерное множество Кантора
- •“Индийский алгоритм” возведения в степень
- •Вычисление факториала
- •Числа Фибоначчи
- •Контрольные вопросы.
“Индийский алгоритм” возведения в степень
Этот алгоритм вычисления натуральной n-й (n>1) степени целого числа х выглядит совсем просто:
при n=1 xn=x;
при n>1 xn= xn mod 2(xn div 2)2.
Основная цель этого алгоритма – сократить количество умножений при возведении в степень. Например, по этому алгоритму х5=х*(х2)2, т.е. достаточно трёх умножений вместо четырёх: х*х*х*х*х. Одно умножение экономится за счёт того, что х2 хранится как промежуточное значение и умножается само на себя. Точно так же х10=1*(х5)2=(х5)2, что требует лишь четырёх умножений (три из них для вычисления х5) вместо девяти “лобовых”. Но здесь придётся хранить сначала х2, а потом х5.
Очевидно, что вычисление хn сводится к вычислению хn div 2, запоминанию результата, возведению в квадрат и умножению его на х при нечётном n. Итак, вычисление xn описывается рекурсивной функцией:
function pow(x,n:integer):integer;
var t:integer;
begin
if odd(n) then t:=x else t:=1;
if n=1 then pow:=x else pow:=t*sqr(pow(x,n div 2))
end;
Как видим, промежуточные сомножители хранятся в локальной памяти процессов выполнения вызовов функции, а именно, в переменных, поставленных в соответствие её имени.
Теперь опишем зависимость глубины рекурсии вызовов функции от значения аргумента. В каждом следующем вложенном вызове значение аргумента n меньше предыдущего значения, по крайней мере, вдвое. Поскольку при n=1 происходит возвращение из вызова, то таких уменьшений значения аргумента n не может быть больше чем log2n. Следовательно, глубина рекурсии вызова с аргументом n не превышает log2n.
Такую глубину можно считать хорошим свойством алгоритма. При каждом выполнении вызова происходит не более одного деления, возведения в квадрат и умножения, поэтому общее количество арифметических операций не больше 3log2n. При больших значениях n это существенно меньше “лобовых” n-1 умножений. Например, при n=1000 это примерно 30.
Отметим, что при некоторых значениях n приведённый алгоритм не даёт наименьшего количества умножений, необходимых для вычисления n-й степени. Например, при n=15 по этому алгоритму необходимо выполнить 6 умножений, хотя можно с помощью 3-х умножений вычислить х5, после чего помножить его на себя дважды (всего 5 умножений). Однако написать алгоритм, который задаёт вычисление произвольной степени с минимальным количеством умножений, – не совсем простая задача.
Построим нерекурсивный аналог приведённого алгоритма. Представим вычисление по рекурсивному алгоритму в таком виде:
х13=(х6)2*х1=((х3)2*х0)2*х1=(((х1)2*х1)2*х0)2*х1
Этому соответствует следующая обработка вычисляемых показателей степеней:
13=6*2+1=(3*2+0)*2+1=((1*2+1)*2+0)*2+1
Как видим, вычислению степеней соответствует вычисление значения 13, представленного полиномом относительно 2. Коэффициентами его являются цифры двоичного разложения числа 13. Нетрудно убедится, что вычислению степени с произвольным показателем n точно так же соответствует вычисление числа n, представленного двоичным разложением. Причём это разложение-полином записано по схеме Горнера. Раскроем в нём скобки:
1*23+1*22+0*21+1*20
Коэффициент при 20, 21, 22 и т.д. – это последовательные остатки от деления на 2 чисел:
n, n div 2, (n div 2) div 2 и т.д.
причём остатку 1 соответствует в рекурсивном алгоритме присваивание t:=x, а 0 – присваивание t:=1. Таким образом, двоичное разложение, например, числа 13 по степеням двойки соответствует такому представлению х13:
х2^3*x2^2*1*x2^0
Итак, достаточно вычислять степени:
x2^0= x, x2^1= x2, x2^2= (x2)2, x2^3= (x2^2)2 и т.д.
и соответствующие им остатки от деления на 2 показателей:
n, n div 2, (n div 2) div 2, ((n div 2) div 2) div 2 и т.д.
накапливая в произведении лишь те степени двойки, которые соответствуют остаткам 1. В следующем алгоритме произведение степеней накапливается в переменной t, а степени двойки – в переменной х:
function pow(x,n:integer):integer;
var t:integer; notfin:boolean;
begin
t:=1; notfin:=true;
while notfin do
begin
if odd(n) then t:=t*x;
n:=n div 2;
if n>0 then x:=x*x else notfin:=false
end;
pow:=t
end.