Zaawansowane algorytmy i struktury danych/Wykład 4: Różnice pomiędzy wersjami

Z Studia Informatyczne
Przejdź do nawigacjiPrzejdź do wyszukiwania
Sank (dyskusja | edycje)
m Zastępowanie tekstu – „<math> ” na „<math>”
 
(Nie pokazano 140 wersji utworzonych przez 4 użytkowników)
Linia 1: Linia 1:
== Abstrakt ==
== Abstrakt ==


Naturalna metoda dodawania dwóch wielomianów wymaga czasu
Naturalna metoda dodawania dwóch wielomianów wymaga czasu <math>\Theta(n)</math>, natomiast prosty algorytm mnożenia dwóch wielomianów stopnia <math>n</math> wymaga czasu <math>\Theta(n^2)</math>. W wykładzie tym pokażemy, jak z wykorzystaniem Szybkiej Transformaty Fouriera (STF) wykonać wszystkie podstawowe operacje na wielomianach w czasie większym niż <math>\Theta(n)</math> o czynnik polilogarytmiczny. W ramach wykładu pokażemy, jak dla wielomianów stopnia <math>n</math>:
<math>\Theta(n)</math>, natomiast prosty algorytm mnożenia dwóch
wielomianów stopnia <math>n</math> wymaga czasu
<math>\Theta(n^2)</math>. W wykładzie tym pokażemy, jak z
wykorzystaniem szybkiej transformaty Fouriera (STF), wykonać wszystkie
podstawowe operacje na wielomianach w czasie większym niż
<math>\Theta(n)</math> o czynnik polilogarytmiczny. Pokażemy jak dla
wielomianów stopnia <math>n</math>:
* mnożyć je w czasie <math>O(n \log n)</math>,
* mnożyć je w czasie <math>O(n \log n)</math>,
* obliczać wielomian interpolacyjny w czasie <math>O(n \log^2 n)</math>,
* dzielić wielomiany w czasie <math>O(n \log n)</math>.
* obliczać wartość wielomianu w <math>n</math> punktach w czasie <math>O(n \log^2 n)</math>,
 
* dzielić wielomiany w czasie <math>O(n \log^3 n)</math>.
Natomiast jako ćwiczenie pozostanie nam pokazanie, jak wykorzystać te algorytmy do:
* obliczania wielomianu interpolacyjnego w czasie <math>O(n \log^2 n)</math>,
* obliczania wartości wielomianu w <math>n</math> punktach w czasie <math>O(n \log^2 n)</math>.


== Mnożenie wielomianów w punktach ==
== Mnożenie wielomianów w punktach ==


Niech <math>A(x) = \sum_{i=0}^{n-1} a_i x^i</math> i
Niech <math>A(x) = \sum_{i=0}^{n-1} a_i x^i</math> i <math>B(x) = \sum_{i=0}^{n-1} b_i x^i</math> będą wielomianami stopnia <math>n</math> nad ciałem <math>F</math>. Wielomiany te możemy jednoznaczne reprezentować poprzez ich wartości w <math>n</math> punktach. Następujące twierdzenie zostało sformułowane w ramach wykładu z Metod Numerycznych ([[MN09#o istnieniu i jednoznaczności zadania interpolacji Lagrange'a|zobacz]]).
<math>B(x) = \sum_{i=0}^{n-1} b_i x^i</math> będzą
wielomianami stopnia <math>n</math> nad ciałem <math>F</math>.
Wielomiany te możemy jednoznaczne reprezentować poprzez ich
wartości w <math>n</math> punktach. Następujące twierdzenie zostało
sformułowane w ramach wykładu z Metod Numerycznych.


{{twierdzenie|[Twierdzenie o interpolacji
{{twierdzenie|1 [Twierdzenie o interpolacji wielomianowej]|interpolacja|Dla dowolnego zbioru <math>n</math> par <math>X = \{(x_0, y_0), (x_1, y_1), \ldots, (x_{n-1},y_{n-1})\}</math> takiego, że wszystkie wartości <math>x_i</math> są parami różne, istnieje jedyny wielomian <math>C(x)</math> stopnia co najwyżej <math>n</math> taki, że <math>C(x_i) = y_i</math> dla <math>i = 0,1,\ldots, n-1</math>
wielomianów]|interpolacja|Dla dowolnego zbioru <math>n</math> par <math>X = \{(x_0, y_0), (x_1, y_1), \ldots, (x_{n-1},y_{n-1})\}</math> takiego, że wszystkie wartości <math>x_i</math> są
parami różne, istnieje jedyny wielomian <math>C(x)</math> stopnia <math>n</math>
taki, że <math>C(x_i) = y_i</math> dla <math>i = 0,1,\ldots, n-1.</math>
}}
}}




Niech <math>X</math> będzie ustalonym zbiorem parami różnych punktów
Niech <math>X</math> będzie ustalonym zbiorem parami różnych punktów <math>x_0, \ldots, x_{2n-1} \in F</math>. Dla tego zbioru punktów możemy wyznaczyć zbiory wartości wielomianów:
<math>x_0, \ldots, x_{2n-1} \in F</math>. Dla tego zbioru punktów
możemy wyznaczyć zbiory wartości wielomianów:


<math>X_A = \{(x_0,A(x_0)), (x_1, A(x_1), \ldots, (x_{2n-1}, A(x_{2n-1}))\}</math>
{{wzor2|1=
<math>X_A = \{(x_0,A(x_0)), (x_1, A(x_1), \ldots, (x_{2n-1}, A(x_{2n-1}))\}</math>}}


{{wzor2|1=
<math>X_B = \{(x_0, B(x_0)), (x_1, B(x_1), \ldots, (x_{2n-1}, B(x_{2n-1}))\}</math>
<math>X_B = \{(x_0, B(x_0)), (x_1, B(x_1), \ldots, (x_{2n-1}, B(x_{2n-1}))\}</math>
}}
Niech <math>C</math> będzie wynikiem mnożenia wielomianów <math>A</math> i <math>B</math>, mamy wtedy:
{{wzor2|1=<math>C(x_i) = A(x_i)\cdot B(x_i)</math>.}}


Niech <math>C</math> będzie wynikiem mnożenia
Ponieważ stopień wielomianu <math>C</math> jest nie większy niż <math>2n</math>, to z [[ZASD Moduł 4#interpolacja| Twierdzenia o interpolacji]] zbiór wartości:
wielomianów <math>A</math> i <math>B</math>, mamy wtedy


<math>C(x_i) = A(x_i)\cdot B(x_i)</math>.
{{wzor2|1=
<math>X_{A\cdot B} = \{(x_0, A(x_0)B(x_0)), (x_1, A(x_1)B(x_1), \ldots, (x_{2n-1}, A(x_{2n-1})B(x_{2n-1})) \}</math>,
}}


Ponieważ stopień wielomianu <math>C</math> jest nie większy niż
jednoznacznie wyznacza wielomian <math>A \cdot B</math>. Mając zbiory <math>X_A</math> i <math>X_B</math>, możemy wyznaczyć zbiór <math>X_C</math> w czasie <math>O(n)</math>. A następnie na jego podstawie znaleźć wielomian <math>C</math>. Procedura ta jest przedstawiona na następującym rysunku:
<math>2n</math> to z [[interpolacja| Twierdzenia o interpolacji]] zbiór wartości


<math>X_{A\times B} = \{(x_0, A(x_0)B(x_0)), (x_1, A(x_1)B(x_1),
\ldots, (2x_{n-1}, A(x_{2n-1})B(x_{2n-1})) \}</math>,


jednoznacznie wyznacza wielomian <math>A \times B</math>. Mając
<center>
zbiory <math>X_A</math> i <math>X_B</math> możemy wyznaczyć zbiór
[[File:Zasd_ilustr_u.svg|600x450px|thumb|center]]</center>
<math>X_C</math> w czasie <math>O(n)</math>. Procedura ta jest
przedstawiona na następującym rysunku:


<flash>file=Zasd_fft1.swf|width=460|height=350</flash>




Jednak aby ostatecznie otrzymać szybszy algorytm niż algorytm naiwny
Jednak aby ostatecznie otrzymać szybszy algorytm niż algorytm naiwny, musimy pokazać jak rozwiązać problem obliczania wartości wielomianu w <math>n</math> punktach w czasie szybszym niż <math>\Theta(n^2)</math>. Podobnie musimy umieć obliczać wielomian interpolacyjny dla danego zbioru punktów.
musimy pokazać jak rozwiązać problem obliczania wartości wielomianu
w <math>n</math> punktach w czasie szybszym niż
<math>\Theta(n^2)</math>. Podobnie musimy umieć obliczać wielomian
interpolacyjny dla danego zbioru punktów.


== Szybka transformata Fouriera (STF) ==
== Szybka transformata Fouriera (STF) ==


Problem obliczania wartości wielomianu w <math>n</math> punktach i
Problem obliczania wartości wielomianu w <math>n</math> punktach i problem jego interpolacji rozwiążemy, wykorzystując szybką transformatę Fouriera. W poprzednim rozdziale nie zakładaliśmy nic na temat zbioru punktów <math>X</math>. Głównym pomysłem w konstrukcji algorytmu STF będzie właśnie wybór odpowiedniego zbioru punktów <math>X</math> tak, aby jak największa ilość wykonywanych obliczeń powtarzała się.
problem jego interpolacji rozwiążemy wykorzystując szybką
transformatę Fouriera. W poprzednim rozdziale nie zakładaliśmy nic
na temat zbioru punktów <math>X</math>. Głównym pomysłem w
konstrukcji algorytmu STF będzie właśnie wybór odpowiedniego zbioru
punktów X tak, aby jak największa ilość wykonywanych obliczeń
powtarzała się.


Założymy chcemy obliczyć wartości wielomianu <math>A(x) =
Założymy, że chcemy obliczyć wartości wielomianu <math>A(x) = \sum_{k=0}^{n-1} a_k x^k</math> oraz <math>n</math> jest parzyste. Jeżeli <math>n</math> jest nieparzyste, to dodajemy na początek <math>A(x)</math> jednomian <math>0x^{n+1}</math> co nie zmienia wyniku działania algorytmu. Punkty <math>X_n = \{x_0,x_1,\ldots,x_{n-1}\}</math> zdefiniujemy w następujący sposób:
\sum_{i=0}^{n-1} a_i x^i</math> oraz <math>n</math> jest parzyste.
Jeżeli <math>n</math> jest nieparzyste to dodajemy na początek
<math>A(x)</math> jednomian <math>0x^{n+1}</math> co nie zmienia nam
wyniku działania algorytmu. Punkty <math>X_n =
\{x_0,x_1,\ldots,x_{n-1}\}</math> zdefiniujemy w następujący sposób:


<math>x_i = e^{\frac{2\pi i}{n}}</math>.
{{wzor2|1=
<math>x_k = e^{\frac{2\pi ki}{n}}</math>.
}}


Dla wielomianu <math>A(x)</math> definiujemy dwa nowe wielomiany
Dla wielomianu <math>A(x)</math> definiujemy dwa nowe wielomiany <math>A^{[0]}(x)</math> i <math>A^{[1]}(x)</math> poprzez wybranie do nich współczynników <math>A(x)</math> o numerach odpowiednio parzystych i nieparzystych:
<math>A^{[0]}(x)</math> i <math>A^{[1])(x)</math> poprzez wybranie
do nich współczynników <math>A(x)</math> o numerach odpowiednio
parzystych i nieparzystych:


{{wzor2|1=
<math>A^{[0]}(x) = a_0 + a_2x + a_4x^2 + \ldots + a_{n-2}x^{\frac{n}{2}-1}</math>,
<math>A^{[0]}(x) = a_0 + a_2x + a_4x^2 + \ldots + a_{n-2}x^{\frac{n}{2}-1}</math>,
}}


{{wzor2|1=
<math>A^{[1]}(x) = a_1 + a_3x + a_5x^2 + \ldots + a_{n-1}x^{\frac{n}{2}-1}</math>.
<math>A^{[1]}(x) = a_1 + a_3x + a_5x^2 + \ldots + a_{n-1}x^{\frac{n}{2}-1}</math>.
}}
Wielomiany <math>A^{[0]}(x)</math> oraz <math>A^{[1]}(x)</math> są stopnia co najwyżej <math>\frac{n}{2}</math>. Co więcej zachodzi:
{{wzor|wzor_1|1|<math>A(x) = A^{[0]}(x^2) + x A^{[1]}(x^2)</math>}}
Widzimy teraz, że problem ewaluacji wielomianu <math>A(x)</math> w punktach <math>x_0, x_1,\ldots, x_{n-1}</math> sprowadza się do:
* ewaluacji wielomianów <math>A^{[0]}(x)</math> i <math>A^{[1]}(x)</math> w punktach
{{wzor2|1=
<math>X' =\{x_0^2, x_1^2, \ldots, x_{n-1}^2\}</math>.
}}
* a następnie obliczenie wartości <math>A(x)</math> zgodnie ze wzorem [[ZASD Moduł 4#wzor_1|(1)]].


Wielomiany <math>A^{[0](x)</math> oraz <math>A^{[1]}(x)</math> są
Zauważmy, że z definicji punktów <math>x_i</math> mamy:
stopnia co najwyżej <math>\frac{n}{2}</math>. Co więcej zachodzi:


<math>A(x) = A^{[0]}(x^2) + x A^{[1]}(x^2)</math> (1).
{{wzor2|1=
<math>x_k^2 = \left(e^{\frac{2\pi ki}{n}} \right)^2 = e^{\frac{2\pi k i}{n/2}}</math>.
}}


Widzimy teraz, że problem ewaluacji wielomianu <math>A(x)</math> w
Możemy teraz zauważyć, że zachodzi <math>x_k^2 = x_{k+\frac{n}{2}}^2</math>, a więc <math>X' = X_{\frac{n}{2}}</math>. Udało nam się więc zredukować problem rozmiaru <math>n</math> - obliczenia wartości wielomianu <math>A(x)</math> stopnia <math>n</math> w <math>n</math> punktach do dwóch problemów rozmiaru <math>\frac{n}{2}</math> - obliczenia wartości wielomianów <math>A^{[0]}(x)</math> i <math>A^{[1]}(x)</math> stopnia <math>\frac{n}{2}</math> w <math>\frac{n}{2}</math> punktach. Możemy teraz zastosować tę technikę rekurencyjnie, otrzymując następujący algorytm.
punktach <math>\omega_n^0, \omega_n^1,\ldots, \omega_n^{n-1}</math> sprowadza się
do:
* ewaluacji wielomianów <math>A^{[0]}(x)</math> i <math>A^{[1]}(x)</math>
w punktach


<math>X' =\{x_0^2, x_1^2, \ldots, x_{n-1}^2\} </math>.
{{algorytm|Szybkiej Transformaty Fouriera|algorytm_fft|
3=
STF(<math>a = (a_0,\ldots,a_{n-1})</math>)
  '''if''' <math>n</math> nieparzyste '''then'''
    dodaj wyraz <math>a_n</math> do <math>a</math>
    zwiększ <math>n</math>
  '''if''' <math>n=1</math> '''then''' '''return''' a
  <math>\omega_n = e^{\frac{2\pi i}{n}}</math>
  <math>\omega = 1</math>
  <math>a^{[0]} = (a_0, a_2, \ldots, a_{n-2})</math>
  <math>a^{[1]} = (a_1, a_3, \ldots, a_{n-1})</math>
  <math>y^{[0]} = SFT(a^{[0]})</math>
  <math>y^{[1]} = SFT(a^{[1]})</math>
  '''for''' k=0 '''to''' <math>\frac{n}{2}-1</math> '''do'''
    <math>y_k = y_k^{[0]} + \omega y_k^{[1]}</math>
    <math>y_{k+\frac{n}{2}} = y_k^{[0]} - \omega y_k^{[1]}</math>
    <math>\omega = \omega \omega_n</math>
  '''return''' y
}}


* a następnie obliczenie wartości <math>A(x) </math>wyniku zgodnie ze wzorem (1).
Algorytm ten najpierw oblicza SFT wielomianów <math>A^{[0]}(x)</math> i <math>A^{[1]}(x)</math>, a następnie łączy te wyniki w celu wyliczenia SFT dla wielomianu <math>A(x)</math>. Przeanalizujmy teraz wykonanie pętli. Zauważmy najpierw, że w <math>k</math>-tym  kroku pętli mamy <math>\omega = \omega_n^k = e^{\frac{2\pi i k}{n}} = x_k</math>. Czyli:


Zauważmy, że z definicji punktów $x_i$ mamy:
{{wzor2|1=<math>
\begin{array}{r@{}c@{}l}
y_k &=& y_k^{[0]} + x_k y_k^{[1]} = A^{[0]}(e^{\frac{2\pi i k}{n/2}}) + x_k A^{[1]}(e^{\frac{2\pi i k}{n/2}}) =
\\&=& A^{[0]} \left(\left(e^{\frac{2\pi i k}{n}}\right)^2\right) + x_k A^{[1]}\left(\left(e^{\frac{2\pi i k}{n}}\right)^2\right)=\\ &=& A^{[0]}(x_k^2) + x_k A^{[1]}(x_k^2) = A(x_k),
\end{array}
</math>}}


<math>x_i^2 = \left(e^{\frac{2\pi i}{n} \right)^2 = e^{\frac{2\pi i}{n/2}</math>.
oraz


Możemy teraz zauważyć, że zachodzi <math>x_i^2 =
{{wzor2|1=<math>
x_{i+\frac{n}{2}}^2</math>, a więc <math>X' =
\begin{array}{r@{}c@{}l}
X_{\frac{n}{2}}</math>. Udało nam się więc zredukować problem
y_{k+\frac{n}{2}} &=& y_k^{[0]} - x_k y_k^{[1]} = A^{[0]}(e^{\frac{2\pi i k}{n/2}}) - e^{\frac{2\pi i k}{n}} A^{[1]}(e^{\frac{2\pi i k}{n/2}}) =
rozmiaru <math>n</math> --- obliczenia wartości wielomianu
\\&=& A^{[0]}(e^{\frac{2\pi i (k + n/2)}{n/2}}) + e^{\frac{2\pi i k + n/2}{n}} A^{[1]}(e^{\frac{2\pi i (k + n/2)}{n/2}})=\\ &=& A^{[0]}(x_{k + n/2}^2) + x_{k + n/2}^2 A^{[1]}(x_{k + n/2}^2) = A(x_{k+\frac{n}{2}}).
<math>A(x)</math> stopnia <math>n</math> w <math>n</math>do
\end{array}
punktach, do dwóch problemów rozmiaru <math>\frac{n}{2}</math> ---
</math>}}
obliczenia wartości wielomianów <math>A^{[0]}(x)</math> i
<math>A^{[1]}(x)</math> stopnia <math>\frac{n}{2}</math> w
<math>\frac{n}{2}</math> punktach. Możemy teraz zastosować tą
technikę rekurencyjne otrzymując następujący algorytm.


Równanie rekurencyjne na czas działania procedury STF wygląda następująco:
Gdzie w ostatniej równości skorzystaliśmy ze wzoru [[ZASD Moduł 4#wzor_1|(1)]]. Widzimy zatem, że algorytm poprawnie oblicza wartość STF dla wielomianu <math>A(x)</math>. Równanie rekurencyjne na czas działania procedury STF wygląda następująco:


{{wzor2|1=
<math>T(n) =2 T(\frac{n}{2}) + \Theta(n) = \Theta(n \log n)</math>.
<math>T(n) =2 T(\frac{n}{2}) + \Theta(n) = \Theta(n \log n)</math>.
}}
Poniższa animacja przedstawia działanie algorytmu SFT wywołanego dla wielomianu <math>A(x) = x^3 + 2x^2 + 5x + 3</math>. Wartości wielomianu <math>A(x)</math> wyznaczone są z wartości wielomianów <math>A^{[0]}(x)</math> i
<math>A^{[1]}(x)</math>, a wartości tych wielomianów z wartości wielomianów <math>A^{[00]}(x)</math>, <math>A^{[01]}(x)</math>, <math>A^{[10]}(x)</math> i <math>A^{[11]}(x)</math>.
<flash>file=Zasd_ilustr_a.swf|height=500|width=600</flash>


=== Odwrotna transformata Fouriera ===
=== Odwrotna transformata Fouriera ===


Aby zakończyć konstrukcję algorytmu dla szybkiego mnożenia
Aby zakończyć konstrukcję algorytmu dla szybkiego mnożenia wielomianów, pozostaje nam pokazanie, jak obliczyć wielomian interpolujący dla zbioru punktów <math>X_n</math>. Obliczenie wykonane w czasie szybkiej transformaty Fouriera możemy przedstawić w postaci macierzowej jako mnożenie macierzy przez wektor <math>(A(x_0), A(x_1), \ldots, A(x_{n-1}))^T = V_n (a_0, a_1, \ldots, a_{n-1})^T</math>, gdzie <math>V_n = V(x_0,\ldots,x_{n-1})</math> jest macierzą Vandermonde'a zawierającą potęgi <math>x_j</math>:
wielomianów pozostaje nam pokazanie jak wykonać obliczyć wielomian
interpolujący dla zbioru punktów <math>X_n</math>. Obliczenie
wykonane w czasie szybkiej transformaty Fouriera możemy przedstawić
w postaci macierzowej jako mnożenie macierzy przez wektor
<math>(A(x_0), A(x_1), \ldots, A(x_{n-1}))^T = V_n (a_0, a_1,
\ldots, a_{n-1}</math>, gdzie <math>V(x_0,\ldots,x_{n-1})</math> jest macierzą
Vandermonde'a zawierającą potęgi <math>x_i</math>


<math>\left(
{{wzor2|1=
\begin{array}{c}
<math>
\left(
\begin{matrix}
A(x_0)\\
A(x_0)\\
A(x_1)\\
A(x_1)\\
Linia 143: Linia 150:
\vdots\\
\vdots\\
A(x_{n-1})
A(x_{n-1})
\end{array}
\end{matrix}
\right) =
\right) =
\left[
\left[
\begin{array}
\begin{array}{cccccc}
x_0^0 & x_0^1 & x_0^2 & x_0^3 & \hdots & x_0^{n-1} \\
x_0^0 & x_0^1 & x_0^2 & x_0^3 & \cdots & x_0^{n-1} \\
x_1^0 & x_1^1 & x_1^2 & x_1^3 & \hdots & x_1^{n-1} \\
x_1^0 & x_1^1 & x_1^2 & x_1^3 & \cdots & x_1^{n-1} \\
x_2^0 & x_2^1 & x_2^2 & x_2^3 & \hdots & x_2^{n-1} \\
x_2^0 & x_2^1 & x_2^2 & x_2^3 & \cdots & x_2^{n-1} \\
\vdots & \vdots & \vdots & \vdots & \ddots & \vdots \\
\vdots & \vdots & \vdots & \vdots & \ddots & \vdots \\
x_{n-1}^0 & x_{n-1}^1 & x_{n-1}^2 & x_{n-1}^3 & \hdots & x_{n-1}^{n-1} \\
x_{n-1}^0 & x_{n-1}^1 & x_{n-1}^2 & x_{n-1}^3 & \cdots & x_{n-1}^{n-1} \\
\end{array}
\end{array}
\right]
\right]
\left(
\left(
\begin{array}{c}
\begin{matrix}
a_0\\
a_0\\
a_1\\
a_1\\
a_2)\\
a_2\\
\vdots\\
\vdots\\
a_{n-1)
a_{n-1}
\end{array}
\end{matrix}
\right)
\right).
</math>
</math>
}}


Element macierzy <math>V(x_0,\ldots,x_{n-1})</math> dany jest jako
Element macierzy <math>V(x_0,\ldots,x_{n-1})</math> dany jest jako


<math>V(x_0,\ldots,x_{n-1})_{i,j} = x_i^j</math>.
{{wzor2|1=
<math>(V_n)_{j,k} = V(x_0,\ldots,x_{n-1})_{j,k} = x_j^k</math>.
}}
 
Korzystając z definicji zbioru <math>X_n</math>, otrzymujemy
 
{{wzor2|1=
<math>V(x_0,\ldots,x_{n-1})_{j,k} = \left(e^{\frac{2\pi i j}{n}}\right)^k
= e^{\frac{2\pi ijk}{n}}</math>
}}
 
W celu wykonania operacji odwrotnej do SFT, czyli obliczenia wielomianu interpolacyjnego, musimy wykonać mnożenie <math>V_n^{-1} (A(x_0), A(x_1), \ldots, A(x_{n-1}))^T</math>.
 
{{lemat|2||Macierz <math>W_n</math> zdefiniowana jako:
 
{{wzor2|1=<math>
\left(W_n\right)_{j,k} = \frac{1}{n} e^{\frac{-2\pi ijk}{n}}</math>,}}
 
jest macierzą odwrotną do macierzy <math>V_n</math>.
}}
 
{{dowod|||Pokażemy, że <math>V_n W_n = I</math>. Rozważmy pozycję <math>(j,k)</math> macierzy <math>V_n W_n</math>:
 
{{wzor2|1=<math>
\left(V_n W_n\right)_{j,k} = \sum_{l=0}^{n-1} \left(V_n\right)_{j,l} \left(W_n\right)_{l,k}
=\sum_{l=0}^{n-1} e^{\frac{2\pi i j l}{n}} \frac{1}{n} e^{\frac{-2\pi i l k}{n}} =
\sum_{l=0}^{n-1} \frac{1}{n} e^{\frac{2\pi l(j-k)}{n}} =
</math>}}
 
Jeżeli <math>j=k</math>, to <math>e^{\frac{2\pi k(j-k)}{n}} = 1</math> i suma ta jest równa <math>1</math>. W przeciwnym przypadku możemy skorzystać ze wzoru na sumę szeregu geometrycznego:
 
{{wzor2|1=<math>
= \frac{1}{n} \frac{1 - e^{\frac{2\pi n(j-k)}{n}}}
{1 - e^{\frac{2\pi (j-k)}{n}}} =
\frac{1}{n} \frac{1 - 1^{(j-k)}}
{1 - e^{\frac{2\pi (j-k)}{n}}} = 0.
</math>}}
 
Czyli rzeczywiście <math>V_n W_n = I</math>.
}}
 
Porównując postać macierzy <math>V_n</math> oraz macierzy <math>W_n</math> widzimy, że w celu obliczenia transformaty odwrotnej możemy użyć [[ZASD Moduł 4#algorytm_fft|Algorytmu Szybkiej Transformaty Fouriera]], musimy tylko zamienić linijkę <math>\omega_m = e^{\frac{2\pi i}{n}}</math> na <math>\omega_m = e^{-\frac{2\pi i}{n}}</math> i podzielić otrzymany wynik przez <math>n</math>.
 
== Dzielenie wielomianów ==
 
W tej części wykładu skupimy się na problemie dzielenia dwóch wielomianów. Niech <math>A(x)</math> będzie wielomianem stopnia <math>m</math>, a <math>B(x)</math> wielomianem stopnia <math>n</math>. Zakładamy bez straty ogólności, że <math>b_{n-1}\neq 0</math>. W problemie dzielenia wielomianów chcemy obliczyć dwa wielomiany <math>D(x)</math> i <math>R(x)</math> takie, że
 
{{wzor|wzor_2|2|<math>A(x) = D(x) B(x)  + R(x)</math>}}
 
oraz stopień wielomianu <math>R(x)</math> jest ostro mniejszy niż <math>n</math>. Wielomian <math>D(x)</math> nazywamy '''wynikiem dzielenia''', a wielomian <math>R(x)</math> to '''reszta z dzielenia'''. Pierwszy pomysł jaki się od razu nasuwa, to spróbować policzyć odwrotność wielomianu <math>B(x)</math> i przemnożyć przez tę odwrotność strony tego równania. Niestety wielomiany nie mają odwrotności będących wielomianami. Jednak nie pozostajemy tutaj zupełnie bezradni. Możemy rozszerzyć naszą dziedzinę obliczeń tak aby zagwarantować istnienie pewnych odwrotności.
 
Obliczenia będziemy wykonywać nad zbiorem szeregów formalnych <math>F[[x]]</math> nad ciałem <math>F</math>, patrz [[Matematyka dyskretna 1/Wykład 7: Funkcje tworzące|Wykład z matematyki dyskretnej o funkcjach tworzących]]. Dla części elementów <math>F[[x]]</math> istnieją odwrotności. Elementy te są postaci <math>a + x A(x)</math>, gdzie <math>a\neq 0</math> i <math>A(x) \in F[[x]]</math>.
 
 
{{cwiczenie||odwrotność_formalna|3=
<div class="mw-collapsible mw-made=collapsible mw-collapsed">
Czy umiesz obliczyć odwrotność dla szeregu <math>1 + x</math>?
<div class="mw-collapsible-content" style="display:none">Odwrotność to <math>\sum_{i=0}^{\infty} (-x)^{i}</math>.</div>
</div>
}}
 
{{cwiczenie||odwrotność_formalna|3=
<div class="mw-collapsible mw-made=collapsible mw-collapsed">
Czy umiesz obliczyć odwrotność dla szeregu <math>\sum_{j=0}^{\infty}(j+1)x^j</math>?
<div class="mw-collapsible-content" style="display:none">Odwrotność to <math>(1-x)^2</math>.</div>
</div>
}}
 
 
Do wzoru [[ZASD Moduł 4#wzor_2|(2)]] wstawmy <math>x = \frac{1}{z}</math>, otrzymamy
wtedy:
 
{{wzor2|1=<math>
A^R(z) = D^R(z) B^R(z)  + z^{m-n-1}R^R(z) = B^R(z)D^R(z) \mod  z^{m-n-1}</math>,}}
 
gdzie <math>A^R(z) = z^m A(\frac{1}{z})</math>, <math>D^R(z) = z^{m-n} D(\frac{1}{z})</math>, <math>B^R(z) = z^{n} B(\frac{1}{z})</math> i <math>R^R(z) = z^{n-1} R(\frac{1}{z})</math>, oznaczają wielomiany otrzymane poprzez odwrócenie kolejności współczynników. Z założenia, że <math>b_{n-1}\neq 0</math> wiemy, że wielomian <math>B^R</math> ma odwrotność nad zbiorem szeregów formalnych. Możemy zapisać więc:
 
{{wzor|wzor_3|3|<math>
D^R(z) = A^R(x) \left(B^R(z)\right)^{-1} \mod z^{m-n-1}.
</math>}}
 
Zauważmy, że w celu wyznaczenia <math>D^R(z)</math> potrzebujemy tylko <math>m-n-1</math> wyrazów szeregu <math>\left(B^R(z)\right)^{-1}</math>. Wyższe wyrazy i tak znikną w wyniku wykonania mnożenia modulo <math>z^{m-n-1}</math>. Pozostało nam teraz tylko pokazać, jak wyznaczyć odwrotność dla szeregu formalnego. Algorytm ten przedstawiony jest poniżej.
 
 
{{algorytm|obliczania pierwszych <math>m</math> wyrazów odwrotnośći
szeregu formalnego|algorytm_odwrotność|
3=
ODWROTNOŚĆ<math>(A(x) = \sum_{i=0}^{n-1} a_i x^i, m)</math>
  '''if''' <math>m=1</math> '''then''' '''return''' <math>1/a_0</math>
  <math>A^{[0]}(x) =</math>ODWROTNOŚĆ<math>(A(x), \lfloor \frac{m}{2} \rfloor)</math>
  '''return''' <math>(A^{[0]}(x) -(A(x) A^{[0]}(x) -1)A^{[0]}(x)) \mod x^m</math>
}}
 
Obliczenie to jest poprawne, ponieważ:
 
{{wzor2|1=<math>
\frac{1}{A(x)} - (A^{[0]}(x) -(A(x) A^{[0]}(x) -1) A^{[0]}(x)) = A(x)\left(\frac{1}{A(x)} - A^{[0]}(x)\right)^2</math>,}}


W celu wykonania operacji odwrotnej do SFT, czyli obliczenia
a to jest równe wielokrotności <math>x^{m}</math>. Jeżeli wykorzystamy szybkie mnożenie wielomianów do obliczenia <math>(A^{[0]}(x) -(A(x) A^{[0]}(x) -1)A^{[0]}(x))</math>, to złożoność tego algorytmu wynosić będzie <math>O(n\log n)</math>. Możemy teraz skonstruować algorytm wykonujący dzielenie wielomianu <math>A(x)</math> przez wielomian <math>B(x)</math> w czasie <math>O(m \log m)</math>, gdzie <math>m</math> to stopień wielomianu <math>A(x)</math>.
wielomianu interpolacyjnego, musimy wykonać mnożenie
<math>V(x_0,\ldots,x_{n-1})^{-1} (A(x_0), A(x_1), \ldots,
A(x_{n-1}))^T</math>.


Korzystając z definicji zbioru <math>X_n</math> otrzymujemy
{{algorytm|dzielenia wielomianów|algorytm_dzielenie|
3=
PODZIEL<math>(A(x), B(x))</math>
  <math>A^R(z) = z^m A(\frac{1}{z})</math>
  <math>B^R(z) = z^{n} B(\frac{1}{z})</math>
  <math>(B^R(z)^{-1})(z) =</math>ODWROTNOŚĆ<math>(B^R(z), m-n-1)</math>
  <math>D^R(z) = A^R(x) \left(B^R(z)\right)^{-1} \mod z^{m-n-1}</math>
  <math>D(x) = z^{m-n-1} D^R(\frac{1}{x})</math>
  <math>R(x) = A(x) - D(x)B(x)</math>
  '''return''' <math>(D(x), R(x))</math>
}}


<math>V(x_0,\ldots,x_{n-1})_{i,j} = \left(e^{\frac{2\pi i}{n}\right)^j
Poprawność tego algorytmu wynika wprost ze [[#wzor_3|wzoru (3)]], a jego złożoność wynosi naturalnie <math>O(n \log n)</math>.
= e^{\frac{2\pi ij}{n}.</math>

Aktualna wersja na dzień 22:18, 11 wrz 2023

Abstrakt

Naturalna metoda dodawania dwóch wielomianów wymaga czasu Θ(n), natomiast prosty algorytm mnożenia dwóch wielomianów stopnia n wymaga czasu Θ(n2). W wykładzie tym pokażemy, jak z wykorzystaniem Szybkiej Transformaty Fouriera (STF) wykonać wszystkie podstawowe operacje na wielomianach w czasie większym niż Θ(n) o czynnik polilogarytmiczny. W ramach wykładu pokażemy, jak dla wielomianów stopnia n:

  • mnożyć je w czasie O(nlogn),
  • dzielić wielomiany w czasie O(nlogn).

Natomiast jako ćwiczenie pozostanie nam pokazanie, jak wykorzystać te algorytmy do:

  • obliczania wielomianu interpolacyjnego w czasie O(nlog2n),
  • obliczania wartości wielomianu w n punktach w czasie O(nlog2n).

Mnożenie wielomianów w punktach

Niech A(x)=i=0n1aixi i B(x)=i=0n1bixi będą wielomianami stopnia n nad ciałem F. Wielomiany te możemy jednoznaczne reprezentować poprzez ich wartości w n punktach. Następujące twierdzenie zostało sformułowane w ramach wykładu z Metod Numerycznych (zobacz).

Twierdzenie 1 [Twierdzenie o interpolacji wielomianowej]

Dla dowolnego zbioru n par X={(x0,y0),(x1,y1),,(xn1,yn1)} takiego, że wszystkie wartości xi są parami różne, istnieje jedyny wielomian C(x) stopnia co najwyżej n taki, że C(xi)=yi dla i=0,1,,n1


Niech X będzie ustalonym zbiorem parami różnych punktów x0,,x2n1F. Dla tego zbioru punktów możemy wyznaczyć zbiory wartości wielomianów:

XA={(x0,A(x0)),(x1,A(x1),,(x2n1,A(x2n1))}
XB={(x0,B(x0)),(x1,B(x1),,(x2n1,B(x2n1))}


Niech C będzie wynikiem mnożenia wielomianów A i B, mamy wtedy:

C(xi)=A(xi)B(xi).

Ponieważ stopień wielomianu C jest nie większy niż 2n, to z Twierdzenia o interpolacji zbiór wartości:

XAB={(x0,A(x0)B(x0)),(x1,A(x1)B(x1),,(x2n1,A(x2n1)B(x2n1))},

jednoznacznie wyznacza wielomian AB. Mając zbiory XA i XB, możemy wyznaczyć zbiór XC w czasie O(n). A następnie na jego podstawie znaleźć wielomian C. Procedura ta jest przedstawiona na następującym rysunku:


Plik:Zasd ilustr u.svg


Jednak aby ostatecznie otrzymać szybszy algorytm niż algorytm naiwny, musimy pokazać jak rozwiązać problem obliczania wartości wielomianu w n punktach w czasie szybszym niż Θ(n2). Podobnie musimy umieć obliczać wielomian interpolacyjny dla danego zbioru punktów.

Szybka transformata Fouriera (STF)

Problem obliczania wartości wielomianu w n punktach i problem jego interpolacji rozwiążemy, wykorzystując szybką transformatę Fouriera. W poprzednim rozdziale nie zakładaliśmy nic na temat zbioru punktów X. Głównym pomysłem w konstrukcji algorytmu STF będzie właśnie wybór odpowiedniego zbioru punktów X tak, aby jak największa ilość wykonywanych obliczeń powtarzała się.

Założymy, że chcemy obliczyć wartości wielomianu A(x)=k=0n1akxk oraz n jest parzyste. Jeżeli n jest nieparzyste, to dodajemy na początek A(x) jednomian 0xn+1 co nie zmienia wyniku działania algorytmu. Punkty Xn={x0,x1,,xn1} zdefiniujemy w następujący sposób:

xk=e2πkin.

Dla wielomianu A(x) definiujemy dwa nowe wielomiany A[0](x) i A[1](x) poprzez wybranie do nich współczynników A(x) o numerach odpowiednio parzystych i nieparzystych:

A[0](x)=a0+a2x+a4x2++an2xn21,
A[1](x)=a1+a3x+a5x2++an1xn21.

Wielomiany A[0](x) oraz A[1](x) są stopnia co najwyżej n2. Co więcej zachodzi:

A(x)=A[0](x2)+xA[1](x2)      (1)

Widzimy teraz, że problem ewaluacji wielomianu A(x) w punktach x0,x1,,xn1 sprowadza się do:

  • ewaluacji wielomianów A[0](x) i A[1](x) w punktach
X={x02,x12,,xn12}.
  • a następnie obliczenie wartości A(x) zgodnie ze wzorem (1).

Zauważmy, że z definicji punktów xi mamy:

xk2=(e2πkin)2=e2πkin/2.

Możemy teraz zauważyć, że zachodzi xk2=xk+n22, a więc X=Xn2. Udało nam się więc zredukować problem rozmiaru n - obliczenia wartości wielomianu A(x) stopnia n w n punktach do dwóch problemów rozmiaru n2 - obliczenia wartości wielomianów A[0](x) i A[1](x) stopnia n2 w n2 punktach. Możemy teraz zastosować tę technikę rekurencyjnie, otrzymując następujący algorytm.

Algorytm Szybkiej Transformaty Fouriera


 STF(a=(a0,,an1))
 if n nieparzyste then
   dodaj wyraz an do a
   zwiększ n
 if n=1 then return a
 ωn=e2πin
 ω=1
 a[0]=(a0,a2,,an2)
 a[1]=(a1,a3,,an1)
 y[0]=SFT(a[0])
 y[1]=SFT(a[1])
 for k=0 to n21 do
   yk=yk[0]+ωyk[1]
   yk+n2=yk[0]ωyk[1]
   ω=ωωn
 return y

Algorytm ten najpierw oblicza SFT wielomianów A[0](x) i A[1](x), a następnie łączy te wyniki w celu wyliczenia SFT dla wielomianu A(x). Przeanalizujmy teraz wykonanie pętli. Zauważmy najpierw, że w k-tym kroku pętli mamy ω=ωnk=e2πikn=xk. Czyli:

Parser nie mógł rozpoznać (nieznana funkcja „\begin{array}”): {\displaystyle \begin{array}{r@{}c@{}l} y_k &=& y_k^{[0]} + x_k y_k^{[1]} = A^{[0]}(e^{\frac{2\pi i k}{n/2}}) + x_k A^{[1]}(e^{\frac{2\pi i k}{n/2}}) = \\&=& A^{[0]} \left(\left(e^{\frac{2\pi i k}{n}}\right)^2\right) + x_k A^{[1]}\left(\left(e^{\frac{2\pi i k}{n}}\right)^2\right)=\\ &=& A^{[0]}(x_k^2) + x_k A^{[1]}(x_k^2) = A(x_k), \end{array} }

oraz

Parser nie mógł rozpoznać (nieznana funkcja „\begin{array}”): {\displaystyle \begin{array}{r@{}c@{}l} y_{k+\frac{n}{2}} &=& y_k^{[0]} - x_k y_k^{[1]} = A^{[0]}(e^{\frac{2\pi i k}{n/2}}) - e^{\frac{2\pi i k}{n}} A^{[1]}(e^{\frac{2\pi i k}{n/2}}) = \\&=& A^{[0]}(e^{\frac{2\pi i (k + n/2)}{n/2}}) + e^{\frac{2\pi i k + n/2}{n}} A^{[1]}(e^{\frac{2\pi i (k + n/2)}{n/2}})=\\ &=& A^{[0]}(x_{k + n/2}^2) + x_{k + n/2}^2 A^{[1]}(x_{k + n/2}^2) = A(x_{k+\frac{n}{2}}). \end{array} }

Gdzie w ostatniej równości skorzystaliśmy ze wzoru (1). Widzimy zatem, że algorytm poprawnie oblicza wartość STF dla wielomianu A(x). Równanie rekurencyjne na czas działania procedury STF wygląda następująco:

T(n)=2T(n2)+Θ(n)=Θ(nlogn).

Poniższa animacja przedstawia działanie algorytmu SFT wywołanego dla wielomianu A(x)=x3+2x2+5x+3. Wartości wielomianu A(x) wyznaczone są z wartości wielomianów A[0](x) i A[1](x), a wartości tych wielomianów z wartości wielomianów A[00](x), A[01](x), A[10](x) i A[11](x).

<flash>file=Zasd_ilustr_a.swf|height=500|width=600</flash>

Odwrotna transformata Fouriera

Aby zakończyć konstrukcję algorytmu dla szybkiego mnożenia wielomianów, pozostaje nam pokazanie, jak obliczyć wielomian interpolujący dla zbioru punktów Xn. Obliczenie wykonane w czasie szybkiej transformaty Fouriera możemy przedstawić w postaci macierzowej jako mnożenie macierzy przez wektor (A(x0),A(x1),,A(xn1))T=Vn(a0,a1,,an1)T, gdzie Vn=V(x0,,xn1) jest macierzą Vandermonde'a zawierającą potęgi xj:

(A(x0)A(x1)A(x2)A(xn1))=[x00x01x02x03x0n1x10x11x12x13x1n1x20x21x22x23x2n1xn10xn11xn12xn13xn1n1](a0a1a2an1).

Element macierzy V(x0,,xn1) dany jest jako

(Vn)j,k=V(x0,,xn1)j,k=xjk.

Korzystając z definicji zbioru Xn, otrzymujemy

V(x0,,xn1)j,k=(e2πijn)k=e2πijkn

W celu wykonania operacji odwrotnej do SFT, czyli obliczenia wielomianu interpolacyjnego, musimy wykonać mnożenie Vn1(A(x0),A(x1),,A(xn1))T.

Lemat 2

Macierz Wn zdefiniowana jako:
(Wn)j,k=1ne2πijkn,

jest macierzą odwrotną do macierzy Vn.

Dowód

Pokażemy, że VnWn=I. Rozważmy pozycję (j,k) macierzy VnWn:
(VnWn)j,k=l=0n1(Vn)j,l(Wn)l,k=l=0n1e2πijln1ne2πilkn=l=0n11ne2πl(jk)n=

Jeżeli j=k, to e2πk(jk)n=1 i suma ta jest równa 1. W przeciwnym przypadku możemy skorzystać ze wzoru na sumę szeregu geometrycznego:

=1n1e2πn(jk)n1e2π(jk)n=1n11(jk)1e2π(jk)n=0.

Czyli rzeczywiście VnWn=I.

Porównując postać macierzy Vn oraz macierzy Wn widzimy, że w celu obliczenia transformaty odwrotnej możemy użyć Algorytmu Szybkiej Transformaty Fouriera, musimy tylko zamienić linijkę ωm=e2πin na ωm=e2πin i podzielić otrzymany wynik przez n.

Dzielenie wielomianów

W tej części wykładu skupimy się na problemie dzielenia dwóch wielomianów. Niech A(x) będzie wielomianem stopnia m, a B(x) wielomianem stopnia n. Zakładamy bez straty ogólności, że bn10. W problemie dzielenia wielomianów chcemy obliczyć dwa wielomiany D(x) i R(x) takie, że

A(x)=D(x)B(x)+R(x)      (2)

oraz stopień wielomianu R(x) jest ostro mniejszy niż n. Wielomian D(x) nazywamy wynikiem dzielenia, a wielomian R(x) to reszta z dzielenia. Pierwszy pomysł jaki się od razu nasuwa, to spróbować policzyć odwrotność wielomianu B(x) i przemnożyć przez tę odwrotność strony tego równania. Niestety wielomiany nie mają odwrotności będących wielomianami. Jednak nie pozostajemy tutaj zupełnie bezradni. Możemy rozszerzyć naszą dziedzinę obliczeń tak aby zagwarantować istnienie pewnych odwrotności.

Obliczenia będziemy wykonywać nad zbiorem szeregów formalnych F[[x]] nad ciałem F, patrz Wykład z matematyki dyskretnej o funkcjach tworzących. Dla części elementów F[[x]] istnieją odwrotności. Elementy te są postaci a+xA(x), gdzie a0 i A(x)F[[x]].


Ćwiczenie

Czy umiesz obliczyć odwrotność dla szeregu 1+x?

Ćwiczenie

Czy umiesz obliczyć odwrotność dla szeregu j=0(j+1)xj?


Do wzoru (2) wstawmy x=1z, otrzymamy wtedy:

AR(z)=DR(z)BR(z)+zmn1RR(z)=BR(z)DR(z)modzmn1,

gdzie AR(z)=zmA(1z), DR(z)=zmnD(1z), BR(z)=znB(1z) i RR(z)=zn1R(1z), oznaczają wielomiany otrzymane poprzez odwrócenie kolejności współczynników. Z założenia, że bn10 wiemy, że wielomian BR ma odwrotność nad zbiorem szeregów formalnych. Możemy zapisać więc:

DR(z)=AR(x)(BR(z))1modzmn1.      (3)

Zauważmy, że w celu wyznaczenia DR(z) potrzebujemy tylko mn1 wyrazów szeregu (BR(z))1. Wyższe wyrazy i tak znikną w wyniku wykonania mnożenia modulo zmn1. Pozostało nam teraz tylko pokazać, jak wyznaczyć odwrotność dla szeregu formalnego. Algorytm ten przedstawiony jest poniżej.


Algorytm obliczania pierwszych m wyrazów odwrotnośći szeregu formalnego


 ODWROTNOŚĆ(A(x)=i=0n1aixi,m)
 if m=1 then return 1/a0
 A[0](x)=ODWROTNOŚĆ(A(x),m2)
 return (A[0](x)(A(x)A[0](x)1)A[0](x))modxm

Obliczenie to jest poprawne, ponieważ:

1A(x)(A[0](x)(A(x)A[0](x)1)A[0](x))=A(x)(1A(x)A[0](x))2,

a to jest równe wielokrotności xm. Jeżeli wykorzystamy szybkie mnożenie wielomianów do obliczenia (A[0](x)(A(x)A[0](x)1)A[0](x)), to złożoność tego algorytmu wynosić będzie O(nlogn). Możemy teraz skonstruować algorytm wykonujący dzielenie wielomianu A(x) przez wielomian B(x) w czasie O(mlogm), gdzie m to stopień wielomianu A(x).

Algorytm dzielenia wielomianów


 PODZIEL(A(x),B(x))
  AR(z)=zmA(1z)
  BR(z)=znB(1z)
  (BR(z)1)(z)=ODWROTNOŚĆ(BR(z),mn1)
  DR(z)=AR(x)(BR(z))1modzmn1
  D(x)=zmn1DR(1x)
  R(x)=A(x)D(x)B(x)
  return (D(x),R(x))

Poprawność tego algorytmu wynika wprost ze wzoru (3), a jego złożoność wynosi naturalnie O(nlogn).