Теоретический материал: алгоритм Карацубы и быстрое преобразование Фурье (А.Климовский)

Дискретное преобразование Фурье

Введение (комплексные числа, комплексная экспонента, корни из единицы и прочие простые сущности из математики)

При обсуждении вопросов преобразования Фурье нам понадобятся некоторые факты. Коротко:

Поле комплексных чисел (само понятие поля вовсе не нужно)

Комплексная экспонента

Корни из единицы (решения уравнения ) ― это множество

Многочлены

Многочлен ― это функция вида

По значениям многочлена в (n+1)-ой различной точке, он восстанавливается однозначно. Например, по формуле (интерполяционный многочлен Лагранжа)

Дискретное преобразование Фурье (формальное определение, терминология)

Определение. Дискретным преобразованием Фурье называется преобразование последовательности в последовательность по формуле:

Примечание. В литературе также встречаются определения, отличающиеся от указанного наличием знака «минус» в экспоненте, а так же коэффициентом перед всем выражением.

Как несложно заметить, это есть значения многочлена в корнях из единицы.

Обратное преобразование

Обратное преобразование, как несложно доказать, записывается в виде

Если прямое преобразование обозначить за (представляя его как оператор, действующий на столбец), то . Можно так же его выразить через перестановки последовательностей, это не сложно.

Алгоритм Cooley-Tukey

Рассмотрим случай, когда . Так как для многочленов это всегда можно сделать, дополнив нулями в конце, не будем останавливаться на других случаях. В теории рассмотрим только прямое преобразование, а при реализации учтем оба варианта.

Основная идея ― разбить сумму на половину слагаемых с четными номерами и половину с нечетными. Построим для них преобразованные последовательности. Обозначим указанные последовательности и для четных номеров, и для нечетных ().

Теперь всё готово для рекурсивной реализации преобразования, это предлагается сделать в качестве домашнего задания. Далее же рассмотрим, как избавиться от рекурсии и получить большую эффективность.

Для этого посмотрим, какие будут рекурсивные вызовы, например, при :

(0, 1, 2, 3, 4, 5, 6, 7)

(0, 2, 4, 6) (1, 3, 5, 7)

(0, 4) (2,6) (1, 5) (3, 7)

(0) (4) (2) (6) (1) (5) (3) (7)

Теперь если мы будем иметь конечную перестановку и за каждую из итераций будем объединять попарно участки, как описано выше, то получим в итоге преобразование Фурье.

Несложно понять и доказать, что конечный порядок является реверсом битов (bit-reversal permutation), то есть числа упорядочены по «перевернутым» битам. Это следует из того, что на каждом этапе рекурсивного спуска выделяются четные и нечетные половины, соответствующие числам с соответствующим битом 0 или 1. Эта перестановка получается, например, методом «перевернутого» бинарного счетчика за линейное время.

Разбор реализации

Вот пример более-менее эффективного кода (взят из открытых источников и проверен). Здесь не вводится класс комплексных чисел, а все хранится в одном массиве. На четных местах вещественные части, а нечетных мнимые. Так же код универсальный для прямого и обратного преобразования.

//nn должно быть степенью двойки!!!

procedure FastFourierTransform(var a : Array of Double; nn : Integer; InverseFFT : Boolean);

var

   ii, jj, n, mmax, m, j, istep, i, isign : Integer;

   wtemp, wr, wpr, wpi, wi, theta, tempr, tempi : Double;

begin

   if InverseFFT then isign := -1

                 else isign := 1;

   n := 2*nn;

   j := 1;

   ii:= 1;

   while ii<=nn do begin

      i := 2*ii-1;

      if j>i then begin

         tempr := a[j-1];

         tempi := a[j];

         a[j-1] := a[i-1];

         a[j] := a[i];

         a[i-1] := tempr;

         a[i] := tempi;

      end;

      m := n div 2;

      while (m>=2) and (j>m) do begin

         j := j-m;

         m := m div 2;

      end;

      j := j+m;

      Inc(ii);

   end;

   mmax := 2;

   while n>mmax do begin

      istep := 2*mmax;

      theta := 2*Pi/(isign*mmax);

      wpr := -2.0*sqr(sin(0.5*theta));

      wpi := sin(theta);

      wr := 1.0;

      wi := 0.0;

      ii:=1;

      while ii<=mmax div 2 do begin

         m := 2*ii-1;

         jj:=0;

         while jj<=(n-m) div istep do begin

            i := m+jj*istep;

            j := i+mmax;

            tempr := wr*a[j-1]-wi*a[j];

            tempi := wr*a[j]+wi*a[j-1];

            a[j-1] := a[i-1]-tempr;

            a[j] := a[i]-tempi;

            a[i-1] := a[i-1]+tempr;

            a[i] := a[i]+tempi;

            Inc(jj);

         end;

         wtemp := wr;

         wr := wr*wpr-wi*wpi+wr;

         wi := wi*wpr+wtemp*wpi+wi;

         Inc(ii);

      end;

      mmax := istep;

   end;

   if InverseFFT then

      for I := 0 to 2*nn-1 do

         a[I-1] := a[I-1]/nn;

end;

Подключение к длинной арифметике

Для умножения двух многочленов нужно представить их в виде своих значений в корнях из 1, перемножить эти значения, а потом восстановить многочлен по значениям. Из показанного выше следует, что это легко сделать тремя преобразованиями Фурье (двумя прямыми и одним обратным). Единственное, где можно ошибиться – это неправильно задать количество точек. Оно должно соответствовать степени не только исходных многочленов, но и степени их произведения.

Для перемножения длинных чисел нужно не забыть про переходы через разряд, а также про ограничение на основание системы счисления, связанное с «переполнением» мантиссы вещественного числа.