Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Алгоритмы C++

.pdf
Скачиваний:
689
Добавлен:
15.03.2016
Размер:
6 Mб
Скачать

mulmod (b1, b1, n); if (++b1 == n)

b1 = 0;

g = gcd (abs (b1 - b0), n);

for (unsigned count=0; count<iterations_count && (g == 1 || g == n); count++)

{

mulmod (b0, b0, n); if (++b0 == n)

b0 = 0; mulmod (b1, b1, n); ++b1;

mulmod (b1, b1, n); if (++b1 == n)

b1 = 0;

g = gcd (abs (b1 - b0), n);

}

return g;

}

Метод Бента (модификация метода Полларда "Ро")

Вероятностный тест, быстро даёт ответ далеко не для всех чисел. Возвращает либо найденный делитель, либо 1, если делитель не был найден.

template <class T>

T pollard_bent (T n, unsigned iterations_count = 19)

{

T

b0 = rand() % n,

b1 = (b0*b0 + 2) % n, a = b1;

for (unsigned iteration=0, series_len=1; iteration<iterations_count; iteration++, series_len*=2)

{

T g = gcd (b1-b0, n);

for (unsigned len=0; len<series_len && (g==1 && g==n); len++)

{

b1 = (b1*b1 + 2) % n;

g = gcd (abs(b1-b0), n);

}

b0 = a; a = b1;

if (g != 1 && g != n) return g;

}

return 1;

}

Метод Полларда Монте-Карло

Вероятностный тест, быстро даёт ответ далеко не для всех чисел. Возвращает либо найденный делитель, либо 1, если делитель не был найден.

template <class T>

T pollard_monte_carlo (T n, unsigned m = 100)

{

T b = rand() % (m-2) + 2;

static std::vector<T> primes; static T m_max;

if (primes.empty()) primes.push_back (3);

if (m_max < m)

{

m_max = m;

for (T prime=5; prime<=m; ++++prime)

{

bool is_prime = true;

for (std::vector<T>::const_iterator iter=primes. begin(), end=primes.end();

iter!=end; ++iter)

{

T div = *iter;

if (div*div > prime) break;

if (prime % div == 0)

{

is_prime = false; break;

}

}

if (is_prime)

primes.push_back (prime);

}

}

T g = 1;

for (size_t i=0; i<primes.size() && g==1; i++)

{

T cur = primes[i]; while (cur <= n)

cur *= primes[i]; cur /= primes[i];

b = powmod (b, cur, n); g = gcd (abs(b-1), n); if (g == n)

g = 1;

}

return g;

}

Метод Ферма

Это стопроцентный метод, но он может работать очень медленно, если у числа есть маленькие делители. Поэтому запускать его стоит только после всех остальных методов.

template <class T, class T2>

T ferma (const T & n, T2 unused)

{

T2

x = sq_root (n), y = 0,

r = x*x - y*y - n;

for (;;)

if (r == 0)

return x!=y ? x-y : x+y;

else

if (r > 0)

{

r -= y+y+1; ++y;

}

else

{

r += x+x+1; ++x;

}

}

Тривиальное деление

Этот элементарный метод пригодится, чтобы сразу обрабатывать числа с очень маленькими делителями.

template <class T, class T2>

T2 prime_div_trivial (const T & n, T2 m)

{

//сначала проверяем тривиальные случаи if (n == 2 || n == 3)

return 1; if (n < 2)

return 0; if (even (n))

return 2;

//генерируем простые от 3 до m

T2 pi;

const vector<T2> & primes = get_primes (m, pi);

// делим на все простые

for (std::vector<T2>::const_iterator iter=primes.begin(), end=primes.

end();

iter!=end; ++iter)

{

const T2 & div = *iter; if (div * div > n)

break;

else

if (n % div == 0) return div;

}

if (n < m*m) return 1;

return 0;

}

Собираем всё вместе

Объединяем все методы в одной функции.

Также функция использует тест на простоту, иначе алгоритмы факторизации могут работать очень долго. Например, можно выбрать тест BPSW (читать статью по BPSW).

template <class T, class T2>

void factorize (const T & n, std::map<T,unsigned> & result, T2 unused)

{

if (n == 1)

;

else

// проверяем, не простое ли число if (isprime (n))

++result[n];

else

// если число достаточно маленькое, то его разлагаем простым перебором

if (n < 1000*1000)

{

T div = prime_div_trivial (n, 1000); ++result[div];

factorize (n / div, result, unused);

}

else

{

// число большое, запускаем на нем

алгоритмы факторизации

T div;

//сначала идут быстрые алгоритмы Полларда div = pollard_monte_carlo (n);

if (div == 1)

div = pollard_rho (n); if (div == 1)

div = pollard_p_1 (n); if (div == 1)

div = pollard_bent (n);

//придётся запускать 100%-ый алгоритм Ферма if (div == 1)

div = ferma (n, unused);

//рекурсивно обрабатываем найденные множители factorize (div, result, unused);

factorize (n / div, result, unused);

}

}

Приложение

Скачать [5 КБ] исходник программы, которая использует все указанные методы факторизации и тест BPSW на простоту.

Быстрое преобразование Фурье за O (N log N). Применение к умножению двух полиномов или длинных чисел

Здесь мы рассмотрим алгоритм, который позволяет перемножить два полинома длиной за время

,

что значительно лучше времени

, достигаемого тривиальным алгоритмом умножения. Очевидно, что

 

умножение двух длинных чисел можно свести к умножению полиномов, поэтому два длинных числа также можно перемножить за время .

Изобретение Быстрого преобразования Фурье приписывается Кули (Coolet) и Таки (Tukey) — 1965 г. На самом деле БПФ неоднократно изобреталось до этого, но важность его в полной мере не осознавалась до появления

современных компьютеров. Некоторые исследователи приписывают открытие БПФ Рунге (Runge) и Кёнигу (Konig) в 1924 г. Наконец, открытие этого метода приписывается ещё Гауссу (Gauss) в 1805 г.

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

Пусть имеется многочлен -ой степени:

Не теряя общности, можно считать, что является степенью 2. Если в действительности не является степенью 2, то мы просто добавим недостающие коэффициенты, положив их равными нулю.

Из теории функций комплексного переменного известно, что комплексных корней -ой степени из единицы

существует ровно . Обозначим эти корни через

, тогда известно, что

.

Кроме того, один из этих корней

(называемый главным значением корня

-ой степени из

единицы) таков, что все остальные корни являются его степенями: .

Тогда дискретным преобразованием Фурье (ДПФ) (discrete Fourier transform, DFT) многочлена

(или, что то же самое, ДПФ вектора его коэффициентов

) называются значения этого многочлена

в точках

, т.е. это вектор:

 

 

 

 

 

 

 

Аналогично определяется и обратное дискретное преобразование Фурье (InverseDFT). Обратное ДПФ

для вектора значений многочлена — это вектор коэффициентов многочлена :

Таким образом, если прямое ДПФ переходит от коэффициентов многочлена к его значениям в комплексных корнях - ой степени из единицы, то обратное ДПФ — наоборот, по значениям многочлена восстанавливает коэффициенты многочлена.

Применение ДПФ для быстрого умножения полиномов

Пусть даны два многочлена

и . Посчитаем ДПФ для каждого из них:

и

— это два

вектора-значения многочленов.

 

 

 

Теперь, что происходит при умножении многочленов? Очевидно, в каждой точке их значения просто перемножаются, т.е.

 

 

 

Но это означает, что если мы перемножим вектора

и

, просто умножив каждый элемент

одного вектора на соответствующий ему элемент другого вектора, то мы получим не что иное, как ДПФ от многочлена :

Наконец, применяя обратное ДПФ, получаем:

где, повторимся, справа под произведением двух ДПФ понимается попарные произведения элементов векторов.

Такое произведение, очевидно, требует для вычисления только

операций. Таким образом, если мы

научимся вычислять ДПФ и обратное ДПФ за время

, то и произведение двух полиномов (а, следовательно,

и двух длинных чисел) мы сможем найти за ту же асимптотику.

 

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

удвоить степени каждого многочлена (опять же, дополнив их нулевыми коэффициентами).

Быстрое преобразование Фурье

Быстрое преобразование Фурье (fast Fourier transform) — это метод, позволяющий вычислять ДПФ за время . Этот метод основывается на свойствах комплексных корней из единицы (а именно, на том, что степени одних корней дают другие корни).

Основная идея БПФ заключается в разделении вектора коэффициентов на два вектора, рекурсивном вычислении ДПФ для них, и объединении результатов в одно БПФ.

Итак, пусть имеется многочлен степени , где — степень двойки, и :

Разделим его на два многочлена, один — с чётными, а другой — с нечётными коэффициентами:

Нетрудно убедиться, что:

 

 

 

 

 

 

Многочлены

и

имеют вдвое меньшую степень, чем многочлен . Если мы сможем за линейное время

по вычисленным

 

и

вычислить

, то мы и получим искомый алгоритм

 

быстрого преобразования Фурье (т.к. это стандартная схема алгоритма "разделяй и властвуй", и для неё

 

известна асимптотическая оценка

).

 

 

Итак, пусть мы имеем вычисленные вектора

 

и

.

Найдём выражения для

.

 

 

Во-первых, вспоминая (1), мы сразу получаем значения для первой половины коэффициентов:

Для второй половины коэффициентов после преобразований также получаем простую формулу:

(Здесь мы воспользовались (1), а также тождествами , .)

Итак, в результате мы получили формулы для вычисления всего вектора :

 

 

 

(эти формулы, т.е. две формулы вида

и

, иногда называют "преобразование

бабочки" ("butterfly operation"))

 

 

Тем самым, мы окончательно построили алгоритм БПФ.

Обратное БПФ

Итак, пусть дан вектор

— значения многочлена

степени в точках

.

Требуется восстановить коэффициенты

многочлена. Эта известная задача

 

называется интерполяцией, для этой задачи есть и общие алгоритмы решения, однако в данном случае будет получен очень простой алгоритм (простой тем, что он практически не отличается от прямого БПФ).

ДПФ мы можем записать, согласно его определению, в матричном виде:

 

 

 

Тогда вектор

можно найти, умножив вектор

на обратную матрицу к

матрице, стоящей слева (которая, кстати, называется матрицей Вандермонда):

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]