Коллекция алгоритмов

"Индийский алгоритм" возведения в степень
    Этот алгоритм вычисления натуральной n-й (n > 0) степени целого числа x выглядит совсем просто:
  • при n = 1 xn = x;
  • при n > 1 xn = xn mod 2((xn div 2)2.
    Основная цель этого алгоритма - сократить количество умножений при возведении в степень. Например, по этому алгоритму x5 = x·(x2)2, т.е. достаточно трех умножений вместо четырех: x·x·x·x·x. Одно умножение экономится за счет того, что x2 хранится как промежуточное значение и умножается само на себя. Точно так же x10 = 1·(x5)2 = (x5)2, что требует лишь четырех умножений (три из них для вычисления x5) вместо девяти "лобовых". Но здесь придется хранить сначала x2, а потом x5.
    Очевидно, что вычисление xn сводится к вычислению xn div 2, запоминанию результата, возведению в квадрат и умножению его на x при нечетном 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-х умножений вычислить x5, после чего помножить его на себя дважды (всего 5 умножений). Однако написать алгоритм, который задает вычисление произвольной степени с минимальным количеством умножений, - не совсем простая задача.
    Построим нерекурсивный аналог приведенного алгоритма. Представим вычисление по рекурсивному алгоритму в таком виде:
x13 = (x6)2 · x1 = ((x3)2 · x0)2 · x1 = (((x1)2 · x1)2 · x0)2 · x1
    Этому соответствует следующая обработка вычисляемых показателей степеней:
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 по степеням двойки соответствует такому представлению x13:
x23 · x22 · 1 · x20
    Итак, достаточно вычислять степени:
x20 = x, x21 = x2, x22 = (x2)2, x23 = (x22)2 и так далее
и соответствующие им остатки от деления на 2 показателей:
n, n div 2, (n div 2) div 2, ((n div 2) div 2) div 2 и так далее
накапливая в произведении лишь те степени двойки, которые соответствуют остаткам 1. В следующем алгоритме произведение степеней накапливается в переменной t, а степени двойки - в переменной x:
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;
			
Рейтинг@Mail.ru Rambler's Top100
Сайт управляется системой uCoz