<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="fr">
	<id>http://os-vps418.infomaniak.ch:1250/mediawiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Mangala</id>
	<title>Wiki du LAMA (UMR 5127) - Contributions [fr]</title>
	<link rel="self" type="application/atom+xml" href="http://os-vps418.infomaniak.ch:1250/mediawiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Mangala"/>
	<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php/Sp%C3%A9cial:Contributions/Mangala"/>
	<updated>2026-06-10T05:26:35Z</updated>
	<subtitle>Contributions</subtitle>
	<generator>MediaWiki 1.39.4</generator>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17096</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17096"/>
		<updated>2026-05-12T04:21:33Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* A) Fonctionnement */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calculs a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrées qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs étroitement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calculs nécessaires en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt; f(n) &amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cet algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; m &amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt; f(x) = x^2 &amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cette courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karatsuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt; x &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt; b &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt; Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 Résultat &amp;lt;math&amp;gt;= w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Nous avons réutilisé le graphique précédant, cela nous permettra de comparer Toom-Cook avec les autres algorithmes.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que Toom-Cook 3 est plus performant que karatsuba et ainsi que la multiplication classique.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Toom-cook est par conséquent un algorithme qui repousse encore une fois la complexité minimum du produit d’entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice pairs et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(x)=P_{\text{pair}}(x^2) + x \cdot P_{\text{impair}}(x^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt; IFFT(FFT(C)) &amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 1 : Ecrire les deux facteurs sous la forme de polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 2 : Effectuer la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; des deux polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 3 : Faire le produit terme à terme des &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; obtenues&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 4 : Faire l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; du produit&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 5 : Gérer les retenues et écrire le nombre qui en découle&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or, parmi toutes ces étapes, certaines sont négligeable comme l&#039;étape 1 et 5.&lt;br /&gt;
&lt;br /&gt;
Pour connaitre la complexité de l&#039;algorithme, additionnons les complexités des étapes restantes :&lt;br /&gt;
&lt;br /&gt;
Une &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt; car elle fait &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; évaluation sur &amp;lt;math&amp;gt; log(n)&amp;lt;/math&amp;gt; récursions. Dans le cadre de notre algorithme, nous devons faire la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; de nos deux facteurs. Soit &amp;lt;math&amp;gt;2\times \mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Pour le produit terme à terme, nous faisons naturellement &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&lt;br /&gt;
&lt;br /&gt;
Pour finir, l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; a la même complexité que la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt;. Dans le cadre de notre algorithme nous l&#039;emploierons qu&#039;une seule fois, ce qui fait une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Après addition de toutes ces complexités, nous obtenons la complexité réelle de &amp;lt;math&amp;gt;3\mathcal{O}(n\times logn) + \mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
D&#039;après le Master Theorem, nous avons donc &amp;lt;math&amp;gt; f(n) = 3(n\times logn)+n &amp;lt;/math&amp;gt;, cherchons &amp;lt;math&amp;gt;f(n)\in\mathcal{O}(g(n))&amp;lt;/math&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
Nous pouvons ignorer les expressions linéaires ainsi que les constantes multiplicatrices, nous obtenons donc &amp;lt;math&amp;gt;\mathcal{O}(g(n)) = \mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT complexite.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre ne provient pas du fruit de nos recherches personnelles mais illustre bien la comparaison entre la complexité de la FFT et des autres algorithmes.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la FFT est très efficace et devance de loin les autres algorithmes en terme de complexité. &amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les algorithmes les plus efficaces pour calculer le produit de grands entiers reposent aujourd&#039;hui sur des variantes de la transformée de Fourier rapide.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
 &lt;br /&gt;
L&#039;étude de ces algorithmes de multiplication nous a clairement montré l&#039;évolution de la complexité algorithmique permettant de gagner en efficacité lorsque la taille des données augmente.&lt;br /&gt;
&lt;br /&gt;
La multiplication classiqe, en &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;, résulte d&#039;une approche naïve qui devient rapidement inefficace pour les grands entiers ou les grandes matrices. L&#039;algorithme de Karatsuba nous montre qu&#039;organiser de manière intelligente ses calculs algébrique permet parfois de gagner beaucoup de multiplications, et donc de temps. Nous avons ainsi réussi à passer d&#039;une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt; à &amp;lt;math&amp;gt; \mathcal{O}(n^{log_2(3)}) &amp;lt;/math&amp;gt;, soit environ &amp;lt;math&amp;gt;\mathcal{O}(n^{1/585})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Des méthodes encore plus avancées comme utilise l&#039;algorithme de Toom-Cook-3 continue dans cette idées de divisions d&#039;entiers en blocs plus petits, tandis que la transformée de Fourier rapide change complètement de point de vue. En effet la FFT n&#039;utilise pas la représentation classique des entiers mais fait appel à une vision polynomiale, ce qui transforme la convolution classique en multiplication terme à terme, ce qui lui permet d&#039;atteindre une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Dans le cas des matrices, les mêmes logiques apparaissent comme par exemple avec l&#039;algorithme de Strassen qui se rapproche très fortement de Karatsuba en combinant de manière astucieuse les parties d&#039;entiers qui avaient été précédemment découpée.&lt;br /&gt;
&lt;br /&gt;
On remarque néanmoins que la majorité des algorithmes efficaces s&#039;orientent vers une approche récursive et non itérative. Néanmoins, nous avons pu trouver certaines de ces implémentations (comme par exemple la FFT) en itérative. &lt;br /&gt;
&lt;br /&gt;
Avec ces recherches, nous comprenons que la réorganisations des calculs algébriques peuvent aider à gagner en complexité mais que pour faire de réels progrès, il nous faudra surement changer notre point de vue une nouvelle fois, et aborder le problème sous un angle nouveau comme le font dors et déjà les algorithmes les plus performants.&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
Utilisation du site de production graphique &amp;quot;Desmos&amp;quot; : https://www.desmos.com/calculator?lang=fr&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : &lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=PZ2gdWdKHAA &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&amp;lt;ul&amp;gt; &lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://fr.wikipedia.org/wiki/Interpolation_polynomiale &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17095</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17095"/>
		<updated>2026-05-11T22:05:56Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* A) Fonctionnement */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calculs a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrées qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs étroitement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calculs nécessaires en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt; f(n) &amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cet algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; m &amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt; f(x) = x^2 &amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cette courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karatsuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt; x &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt; b &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt; Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 Résultat &amp;lt;math&amp;gt;= w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Nous avons réutilisé le graphique précédant, cela nous permettra de comparer Toom-Cook avec les autres algorithmes.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que Toom-Cook 3 est plus performant que karatsuba et ainsi que la multiplication classique.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Toom-cook est par conséquent un algorithme qui repousse encore une fois la complexité minimum du produit d’entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice pairs et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(x)=P_{\text{pair}}(x^2) + x \cdot P_{\text{impair}}(x^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 1 : Ecrire les deux facteurs sous la forme de polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 2 : Effectuer la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; des deux polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 3 : Faire le produit terme à terme des &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; obtenues&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 4 : Faire l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; du produit&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 5 : Gérer les retenues et écrire le nombre qui en découle&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or, parmi toutes ces étapes, certaines sont négligeable comme l&#039;étape 1 et 5.&lt;br /&gt;
&lt;br /&gt;
Pour connaitre la complexité de l&#039;algorithme, additionnons les complexités des étapes restantes :&lt;br /&gt;
&lt;br /&gt;
Une &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt; car elle fait &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; évaluation sur &amp;lt;math&amp;gt; log(n)&amp;lt;/math&amp;gt; récursions. Dans le cadre de notre algorithme, nous devons faire la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; de nos deux facteurs. Soit &amp;lt;math&amp;gt;2\times \mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Pour le produit terme à terme, nous faisons naturellement &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&lt;br /&gt;
&lt;br /&gt;
Pour finir, l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; a la même complexité que la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt;. Dans le cadre de notre algorithme nous l&#039;emploierons qu&#039;une seule fois, ce qui fait une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Après addition de toutes ces complexités, nous obtenons la complexité réelle de &amp;lt;math&amp;gt;3\mathcal{O}(n\times logn) + \mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
D&#039;après le Master Theorem, nous avons donc &amp;lt;math&amp;gt; f(n) = 3(n\times logn)+n &amp;lt;/math&amp;gt;, cherchons &amp;lt;math&amp;gt;f(n)\in\mathcal{O}(g(n))&amp;lt;/math&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
Nous pouvons ignorer les expressions linéaires ainsi que les constantes multiplicatrices, nous obtenons donc &amp;lt;math&amp;gt;\mathcal{O}(g(n)) = \mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT complexite.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre ne provient pas du fruit de nos recherches personnelles mais illustre bien la comparaison entre la complexité de la FFT et des autres algorithmes.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la FFT est très efficace et devance de loin les autres algorithmes en terme de complexité. &amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les algorithmes les plus efficaces pour calculer le produit de grands entiers reposent aujourd&#039;hui sur des variantes de la transformée de Fourier rapide.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
 &lt;br /&gt;
L&#039;étude de ces algorithmes de multiplication nous a clairement montré l&#039;évolution de la complexité algorithmique permettant de gagner en efficacité lorsque la taille des données augmente.&lt;br /&gt;
&lt;br /&gt;
La multiplication classiqe, en &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;, résulte d&#039;une approche naïve qui devient rapidement inefficace pour les grands entiers ou les grandes matrices. L&#039;algorithme de Karatsuba nous montre qu&#039;organiser de manière intelligente ses calculs algébrique permet parfois de gagner beaucoup de multiplications, et donc de temps. Nous avons ainsi réussi à passer d&#039;une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt; à &amp;lt;math&amp;gt; \mathcal{O}(n^{log_2(3)}) &amp;lt;/math&amp;gt;, soit environ &amp;lt;math&amp;gt;\mathcal{O}(n^{1/585})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Des méthodes encore plus avancées comme utilise l&#039;algorithme de Toom-Cook-3 continue dans cette idées de divisions d&#039;entiers en blocs plus petits, tandis que la transformée de Fourier rapide change complètement de point de vue. En effet la FFT n&#039;utilise pas la représentation classique des entiers mais fait appel à une vision polynomiale, ce qui transforme la convolution classique en multiplication terme à terme, ce qui lui permet d&#039;atteindre une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Dans le cas des matrices, les mêmes logiques apparaissent comme par exemple avec l&#039;algorithme de Strassen qui se rapproche très fortement de Karatsuba en combinant de manière astucieuse les parties d&#039;entiers qui avaient été précédemment découpée.&lt;br /&gt;
&lt;br /&gt;
On remarque néanmoins que la majorité des algorithmes efficaces s&#039;orientent vers une approche récursive et non itérative. Néanmoins, nous avons pu trouver certaines de ces implémentations (comme par exemple la FFT) en itérative. &lt;br /&gt;
&lt;br /&gt;
Avec ces recherches, nous comprenons que la réorganisations des calculs algébriques peuvent aider à gagner en complexité mais que pour faire de réels progrès, il nous faudra surement changer notre point de vue une nouvelle fois, et aborder le problème sous un angle nouveau comme le font dors et déjà les algorithmes les plus performants.&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
Utilisation du site de production graphique &amp;quot;Desmos&amp;quot; : https://www.desmos.com/calculator?lang=fr&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : &lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=PZ2gdWdKHAA &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&amp;lt;ul&amp;gt; &lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://fr.wikipedia.org/wiki/Interpolation_polynomiale &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17094</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17094"/>
		<updated>2026-05-11T22:03:01Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* Conclusion */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calculs a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrées qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs étroitement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calculs nécessaires en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt; f(n) &amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cet algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; m &amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt; f(x) = x^2 &amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cette courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karatsuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt; x &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt; b &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt; Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 Résultat &amp;lt;math&amp;gt;= w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Nous avons réutilisé le graphique précédant, cela nous permettra de comparer Toom-Cook avec les autres algorithmes.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que Toom-Cook 3 est plus performant que karatsuba et ainsi que la multiplication classique.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Toom-cook est par conséquent un algorithme qui repousse encore une fois la complexité minimum du produit d’entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice pairs et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(x)=P_{\text{pair}}(x^2) + x \cdot P_{\text{impair}}(x^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 1 : Ecrire les deux facteurs sous la forme de polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 2 : Effectuer la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; des deux polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 3 : Faire le produit terme à terme des &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; obtenues&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 4 : Faire l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; du produit&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 5 : Gérer les retenues et écrire le nombre qui en découle&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or, parmi toutes ces étapes, certaines sont négligeable comme l&#039;étape 1 et 5.&lt;br /&gt;
&lt;br /&gt;
Pour connaitre la complexité de l&#039;algorithme, additionnons les complexités des étapes restantes :&lt;br /&gt;
&lt;br /&gt;
Une &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt; car elle fait &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; évaluation sur &amp;lt;math&amp;gt; log(n)&amp;lt;/math&amp;gt; récursions. Dans le cadre de notre algorithme, nous devons faire la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; de nos deux facteurs. Soit &amp;lt;math&amp;gt;2\times \mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Pour le produit terme à terme, nous faisons naturellement &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&lt;br /&gt;
&lt;br /&gt;
Pour finir, l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; a la même complexité que la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt;. Dans le cadre de notre algorithme nous l&#039;emploierons qu&#039;une seule fois, ce qui fait une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Après addition de toutes ces complexités, nous obtenons la complexité réelle de &amp;lt;math&amp;gt;3\mathcal{O}(n\times logn) + \mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
D&#039;après le Master Theorem, nous avons donc &amp;lt;math&amp;gt; f(n) = 3(n\times logn)+n &amp;lt;/math&amp;gt;, cherchons &amp;lt;math&amp;gt;f(n)\in\mathcal{O}(g(n))&amp;lt;/math&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
Nous pouvons ignorer les expressions linéaires ainsi que les constantes multiplicatrices, nous obtenons donc &amp;lt;math&amp;gt;\mathcal{O}(g(n)) = \mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT complexite.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre ne provient pas du fruit de nos recherches personnelles mais illustre bien la comparaison entre la complexité de la FFT et des autres algorithmes.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la FFT est très efficace et devance de loin les autres algorithmes en terme de complexité. &amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les algorithmes les plus efficaces pour calculer le produit de grands entiers reposent aujourd&#039;hui sur des variantes de la transformée de Fourier rapide.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
 &lt;br /&gt;
L&#039;étude de ces algorithmes de multiplication nous a clairement montré l&#039;évolution de la complexité algorithmique permettant de gagner en efficacité lorsque la taille des données augmente.&lt;br /&gt;
&lt;br /&gt;
La multiplication classiqe, en &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;, résulte d&#039;une approche naïve qui devient rapidement inefficace pour les grands entiers ou les grandes matrices. L&#039;algorithme de Karatsuba nous montre qu&#039;organiser de manière intelligente ses calculs algébrique permet parfois de gagner beaucoup de multiplications, et donc de temps. Nous avons ainsi réussi à passer d&#039;une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt; à &amp;lt;math&amp;gt; \mathcal{O}(n^{log_2(3)}) &amp;lt;/math&amp;gt;, soit environ &amp;lt;math&amp;gt;\mathcal{O}(n^{1/585})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Des méthodes encore plus avancées comme utilise l&#039;algorithme de Toom-Cook-3 continue dans cette idées de divisions d&#039;entiers en blocs plus petits, tandis que la transformée de Fourier rapide change complètement de point de vue. En effet la FFT n&#039;utilise pas la représentation classique des entiers mais fait appel à une vision polynomiale, ce qui transforme la convolution classique en multiplication terme à terme, ce qui lui permet d&#039;atteindre une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Dans le cas des matrices, les mêmes logiques apparaissent comme par exemple avec l&#039;algorithme de Strassen qui se rapproche très fortement de Karatsuba en combinant de manière astucieuse les parties d&#039;entiers qui avaient été précédemment découpée.&lt;br /&gt;
&lt;br /&gt;
On remarque néanmoins que la majorité des algorithmes efficaces s&#039;orientent vers une approche récursive et non itérative. Néanmoins, nous avons pu trouver certaines de ces implémentations (comme par exemple la FFT) en itérative. &lt;br /&gt;
&lt;br /&gt;
Avec ces recherches, nous comprenons que la réorganisations des calculs algébriques peuvent aider à gagner en complexité mais que pour faire de réels progrès, il nous faudra surement changer notre point de vue une nouvelle fois, et aborder le problème sous un angle nouveau comme le font dors et déjà les algorithmes les plus performants.&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
Utilisation du site de production graphique &amp;quot;Desmos&amp;quot; : https://www.desmos.com/calculator?lang=fr&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : &lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=PZ2gdWdKHAA &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&amp;lt;ul&amp;gt; &lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://fr.wikipedia.org/wiki/Interpolation_polynomiale &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17093</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17093"/>
		<updated>2026-05-11T21:44:31Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* B) Graphique */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calculs a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrées qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs étroitement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calculs nécessaires en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt; f(n) &amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cet algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; m &amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt; f(x) = x^2 &amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cette courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karatsuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt; x &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt; b &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt; Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 Résultat &amp;lt;math&amp;gt;= w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Nous avons réutilisé le graphique précédant, cela nous permettra de comparer Toom-Cook avec les autres algorithmes.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que Toom-Cook 3 est plus performant que karatsuba et ainsi que la multiplication classique.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Toom-cook est par conséquent un algorithme qui repousse encore une fois la complexité minimum du produit d’entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice pairs et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(x)=P_{\text{pair}}(x^2) + x \cdot P_{\text{impair}}(x^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 1 : Ecrire les deux facteurs sous la forme de polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 2 : Effectuer la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; des deux polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 3 : Faire le produit terme à terme des &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; obtenues&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 4 : Faire l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; du produit&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 5 : Gérer les retenues et écrire le nombre qui en découle&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or, parmi toutes ces étapes, certaines sont négligeable comme l&#039;étape 1 et 5.&lt;br /&gt;
&lt;br /&gt;
Pour connaitre la complexité de l&#039;algorithme, additionnons les complexités des étapes restantes :&lt;br /&gt;
&lt;br /&gt;
Une &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt; car elle fait &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; évaluation sur &amp;lt;math&amp;gt; log(n)&amp;lt;/math&amp;gt; récursions. Dans le cadre de notre algorithme, nous devons faire la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; de nos deux facteurs. Soit &amp;lt;math&amp;gt;2\times \mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Pour le produit terme à terme, nous faisons naturellement &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&lt;br /&gt;
&lt;br /&gt;
Pour finir, l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; a la même complexité que la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt;. Dans le cadre de notre algorithme nous l&#039;emploierons qu&#039;une seule fois, ce qui fait une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Après addition de toutes ces complexités, nous obtenons la complexité réelle de &amp;lt;math&amp;gt;3\mathcal{O}(n\times logn) + \mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
D&#039;après le Master Theorem, nous avons donc &amp;lt;math&amp;gt; f(n) = 3(n\times logn)+n &amp;lt;/math&amp;gt;, cherchons &amp;lt;math&amp;gt;f(n)\in\mathcal{O}(g(n))&amp;lt;/math&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
Nous pouvons ignorer les expressions linéaires ainsi que les constantes multiplicatrices, nous obtenons donc &amp;lt;math&amp;gt;\mathcal{O}(g(n)) = \mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT complexite.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre ne provient pas du fruit de nos recherches personnelles mais illustre bien la comparaison entre la complexité de la FFT et des autres algorithmes.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la FFT est très efficace et devance de loin les autres algorithmes en terme de complexité. &amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les algorithmes les plus efficaces pour calculer le produit de grands entiers reposent aujourd&#039;hui sur des variantes de la transformée de Fourier rapide.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
 &lt;br /&gt;
L&#039;étude de ces algorithmes de multiplication nous a clairement montré l&#039;évolution de la complexité algorithmique permettant de gagner en efficacité lorsque la taille des données augmente.&lt;br /&gt;
&lt;br /&gt;
La multiplication classiqe, en &amp;lt;math&amp;gt; \mathcal{O}(n^2) &amp;lt;/math&amp;gt;, résulte d&#039;une approche naïve qui devient rapidement inefficace pour les grands entiers ou les grandes matrices. L&#039;algorithme de Karatsuba nous montre qu&#039;organiser de manière intelligente ses calculs algébrique permet parfois de gagner beaucoup de multiplications, et donc de temps. Nous avons ainsi réussi à passer d&#039;une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt; à &amp;lt;math&amp;gt; \mathcal{O}(n^{log_2(3)}) &amp;lt;/math&amp;gt;, soit environ &amp;lt;math&amp;gt;\mathcal{O}(n^{1/585})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Des méthodes encore plus avancées comme utilise l&#039;algorithme de Toom-Cook-3 continue dans cette idées de divisions d&#039;entiers en blocs plus petits, tandis que la transformée de Fourier rapide change complètement de point de vue. En effet la FFT n&#039;utilise pas la représentation classique des entiers mais fait appel à une vision polynomiale, ce qui transforme la convolution classique en multiplication terme à terme, ce qui lui permet d&#039;atteindre une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Dans le cas des matrices, les mêmes logiques apparaissent comme par exemple avec l&#039;algorithme de Strassen qui se rapproche très fortement de Karatsuba en combinant de manière astucieuse les parties d&#039;entiers qui avaient été précédemment découpée.&lt;br /&gt;
&lt;br /&gt;
On remarque néanmoins que la majorité des algorithmes efficaces s&#039;orientent vers une approche récursive et non itérative. Néanmoins, nous avons pu trouver certaines de ces implémentations (comme par exemple la FFT) en itérative. &lt;br /&gt;
&lt;br /&gt;
Avec ces recherches, nous comprenons que la réorganisations des calculs algébriques peuvent aider à gagner en complexité mais que pour faire de réels progrès, il nous faudra surement changer notre point de vue une nouvelle fois, et aborder le problème sous un angle nouveau comme le font dors et déjà les algorithmes les plus performants.&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
Utilisation du site de production graphique &amp;quot;Desmos&amp;quot; : https://www.desmos.com/calculator?lang=fr&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : &lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=PZ2gdWdKHAA &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&amp;lt;ul&amp;gt; &lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://fr.wikipedia.org/wiki/Interpolation_polynomiale &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17092</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17092"/>
		<updated>2026-05-11T21:27:33Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* Conclusion */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calculs a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrées qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs étroitement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calculs nécessaires en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt; f(n) &amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cet algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; m &amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt; f(x) = x^2 &amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cette courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karatsuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt; x &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt; b &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt; Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 Résultat &amp;lt;math&amp;gt;= w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice pairs et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(x)=P_{\text{pair}}(x^2) + x \cdot P_{\text{impair}}(x^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 1 : Ecrire les deux facteurs sous la forme de polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 2 : Effectuer la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; des deux polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 3 : Faire le produit terme à terme des &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; obtenues&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 4 : Faire l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; du produit&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 5 : Gérer les retenues et écrire le nombre qui en découle&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or, parmi toutes ces étapes, certaines sont négligeable comme l&#039;étape 1 et 5.&lt;br /&gt;
&lt;br /&gt;
Pour connaitre la complexité de l&#039;algorithme, additionnons les complexités des étapes restantes :&lt;br /&gt;
&lt;br /&gt;
Une &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt; car elle fait &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; évaluation sur &amp;lt;math&amp;gt; log(n)&amp;lt;/math&amp;gt; récursions. Dans le cadre de notre algorithme, nous devons faire la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; de nos deux facteurs. Soit &amp;lt;math&amp;gt;2\times \mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Pour le produit terme à terme, nous faisons naturellement &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&lt;br /&gt;
&lt;br /&gt;
Pour finir, l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; a la même complexité que la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt;. Dans le cadre de notre algorithme nous l&#039;emploierons qu&#039;une seule fois, ce qui fait une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Après addition de toutes ces complexités, nous obtenons la complexité réelle de &amp;lt;math&amp;gt;3\mathcal{O}(n\times logn) + \mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
D&#039;après le Master Theorem, nous avons donc &amp;lt;math&amp;gt; f(n) = 3(n\times logn)+n &amp;lt;/math&amp;gt;, cherchons &amp;lt;math&amp;gt;f(n)\in\mathcal{O}(g(n))&amp;lt;/math&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
Nous pouvons ignorer les expressions linéaires ainsi que les constantes multiplicatrices, nous obtenons donc &amp;lt;math&amp;gt;\mathcal{O}(g(n)) = \mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT complexite.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre ne provient pas du fruit de nos recherches personnelles mais illustre bien la comparaison entre la complexité de la FFT et des autres algorithmes.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la FFT est très efficace et devance de loin les autres algorithmes en terme de complexité. &amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les algorithmes les plus efficaces pour calculer le produit de grands entiers reposent aujourd&#039;hui sur des variantes de la transformée de Fourier rapide.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
 &lt;br /&gt;
L&#039;étude de ces algorithmes de multiplication nous a clairement montré l&#039;évolution de la complexité algorithmique permettant de gagner en efficacité lorsque la taille des données augmente.&lt;br /&gt;
&lt;br /&gt;
La multiplication classiqe, en &amp;lt;math&amp;gt; \mathcal{O}(n^2) &amp;lt;/math&amp;gt;, résulte d&#039;une approche naïve qui devient rapidement inefficace pour les grands entiers ou les grandes matrices. L&#039;algorithme de Karatsuba nous montre qu&#039;organiser de manière intelligente ses calculs algébrique permet parfois de gagner beaucoup de multiplications, et donc de temps. Nous avons ainsi réussi à passer d&#039;une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt; à &amp;lt;math&amp;gt; \mathcal{O}(n^{log_2(3)}) &amp;lt;/math&amp;gt;, soit environ &amp;lt;math&amp;gt;\mathcal{O}(n^{1/585})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Des méthodes encore plus avancées comme utilise l&#039;algorithme de Toom-Cook-3 continue dans cette idées de divisions d&#039;entiers en blocs plus petits, tandis que la transformée de Fourier rapide change complètement de point de vue. En effet la FFT n&#039;utilise pas la représentation classique des entiers mais fait appel à une vision polynomiale, ce qui transforme la convolution classique en multiplication terme à terme, ce qui lui permet d&#039;atteindre une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Dans le cas des matrices, les mêmes logiques apparaissent comme par exemple avec l&#039;algorithme de Strassen qui se rapproche très fortement de Karatsuba en combinant de manière astucieuse les parties d&#039;entiers qui avaient été précédemment découpée.&lt;br /&gt;
&lt;br /&gt;
On remarque néanmoins que la majorité des algorithmes efficaces s&#039;orientent vers une approche récursive et non itérative. Néanmoins, nous avons pu trouver certaines de ces implémentations (comme par exemple la FFT) en itérative. &lt;br /&gt;
&lt;br /&gt;
Avec ces recherches, nous comprenons que la réorganisations des calculs algébriques peuvent aider à gagner en complexité mais que pour faire de réels progrès, il nous faudra surement changer notre point de vue une nouvelle fois, et aborder le problème sous un angle nouveau comme le font dors et déjà les algorithmes les plus performants.&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
Utilisation du site de production graphique &amp;quot;Desmos&amp;quot; : https://www.desmos.com/calculator?lang=fr&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : &lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=PZ2gdWdKHAA &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&amp;lt;ul&amp;gt; &lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://fr.wikipedia.org/wiki/Interpolation_polynomiale &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17091</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17091"/>
		<updated>2026-05-11T21:27:21Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* Conclusion */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calculs a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrées qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs étroitement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calculs nécessaires en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt; f(n) &amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cet algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; m &amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt; f(x) = x^2 &amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cette courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karatsuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt; x &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt; b &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt; Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 Résultat &amp;lt;math&amp;gt;= w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice pairs et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(x)=P_{\text{pair}}(x^2) + x \cdot P_{\text{impair}}(x^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 1 : Ecrire les deux facteurs sous la forme de polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 2 : Effectuer la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; des deux polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 3 : Faire le produit terme à terme des &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; obtenues&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 4 : Faire l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; du produit&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 5 : Gérer les retenues et écrire le nombre qui en découle&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or, parmi toutes ces étapes, certaines sont négligeable comme l&#039;étape 1 et 5.&lt;br /&gt;
&lt;br /&gt;
Pour connaitre la complexité de l&#039;algorithme, additionnons les complexités des étapes restantes :&lt;br /&gt;
&lt;br /&gt;
Une &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt; car elle fait &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; évaluation sur &amp;lt;math&amp;gt; log(n)&amp;lt;/math&amp;gt; récursions. Dans le cadre de notre algorithme, nous devons faire la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; de nos deux facteurs. Soit &amp;lt;math&amp;gt;2\times \mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Pour le produit terme à terme, nous faisons naturellement &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&lt;br /&gt;
&lt;br /&gt;
Pour finir, l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; a la même complexité que la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt;. Dans le cadre de notre algorithme nous l&#039;emploierons qu&#039;une seule fois, ce qui fait une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Après addition de toutes ces complexités, nous obtenons la complexité réelle de &amp;lt;math&amp;gt;3\mathcal{O}(n\times logn) + \mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
D&#039;après le Master Theorem, nous avons donc &amp;lt;math&amp;gt; f(n) = 3(n\times logn)+n &amp;lt;/math&amp;gt;, cherchons &amp;lt;math&amp;gt;f(n)\in\mathcal{O}(g(n))&amp;lt;/math&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
Nous pouvons ignorer les expressions linéaires ainsi que les constantes multiplicatrices, nous obtenons donc &amp;lt;math&amp;gt;\mathcal{O}(g(n)) = \mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT complexite.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre ne provient pas du fruit de nos recherches personnelles mais illustre bien la comparaison entre la complexité de la FFT et des autres algorithmes.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la FFT est très efficace et devance de loin les autres algorithmes en terme de complexité. &amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les algorithmes les plus efficaces pour calculer le produit de grands entiers reposent aujourd&#039;hui sur des variantes de la transformée de Fourier rapide.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
 &lt;br /&gt;
L&#039;étude de ces algorithmes de multiplication nous a clairement montré l&#039;évolution de la complexité algorithmique permettant de gagner en efficacité lorsque la taille des données augmente.&lt;br /&gt;
&lt;br /&gt;
La multiplication classiqe, en &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;, résulte d&#039;une approche naïve qui devient rapidement inefficace pour les grands entiers ou les grandes matrices. L&#039;algorithme de Karatsuba nous montre qu&#039;organiser de manière intelligente ses calculs algébrique permet parfois de gagner beaucoup de multiplications, et donc de temps. Nous avons ainsi réussi à passer d&#039;une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt; à &amp;lt;math&amp;gt; \mathcal{O}(n^{log_2(3)}) &amp;lt;/math&amp;gt;, soit environ &amp;lt;math&amp;gt;\mathcal{O}(n^{1/585})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Des méthodes encore plus avancées comme utilise l&#039;algorithme de Toom-Cook-3 continue dans cette idées de divisions d&#039;entiers en blocs plus petits, tandis que la transformée de Fourier rapide change complètement de point de vue. En effet la FFT n&#039;utilise pas la représentation classique des entiers mais fait appel à une vision polynomiale, ce qui transforme la convolution classique en multiplication terme à terme, ce qui lui permet d&#039;atteindre une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Dans le cas des matrices, les mêmes logiques apparaissent comme par exemple avec l&#039;algorithme de Strassen qui se rapproche très fortement de Karatsuba en combinant de manière astucieuse les parties d&#039;entiers qui avaient été précédemment découpée.&lt;br /&gt;
&lt;br /&gt;
On remarque néanmoins que la majorité des algorithmes efficaces s&#039;orientent vers une approche récursive et non itérative. Néanmoins, nous avons pu trouver certaines de ces implémentations (comme par exemple la FFT) en itérative. &lt;br /&gt;
&lt;br /&gt;
Avec ces recherches, nous comprenons que la réorganisations des calculs algébriques peuvent aider à gagner en complexité mais que pour faire de réels progrès, il nous faudra surement changer notre point de vue une nouvelle fois, et aborder le problème sous un angle nouveau comme le font dors et déjà les algorithmes les plus performants.&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
Utilisation du site de production graphique &amp;quot;Desmos&amp;quot; : https://www.desmos.com/calculator?lang=fr&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : &lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=PZ2gdWdKHAA &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&amp;lt;ul&amp;gt; &lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://fr.wikipedia.org/wiki/Interpolation_polynomiale &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17090</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17090"/>
		<updated>2026-05-11T21:22:27Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* B) Graphique */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calculs a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrées qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs étroitement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calculs nécessaires en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt; f(n) &amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cet algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; m &amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt; f(x) = x^2 &amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cette courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karatsuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt; x &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt; b &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt; Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 Résultat &amp;lt;math&amp;gt;= w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice pairs et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(x)=P_{\text{pair}}(x^2) + x \cdot P_{\text{impair}}(x^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 1 : Ecrire les deux facteurs sous la forme de polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 2 : Effectuer la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; des deux polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 3 : Faire le produit terme à terme des &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; obtenues&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 4 : Faire l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; du produit&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 5 : Gérer les retenues et écrire le nombre qui en découle&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or, parmi toutes ces étapes, certaines sont négligeable comme l&#039;étape 1 et 5.&lt;br /&gt;
&lt;br /&gt;
Pour connaitre la complexité de l&#039;algorithme, additionnons les complexités des étapes restantes :&lt;br /&gt;
&lt;br /&gt;
Une &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt; car elle fait &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; évaluation sur &amp;lt;math&amp;gt; log(n)&amp;lt;/math&amp;gt; récursions. Dans le cadre de notre algorithme, nous devons faire la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; de nos deux facteurs. Soit &amp;lt;math&amp;gt;2\times \mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Pour le produit terme à terme, nous faisons naturellement &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&lt;br /&gt;
&lt;br /&gt;
Pour finir, l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; a la même complexité que la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt;. Dans le cadre de notre algorithme nous l&#039;emploierons qu&#039;une seule fois, ce qui fait une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Après addition de toutes ces complexités, nous obtenons la complexité réelle de &amp;lt;math&amp;gt;3\mathcal{O}(n\times logn) + \mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
D&#039;après le Master Theorem, nous avons donc &amp;lt;math&amp;gt; f(n) = 3(n\times logn)+n &amp;lt;/math&amp;gt;, cherchons &amp;lt;math&amp;gt;f(n)\in\mathcal{O}(g(n))&amp;lt;/math&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
Nous pouvons ignorer les expressions linéaires ainsi que les constantes multiplicatrices, nous obtenons donc &amp;lt;math&amp;gt;\mathcal{O}(g(n)) = \mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT complexite.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre ne provient pas du fruit de nos recherches personnelles mais illustre bien la comparaison entre la complexité de la FFT et des autres algorithmes.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la FFT est très efficace et devance de loin les autres algorithmes en terme de complexité. &amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les algorithmes les plus efficaces pour calculer le produit de grands entiers reposent aujourd&#039;hui sur des variantes de la transformée de Fourier rapide.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
 &lt;br /&gt;
L&#039;étude de ces algorithmes de multiplication nous a clairement montré l&#039;évolution de la complexité algorithmique permettant de gagner en efficacité lorsque la taille des données augmente.&lt;br /&gt;
&lt;br /&gt;
La multiplication classiqe, en &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;, résulte d&#039;une approche naïve qui devient rapidement inefficace pour les grands entiers ou les grandes matrices. L&#039;algorithme de Karatsuba nous montre qu&#039;organiser de manière intelligente ses calculs algébrique permet parfois de gagner beaucoup de multiplications, et donc de temps. Nous avons ainsi réussi à passer d&#039;une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt; à &amp;lt;math&amp;gt;\mathcal{O}(n^{log_2(3)})&amp;lt;/math&amp;gt;, soit environ &amp;lt;math&amp;gt;\mathcal{O}(n^{1/585})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Des méthodes encore plus avancées comme utilise l&#039;algorithme de Toom-Cook-3 continue dans cette idées de divisions d&#039;entiers en blocs plus petits, tandis que la transformée de Fourier rapide change complètement de point de vue. En effet la FFT n&#039;utilise pas la représentation classique des entiers mais fait appel à une vision polynomiale, ce qui transforme la convolution classique en multiplication terme à terme, ce qui lui permet d&#039;atteindre une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Dans le cas des matrices, les mêmes logiques apparaissent comme par exemple avec l&#039;algorithme de Strassen qui se rapproche très fortement de Karatsuba en combinant de manière astucieuse les parties d&#039;entiers qui avaient été précédemment découpée.&lt;br /&gt;
&lt;br /&gt;
On remarque néanmoins que la majorité des algorithmes efficaces s&#039;orientent vers une approche récursive et non itérative. Néanmoins, nous avons pu trouver certaines de ces implémentations (comme par exemple la FFT) en itérative. &lt;br /&gt;
&lt;br /&gt;
Avec ces recherches, nous comprenons que la réorganisations des calculs algébriques peuvent aider à gagner en complexité mais que pour faire de réels progrès, il nous faudra surement changer notre point de vue une nouvelle fois, et aborder le problème sous un angle nouveau comme le font dors et déjà les algorithmes les plus performants.&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
Utilisation du site de production graphique &amp;quot;Desmos&amp;quot; : https://www.desmos.com/calculator?lang=fr&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : &lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=PZ2gdWdKHAA &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&amp;lt;ul&amp;gt; &lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://fr.wikipedia.org/wiki/Interpolation_polynomiale &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17089</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17089"/>
		<updated>2026-05-11T21:22:05Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* A) Fonctionnement */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calculs a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrées qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs étroitement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calculs nécessaires en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt; f(n) &amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cet algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; m &amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt; f(x) = x^2 &amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karatsuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt; x &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt; b &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt; Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 Résultat &amp;lt;math&amp;gt;= w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice pairs et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(x)=P_{\text{pair}}(x^2) + x \cdot P_{\text{impair}}(x^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 1 : Ecrire les deux facteurs sous la forme de polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 2 : Effectuer la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; des deux polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 3 : Faire le produit terme à terme des &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; obtenues&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 4 : Faire l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; du produit&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 5 : Gérer les retenues et écrire le nombre qui en découle&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or, parmi toutes ces étapes, certaines sont négligeable comme l&#039;étape 1 et 5.&lt;br /&gt;
&lt;br /&gt;
Pour connaitre la complexité de l&#039;algorithme, additionnons les complexités des étapes restantes :&lt;br /&gt;
&lt;br /&gt;
Une &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt; car elle fait &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; évaluation sur &amp;lt;math&amp;gt; log(n)&amp;lt;/math&amp;gt; récursions. Dans le cadre de notre algorithme, nous devons faire la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; de nos deux facteurs. Soit &amp;lt;math&amp;gt;2\times \mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Pour le produit terme à terme, nous faisons naturellement &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&lt;br /&gt;
&lt;br /&gt;
Pour finir, l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; a la même complexité que la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt;. Dans le cadre de notre algorithme nous l&#039;emploierons qu&#039;une seule fois, ce qui fait une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Après addition de toutes ces complexités, nous obtenons la complexité réelle de &amp;lt;math&amp;gt;3\mathcal{O}(n\times logn) + \mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
D&#039;après le Master Theorem, nous avons donc &amp;lt;math&amp;gt; f(n) = 3(n\times logn)+n &amp;lt;/math&amp;gt;, cherchons &amp;lt;math&amp;gt;f(n)\in\mathcal{O}(g(n))&amp;lt;/math&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
Nous pouvons ignorer les expressions linéaires ainsi que les constantes multiplicatrices, nous obtenons donc &amp;lt;math&amp;gt;\mathcal{O}(g(n)) = \mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT complexite.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre ne provient pas du fruit de nos recherches personnelles mais illustre bien la comparaison entre la complexité de la FFT et des autres algorithmes.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la FFT est très efficace et devance de loin les autres algorithmes en terme de complexité. &amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les algorithmes les plus efficaces pour calculer le produit de grands entiers reposent aujourd&#039;hui sur des variantes de la transformée de Fourier rapide.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
 &lt;br /&gt;
L&#039;étude de ces algorithmes de multiplication nous a clairement montré l&#039;évolution de la complexité algorithmique permettant de gagner en efficacité lorsque la taille des données augmente.&lt;br /&gt;
&lt;br /&gt;
La multiplication classiqe, en &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;, résulte d&#039;une approche naïve qui devient rapidement inefficace pour les grands entiers ou les grandes matrices. L&#039;algorithme de Karatsuba nous montre qu&#039;organiser de manière intelligente ses calculs algébrique permet parfois de gagner beaucoup de multiplications, et donc de temps. Nous avons ainsi réussi à passer d&#039;une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt; à &amp;lt;math&amp;gt;\mathcal{O}(n^{log_2(3)})&amp;lt;/math&amp;gt;, soit environ &amp;lt;math&amp;gt;\mathcal{O}(n^{1/585})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Des méthodes encore plus avancées comme utilise l&#039;algorithme de Toom-Cook-3 continue dans cette idées de divisions d&#039;entiers en blocs plus petits, tandis que la transformée de Fourier rapide change complètement de point de vue. En effet la FFT n&#039;utilise pas la représentation classique des entiers mais fait appel à une vision polynomiale, ce qui transforme la convolution classique en multiplication terme à terme, ce qui lui permet d&#039;atteindre une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Dans le cas des matrices, les mêmes logiques apparaissent comme par exemple avec l&#039;algorithme de Strassen qui se rapproche très fortement de Karatsuba en combinant de manière astucieuse les parties d&#039;entiers qui avaient été précédemment découpée.&lt;br /&gt;
&lt;br /&gt;
On remarque néanmoins que la majorité des algorithmes efficaces s&#039;orientent vers une approche récursive et non itérative. Néanmoins, nous avons pu trouver certaines de ces implémentations (comme par exemple la FFT) en itérative. &lt;br /&gt;
&lt;br /&gt;
Avec ces recherches, nous comprenons que la réorganisations des calculs algébriques peuvent aider à gagner en complexité mais que pour faire de réels progrès, il nous faudra surement changer notre point de vue une nouvelle fois, et aborder le problème sous un angle nouveau comme le font dors et déjà les algorithmes les plus performants.&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
Utilisation du site de production graphique &amp;quot;Desmos&amp;quot; : https://www.desmos.com/calculator?lang=fr&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : &lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=PZ2gdWdKHAA &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&amp;lt;ul&amp;gt; &lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://fr.wikipedia.org/wiki/Interpolation_polynomiale &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17088</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17088"/>
		<updated>2026-05-11T21:21:43Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* A) Fonctionnement */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calculs a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrées qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs étroitement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calculs nécessaires en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt; f(n) &amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cet algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; m &amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt; f(x) = x^2 &amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karatsuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt; x &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt; b &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 Résultat &amp;lt;math&amp;gt;= w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice pairs et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(x)=P_{\text{pair}}(x^2) + x \cdot P_{\text{impair}}(x^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 1 : Ecrire les deux facteurs sous la forme de polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 2 : Effectuer la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; des deux polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 3 : Faire le produit terme à terme des &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; obtenues&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 4 : Faire l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; du produit&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 5 : Gérer les retenues et écrire le nombre qui en découle&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or, parmi toutes ces étapes, certaines sont négligeable comme l&#039;étape 1 et 5.&lt;br /&gt;
&lt;br /&gt;
Pour connaitre la complexité de l&#039;algorithme, additionnons les complexités des étapes restantes :&lt;br /&gt;
&lt;br /&gt;
Une &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt; car elle fait &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; évaluation sur &amp;lt;math&amp;gt; log(n)&amp;lt;/math&amp;gt; récursions. Dans le cadre de notre algorithme, nous devons faire la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; de nos deux facteurs. Soit &amp;lt;math&amp;gt;2\times \mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Pour le produit terme à terme, nous faisons naturellement &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&lt;br /&gt;
&lt;br /&gt;
Pour finir, l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; a la même complexité que la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt;. Dans le cadre de notre algorithme nous l&#039;emploierons qu&#039;une seule fois, ce qui fait une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Après addition de toutes ces complexités, nous obtenons la complexité réelle de &amp;lt;math&amp;gt;3\mathcal{O}(n\times logn) + \mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
D&#039;après le Master Theorem, nous avons donc &amp;lt;math&amp;gt; f(n) = 3(n\times logn)+n &amp;lt;/math&amp;gt;, cherchons &amp;lt;math&amp;gt;f(n)\in\mathcal{O}(g(n))&amp;lt;/math&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
Nous pouvons ignorer les expressions linéaires ainsi que les constantes multiplicatrices, nous obtenons donc &amp;lt;math&amp;gt;\mathcal{O}(g(n)) = \mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT complexite.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre ne provient pas du fruit de nos recherches personnelles mais illustre bien la comparaison entre la complexité de la FFT et des autres algorithmes.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la FFT est très efficace et devance de loin les autres algorithmes en terme de complexité. &amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les algorithmes les plus efficaces pour calculer le produit de grands entiers reposent aujourd&#039;hui sur des variantes de la transformée de Fourier rapide.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
 &lt;br /&gt;
L&#039;étude de ces algorithmes de multiplication nous a clairement montré l&#039;évolution de la complexité algorithmique permettant de gagner en efficacité lorsque la taille des données augmente.&lt;br /&gt;
&lt;br /&gt;
La multiplication classiqe, en &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;, résulte d&#039;une approche naïve qui devient rapidement inefficace pour les grands entiers ou les grandes matrices. L&#039;algorithme de Karatsuba nous montre qu&#039;organiser de manière intelligente ses calculs algébrique permet parfois de gagner beaucoup de multiplications, et donc de temps. Nous avons ainsi réussi à passer d&#039;une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt; à &amp;lt;math&amp;gt;\mathcal{O}(n^{log_2(3)})&amp;lt;/math&amp;gt;, soit environ &amp;lt;math&amp;gt;\mathcal{O}(n^{1/585})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Des méthodes encore plus avancées comme utilise l&#039;algorithme de Toom-Cook-3 continue dans cette idées de divisions d&#039;entiers en blocs plus petits, tandis que la transformée de Fourier rapide change complètement de point de vue. En effet la FFT n&#039;utilise pas la représentation classique des entiers mais fait appel à une vision polynomiale, ce qui transforme la convolution classique en multiplication terme à terme, ce qui lui permet d&#039;atteindre une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Dans le cas des matrices, les mêmes logiques apparaissent comme par exemple avec l&#039;algorithme de Strassen qui se rapproche très fortement de Karatsuba en combinant de manière astucieuse les parties d&#039;entiers qui avaient été précédemment découpée.&lt;br /&gt;
&lt;br /&gt;
On remarque néanmoins que la majorité des algorithmes efficaces s&#039;orientent vers une approche récursive et non itérative. Néanmoins, nous avons pu trouver certaines de ces implémentations (comme par exemple la FFT) en itérative. &lt;br /&gt;
&lt;br /&gt;
Avec ces recherches, nous comprenons que la réorganisations des calculs algébriques peuvent aider à gagner en complexité mais que pour faire de réels progrès, il nous faudra surement changer notre point de vue une nouvelle fois, et aborder le problème sous un angle nouveau comme le font dors et déjà les algorithmes les plus performants.&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
Utilisation du site de production graphique &amp;quot;Desmos&amp;quot; : https://www.desmos.com/calculator?lang=fr&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : &lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=PZ2gdWdKHAA &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&amp;lt;ul&amp;gt; &lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://fr.wikipedia.org/wiki/Interpolation_polynomiale &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17087</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17087"/>
		<updated>2026-05-11T21:21:26Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* B) Graphique */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calculs a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrées qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs étroitement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calculs nécessaires en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt; f(n) &amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cet algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; m &amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt; f(x) = x^2 &amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karatsuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt; b &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 Résultat &amp;lt;math&amp;gt;= w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice pairs et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(x)=P_{\text{pair}}(x^2) + x \cdot P_{\text{impair}}(x^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 1 : Ecrire les deux facteurs sous la forme de polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 2 : Effectuer la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; des deux polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 3 : Faire le produit terme à terme des &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; obtenues&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 4 : Faire l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; du produit&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 5 : Gérer les retenues et écrire le nombre qui en découle&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or, parmi toutes ces étapes, certaines sont négligeable comme l&#039;étape 1 et 5.&lt;br /&gt;
&lt;br /&gt;
Pour connaitre la complexité de l&#039;algorithme, additionnons les complexités des étapes restantes :&lt;br /&gt;
&lt;br /&gt;
Une &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt; car elle fait &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; évaluation sur &amp;lt;math&amp;gt; log(n)&amp;lt;/math&amp;gt; récursions. Dans le cadre de notre algorithme, nous devons faire la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; de nos deux facteurs. Soit &amp;lt;math&amp;gt;2\times \mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Pour le produit terme à terme, nous faisons naturellement &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&lt;br /&gt;
&lt;br /&gt;
Pour finir, l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; a la même complexité que la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt;. Dans le cadre de notre algorithme nous l&#039;emploierons qu&#039;une seule fois, ce qui fait une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Après addition de toutes ces complexités, nous obtenons la complexité réelle de &amp;lt;math&amp;gt;3\mathcal{O}(n\times logn) + \mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
D&#039;après le Master Theorem, nous avons donc &amp;lt;math&amp;gt; f(n) = 3(n\times logn)+n &amp;lt;/math&amp;gt;, cherchons &amp;lt;math&amp;gt;f(n)\in\mathcal{O}(g(n))&amp;lt;/math&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
Nous pouvons ignorer les expressions linéaires ainsi que les constantes multiplicatrices, nous obtenons donc &amp;lt;math&amp;gt;\mathcal{O}(g(n)) = \mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT complexite.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre ne provient pas du fruit de nos recherches personnelles mais illustre bien la comparaison entre la complexité de la FFT et des autres algorithmes.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la FFT est très efficace et devance de loin les autres algorithmes en terme de complexité. &amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les algorithmes les plus efficaces pour calculer le produit de grands entiers reposent aujourd&#039;hui sur des variantes de la transformée de Fourier rapide.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
 &lt;br /&gt;
L&#039;étude de ces algorithmes de multiplication nous a clairement montré l&#039;évolution de la complexité algorithmique permettant de gagner en efficacité lorsque la taille des données augmente.&lt;br /&gt;
&lt;br /&gt;
La multiplication classiqe, en &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;, résulte d&#039;une approche naïve qui devient rapidement inefficace pour les grands entiers ou les grandes matrices. L&#039;algorithme de Karatsuba nous montre qu&#039;organiser de manière intelligente ses calculs algébrique permet parfois de gagner beaucoup de multiplications, et donc de temps. Nous avons ainsi réussi à passer d&#039;une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt; à &amp;lt;math&amp;gt;\mathcal{O}(n^{log_2(3)})&amp;lt;/math&amp;gt;, soit environ &amp;lt;math&amp;gt;\mathcal{O}(n^{1/585})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Des méthodes encore plus avancées comme utilise l&#039;algorithme de Toom-Cook-3 continue dans cette idées de divisions d&#039;entiers en blocs plus petits, tandis que la transformée de Fourier rapide change complètement de point de vue. En effet la FFT n&#039;utilise pas la représentation classique des entiers mais fait appel à une vision polynomiale, ce qui transforme la convolution classique en multiplication terme à terme, ce qui lui permet d&#039;atteindre une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Dans le cas des matrices, les mêmes logiques apparaissent comme par exemple avec l&#039;algorithme de Strassen qui se rapproche très fortement de Karatsuba en combinant de manière astucieuse les parties d&#039;entiers qui avaient été précédemment découpée.&lt;br /&gt;
&lt;br /&gt;
On remarque néanmoins que la majorité des algorithmes efficaces s&#039;orientent vers une approche récursive et non itérative. Néanmoins, nous avons pu trouver certaines de ces implémentations (comme par exemple la FFT) en itérative. &lt;br /&gt;
&lt;br /&gt;
Avec ces recherches, nous comprenons que la réorganisations des calculs algébriques peuvent aider à gagner en complexité mais que pour faire de réels progrès, il nous faudra surement changer notre point de vue une nouvelle fois, et aborder le problème sous un angle nouveau comme le font dors et déjà les algorithmes les plus performants.&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
Utilisation du site de production graphique &amp;quot;Desmos&amp;quot; : https://www.desmos.com/calculator?lang=fr&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : &lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=PZ2gdWdKHAA &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&amp;lt;ul&amp;gt; &lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://fr.wikipedia.org/wiki/Interpolation_polynomiale &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17086</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17086"/>
		<updated>2026-05-11T21:19:50Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* B) Graphique */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calculs a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrées qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs étroitement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calculs nécessaires en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt; f(n) &amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cet algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; m &amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt;f(x) = x^2&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karatsuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt; b &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 Résultat &amp;lt;math&amp;gt;= w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice pairs et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(x)=P_{\text{pair}}(x^2) + x \cdot P_{\text{impair}}(x^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 1 : Ecrire les deux facteurs sous la forme de polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 2 : Effectuer la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; des deux polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 3 : Faire le produit terme à terme des &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; obtenues&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 4 : Faire l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; du produit&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 5 : Gérer les retenues et écrire le nombre qui en découle&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or, parmi toutes ces étapes, certaines sont négligeable comme l&#039;étape 1 et 5.&lt;br /&gt;
&lt;br /&gt;
Pour connaitre la complexité de l&#039;algorithme, additionnons les complexités des étapes restantes :&lt;br /&gt;
&lt;br /&gt;
Une &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt; car elle fait &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; évaluation sur &amp;lt;math&amp;gt; log(n)&amp;lt;/math&amp;gt; récursions. Dans le cadre de notre algorithme, nous devons faire la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; de nos deux facteurs. Soit &amp;lt;math&amp;gt;2\times \mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Pour le produit terme à terme, nous faisons naturellement &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&lt;br /&gt;
&lt;br /&gt;
Pour finir, l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; a la même complexité que la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt;. Dans le cadre de notre algorithme nous l&#039;emploierons qu&#039;une seule fois, ce qui fait une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Après addition de toutes ces complexités, nous obtenons la complexité réelle de &amp;lt;math&amp;gt;3\mathcal{O}(n\times logn) + \mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
D&#039;après le Master Theorem, nous avons donc &amp;lt;math&amp;gt; f(n) = 3(n\times logn)+n &amp;lt;/math&amp;gt;, cherchons &amp;lt;math&amp;gt;f(n)\in\mathcal{O}(g(n))&amp;lt;/math&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
Nous pouvons ignorer les expressions linéaires ainsi que les constantes multiplicatrices, nous obtenons donc &amp;lt;math&amp;gt;\mathcal{O}(g(n)) = \mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT complexite.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre ne provient pas du fruit de nos recherches personnelles mais illustre bien la comparaison entre la complexité de la FFT et des autres algorithmes.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la FFT est très efficace et devance de loin les autres algorithmes en terme de complexité. &amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les algorithmes les plus efficaces pour calculer le produit de grands entiers reposent aujourd&#039;hui sur des variantes de la transformée de Fourier rapide.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
 &lt;br /&gt;
L&#039;étude de ces algorithmes de multiplication nous a clairement montré l&#039;évolution de la complexité algorithmique permettant de gagner en efficacité lorsque la taille des données augmente.&lt;br /&gt;
&lt;br /&gt;
La multiplication classiqe, en &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;, résulte d&#039;une approche naïve qui devient rapidement inefficace pour les grands entiers ou les grandes matrices. L&#039;algorithme de Karatsuba nous montre qu&#039;organiser de manière intelligente ses calculs algébrique permet parfois de gagner beaucoup de multiplications, et donc de temps. Nous avons ainsi réussi à passer d&#039;une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt; à &amp;lt;math&amp;gt;\mathcal{O}(n^{log_2(3)})&amp;lt;/math&amp;gt;, soit environ &amp;lt;math&amp;gt;\mathcal{O}(n^{1/585})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Des méthodes encore plus avancées comme utilise l&#039;algorithme de Toom-Cook-3 continue dans cette idées de divisions d&#039;entiers en blocs plus petits, tandis que la transformée de Fourier rapide change complètement de point de vue. En effet la FFT n&#039;utilise pas la représentation classique des entiers mais fait appel à une vision polynomiale, ce qui transforme la convolution classique en multiplication terme à terme, ce qui lui permet d&#039;atteindre une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Dans le cas des matrices, les mêmes logiques apparaissent comme par exemple avec l&#039;algorithme de Strassen qui se rapproche très fortement de Karatsuba en combinant de manière astucieuse les parties d&#039;entiers qui avaient été précédemment découpée.&lt;br /&gt;
&lt;br /&gt;
On remarque néanmoins que la majorité des algorithmes efficaces s&#039;orientent vers une approche récursive et non itérative. Néanmoins, nous avons pu trouver certaines de ces implémentations (comme par exemple la FFT) en itérative. &lt;br /&gt;
&lt;br /&gt;
Avec ces recherches, nous comprenons que la réorganisations des calculs algébriques peuvent aider à gagner en complexité mais que pour faire de réels progrès, il nous faudra surement changer notre point de vue une nouvelle fois, et aborder le problème sous un angle nouveau comme le font dors et déjà les algorithmes les plus performants.&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
Utilisation du site de production graphique &amp;quot;Desmos&amp;quot; : https://www.desmos.com/calculator?lang=fr&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : &lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=PZ2gdWdKHAA &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&amp;lt;ul&amp;gt; &lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://fr.wikipedia.org/wiki/Interpolation_polynomiale &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17085</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17085"/>
		<updated>2026-05-11T21:17:26Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* B) Graphique */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calculs a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrées qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs étroitement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calculs nécessaires en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt; f(n) &amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cet algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; m &amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt;f(x) = x^2&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karatsuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt; b &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 Résultat &amp;lt;math&amp;gt;= w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice pairs et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(x)=P_{\text{pair}}(x^2) + x \cdot P_{\text{impair}}(x^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 1 : Ecrire les deux facteurs sous la forme de polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 2 : Effectuer la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; des deux polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 3 : Faire le produit terme à terme des &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; obtenues&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 4 : Faire l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; du produit&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 5 : Gérer les retenues et écrire le nombre qui en découle&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or, parmi toutes ces étapes, certaines sont négligeable comme l&#039;étape 1 et 5.&lt;br /&gt;
&lt;br /&gt;
Pour connaitre la complexité de l&#039;algorithme, additionnons les complexités des étapes restantes :&lt;br /&gt;
&lt;br /&gt;
Une &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt; car elle fait &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; évaluation sur &amp;lt;math&amp;gt; log(n)&amp;lt;/math&amp;gt; récursions. Dans le cadre de notre algorithme, nous devons faire la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; de nos deux facteurs. Soit &amp;lt;math&amp;gt;2\times \mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Pour le produit terme à terme, nous faisons naturellement &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&lt;br /&gt;
&lt;br /&gt;
Pour finir, l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; a la même complexité que la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt;. Dans le cadre de notre algorithme nous l&#039;emploierons qu&#039;une seule fois, ce qui fait une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Après addition de toutes ces complexités, nous obtenons la complexité réelle de &amp;lt;math&amp;gt;3\mathcal{O}(n\times logn) + \mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
D&#039;après le Master Theorem, nous avons donc &amp;lt;math&amp;gt; f(n) = 3(n\times logn)+n &amp;lt;/math&amp;gt;, cherchons &amp;lt;math&amp;gt;f(n)\in\mathcal{O}(g(n))&amp;lt;/math&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
Nous pouvons ignorer les expressions linéaires ainsi que les constantes multiplicatrices, nous obtenons donc &amp;lt;math&amp;gt;\mathcal{O}(g(n)) = \mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT complexite.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre ne provient pas du fruit de nos recherches personnelles mais illustre bien la comparaison entre la complexité de la FFT et des autres algorithmes.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la FFT est très efficace et devance de loin les autres algorithmes en terme de complexité. &amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est l&#039;un des plus performants.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
 &lt;br /&gt;
L&#039;étude de ces algorithmes de multiplication nous a clairement montré l&#039;évolution de la complexité algorithmique permettant de gagner en efficacité lorsque la taille des données augmente.&lt;br /&gt;
&lt;br /&gt;
La multiplication classiqe, en &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;, résulte d&#039;une approche naïve qui devient rapidement inefficace pour les grands entiers ou les grandes matrices. L&#039;algorithme de Karatsuba nous montre qu&#039;organiser de manière intelligente ses calculs algébrique permet parfois de gagner beaucoup de multiplications, et donc de temps. Nous avons ainsi réussi à passer d&#039;une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt; à &amp;lt;math&amp;gt;\mathcal{O}(n^{log_2(3)})&amp;lt;/math&amp;gt;, soit environ &amp;lt;math&amp;gt;\mathcal{O}(n^{1/585})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Des méthodes encore plus avancées comme utilise l&#039;algorithme de Toom-Cook-3 continue dans cette idées de divisions d&#039;entiers en blocs plus petits, tandis que la transformée de Fourier rapide change complètement de point de vue. En effet la FFT n&#039;utilise pas la représentation classique des entiers mais fait appel à une vision polynomiale, ce qui transforme la convolution classique en multiplication terme à terme, ce qui lui permet d&#039;atteindre une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Dans le cas des matrices, les mêmes logiques apparaissent comme par exemple avec l&#039;algorithme de Strassen qui se rapproche très fortement de Karatsuba en combinant de manière astucieuse les parties d&#039;entiers qui avaient été précédemment découpée.&lt;br /&gt;
&lt;br /&gt;
On remarque néanmoins que la majorité des algorithmes efficaces s&#039;orientent vers une approche récursive et non itérative. Néanmoins, nous avons pu trouver certaines de ces implémentations (comme par exemple la FFT) en itérative. &lt;br /&gt;
&lt;br /&gt;
Avec ces recherches, nous comprenons que la réorganisations des calculs algébriques peuvent aider à gagner en complexité mais que pour faire de réels progrès, il nous faudra surement changer notre point de vue une nouvelle fois, et aborder le problème sous un angle nouveau comme le font dors et déjà les algorithmes les plus performants.&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
Utilisation du site de production graphique &amp;quot;Desmos&amp;quot; : https://www.desmos.com/calculator?lang=fr&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : &lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=PZ2gdWdKHAA &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&amp;lt;ul&amp;gt; &lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://fr.wikipedia.org/wiki/Interpolation_polynomiale &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Fichier:FFT_complexite.png&amp;diff=17084</id>
		<title>Fichier:FFT complexite.png</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Fichier:FFT_complexite.png&amp;diff=17084"/>
		<updated>2026-05-11T21:16:56Z</updated>

		<summary type="html">&lt;p&gt;Mangala : &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17083</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17083"/>
		<updated>2026-05-11T21:13:54Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* 3) La multiplication karatsuba */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calculs a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrées qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs étroitement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calculs nécessaires en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt; f(n) &amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cet algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; m &amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt;f(x) = x^2&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karatsuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt; b &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 Résultat &amp;lt;math&amp;gt;= w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice pairs et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(x)=P_{\text{pair}}(x^2) + x \cdot P_{\text{impair}}(x^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 1 : Ecrire les deux facteurs sous la forme de polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 2 : Effectuer la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; des deux polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 3 : Faire le produit terme à terme des &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; obtenues&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 4 : Faire l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; du produit&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 5 : Gérer les retenues et écrire le nombre qui en découle&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or, parmi toutes ces étapes, certaines sont négligeable comme l&#039;étape 1 et 5.&lt;br /&gt;
&lt;br /&gt;
Pour connaitre la complexité de l&#039;algorithme, additionnons les complexités des étapes restantes :&lt;br /&gt;
&lt;br /&gt;
Une &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt; car elle fait &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; évaluation sur &amp;lt;math&amp;gt; log(n)&amp;lt;/math&amp;gt; récursions. Dans le cadre de notre algorithme, nous devons faire la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; de nos deux facteurs. Soit &amp;lt;math&amp;gt;2\times \mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Pour le produit terme à terme, nous faisons naturellement &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&lt;br /&gt;
&lt;br /&gt;
Pour finir, l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; a la même complexité que la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt;. Dans le cadre de notre algorithme nous l&#039;emploierons qu&#039;une seule fois, ce qui fait une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Après addition de toutes ces complexités, nous obtenons la complexité réelle de &amp;lt;math&amp;gt;3\mathcal{O}(n\times logn) + \mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
D&#039;après le Master Theorem, nous avons donc &amp;lt;math&amp;gt; f(n) = 3(n\times logn)+n &amp;lt;/math&amp;gt;, cherchons &amp;lt;math&amp;gt;f(n)\in\mathcal{O}(g(n))&amp;lt;/math&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
Nous pouvons ignorer les expressions linéaires ainsi que les constantes multiplicatrices, nous obtenons donc &amp;lt;math&amp;gt;\mathcal{O}(g(n)) = \mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT_image.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre ne provient pas du fruit de nos recherches personnelles mais illustre bien la comparaison entre la complexité de la FFT et des autres algorithmes.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la FFT est très efficace et devance de loin les autres algorithmes en terme de complexité. &amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est l&#039;un des plus performants.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
 &lt;br /&gt;
L&#039;étude de ces algorithmes de multiplication nous a clairement montré l&#039;évolution de la complexité algorithmique permettant de gagner en efficacité lorsque la taille des données augmente.&lt;br /&gt;
&lt;br /&gt;
La multiplication classiqe, en &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;, résulte d&#039;une approche naïve qui devient rapidement inefficace pour les grands entiers ou les grandes matrices. L&#039;algorithme de Karatsuba nous montre qu&#039;organiser de manière intelligente ses calculs algébrique permet parfois de gagner beaucoup de multiplications, et donc de temps. Nous avons ainsi réussi à passer d&#039;une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt; à &amp;lt;math&amp;gt;\mathcal{O}(n^{log_2(3)})&amp;lt;/math&amp;gt;, soit environ &amp;lt;math&amp;gt;\mathcal{O}(n^{1/585})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Des méthodes encore plus avancées comme utilise l&#039;algorithme de Toom-Cook-3 continue dans cette idées de divisions d&#039;entiers en blocs plus petits, tandis que la transformée de Fourier rapide change complètement de point de vue. En effet la FFT n&#039;utilise pas la représentation classique des entiers mais fait appel à une vision polynomiale, ce qui transforme la convolution classique en multiplication terme à terme, ce qui lui permet d&#039;atteindre une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Dans le cas des matrices, les mêmes logiques apparaissent comme par exemple avec l&#039;algorithme de Strassen qui se rapproche très fortement de Karatsuba en combinant de manière astucieuse les parties d&#039;entiers qui avaient été précédemment découpée.&lt;br /&gt;
&lt;br /&gt;
On remarque néanmoins que la majorité des algorithmes efficaces s&#039;orientent vers une approche récursive et non itérative. Néanmoins, nous avons pu trouver certaines de ces implémentations (comme par exemple la FFT) en itérative. &lt;br /&gt;
&lt;br /&gt;
Avec ces recherches, nous comprenons que la réorganisations des calculs algébriques peuvent aider à gagner en complexité mais que pour faire de réels progrès, il nous faudra surement changer notre point de vue une nouvelle fois, et aborder le problème sous un angle nouveau comme le font dors et déjà les algorithmes les plus performants.&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
Utilisation du site de production graphique &amp;quot;Desmos&amp;quot; : https://www.desmos.com/calculator?lang=fr&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : &lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=PZ2gdWdKHAA &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&amp;lt;ul&amp;gt; &lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://fr.wikipedia.org/wiki/Interpolation_polynomiale &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17082</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17082"/>
		<updated>2026-05-11T21:13:29Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* A) Fonctionnement */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calculs a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrées qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs étroitement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calculs nécessaires en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt; f(n) &amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cet algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; m &amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt;f(x) = x^2&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karatsuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt; b &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 Résultat &amp;lt;math&amp;gt;= w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice pairs et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(x)=P_{\text{pair}}(x^2) + x \cdot P_{\text{impair}}(x^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 1 : Ecrire les deux facteurs sous la forme de polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 2 : Effectuer la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; des deux polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 3 : Faire le produit terme à terme des &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; obtenues&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 4 : Faire l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; du produit&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 5 : Gérer les retenues et écrire le nombre qui en découle&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or, parmi toutes ces étapes, certaines sont négligeable comme l&#039;étape 1 et 5.&lt;br /&gt;
&lt;br /&gt;
Pour connaitre la complexité de l&#039;algorithme, additionnons les complexités des étapes restantes :&lt;br /&gt;
&lt;br /&gt;
Une &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt; car elle fait &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; évaluation sur &amp;lt;math&amp;gt; log(n)&amp;lt;/math&amp;gt; récursions. Dans le cadre de notre algorithme, nous devons faire la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; de nos deux facteurs. Soit &amp;lt;math&amp;gt;2\times \mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Pour le produit terme à terme, nous faisons naturellement &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&lt;br /&gt;
&lt;br /&gt;
Pour finir, l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; a la même complexité que la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt;. Dans le cadre de notre algorithme nous l&#039;emploierons qu&#039;une seule fois, ce qui fait une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Après addition de toutes ces complexités, nous obtenons la complexité réelle de &amp;lt;math&amp;gt;3\mathcal{O}(n\times logn) + \mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
D&#039;après le Master Theorem, nous avons donc &amp;lt;math&amp;gt; f(n) = 3(n\times logn)+n &amp;lt;/math&amp;gt;, cherchons &amp;lt;math&amp;gt;f(n)\in\mathcal{O}(g(n))&amp;lt;/math&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
Nous pouvons ignorer les expressions linéaires ainsi que les constantes multiplicatrices, nous obtenons donc &amp;lt;math&amp;gt;\mathcal{O}(g(n)) = \mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT_image.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre ne provient pas du fruit de nos recherches personnelles mais illustre bien la comparaison entre la complexité de la FFT et des autres algorithmes.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la FFT est très efficace et devance de loin les autres algorithmes en terme de complexité. &amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est l&#039;un des plus performants.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
 &lt;br /&gt;
L&#039;étude de ces algorithmes de multiplication nous a clairement montré l&#039;évolution de la complexité algorithmique permettant de gagner en efficacité lorsque la taille des données augmente.&lt;br /&gt;
&lt;br /&gt;
La multiplication classiqe, en &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;, résulte d&#039;une approche naïve qui devient rapidement inefficace pour les grands entiers ou les grandes matrices. L&#039;algorithme de Karatsuba nous montre qu&#039;organiser de manière intelligente ses calculs algébrique permet parfois de gagner beaucoup de multiplications, et donc de temps. Nous avons ainsi réussi à passer d&#039;une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt; à &amp;lt;math&amp;gt;\mathcal{O}(n^{log_2(3)})&amp;lt;/math&amp;gt;, soit environ &amp;lt;math&amp;gt;\mathcal{O}(n^{1/585})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Des méthodes encore plus avancées comme utilise l&#039;algorithme de Toom-Cook-3 continue dans cette idées de divisions d&#039;entiers en blocs plus petits, tandis que la transformée de Fourier rapide change complètement de point de vue. En effet la FFT n&#039;utilise pas la représentation classique des entiers mais fait appel à une vision polynomiale, ce qui transforme la convolution classique en multiplication terme à terme, ce qui lui permet d&#039;atteindre une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Dans le cas des matrices, les mêmes logiques apparaissent comme par exemple avec l&#039;algorithme de Strassen qui se rapproche très fortement de Karatsuba en combinant de manière astucieuse les parties d&#039;entiers qui avaient été précédemment découpée.&lt;br /&gt;
&lt;br /&gt;
On remarque néanmoins que la majorité des algorithmes efficaces s&#039;orientent vers une approche récursive et non itérative. Néanmoins, nous avons pu trouver certaines de ces implémentations (comme par exemple la FFT) en itérative. &lt;br /&gt;
&lt;br /&gt;
Avec ces recherches, nous comprenons que la réorganisations des calculs algébriques peuvent aider à gagner en complexité mais que pour faire de réels progrès, il nous faudra surement changer notre point de vue une nouvelle fois, et aborder le problème sous un angle nouveau comme le font dors et déjà les algorithmes les plus performants.&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
Utilisation du site de production graphique &amp;quot;Desmos&amp;quot; : https://www.desmos.com/calculator?lang=fr&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : &lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=PZ2gdWdKHAA &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&amp;lt;ul&amp;gt; &lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://fr.wikipedia.org/wiki/Interpolation_polynomiale &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17081</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17081"/>
		<updated>2026-05-11T21:10:01Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* A) Fonctionnement */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calculs a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrées qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs étroitement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calculs nécessaires en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt; f(n) &amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cet algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; m &amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt;f(x) = x^2&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karatsuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt; b &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 15px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 &amp;lt;math&amp;gt;Résultat = w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice pairs et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(x)=P_{\text{pair}}(x^2) + x \cdot P_{\text{impair}}(x^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 1 : Ecrire les deux facteurs sous la forme de polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 2 : Effectuer la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; des deux polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 3 : Faire le produit terme à terme des &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; obtenues&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 4 : Faire l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; du produit&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 5 : Gérer les retenues et écrire le nombre qui en découle&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or, parmi toutes ces étapes, certaines sont négligeable comme l&#039;étape 1 et 5.&lt;br /&gt;
&lt;br /&gt;
Pour connaitre la complexité de l&#039;algorithme, additionnons les complexités des étapes restantes :&lt;br /&gt;
&lt;br /&gt;
Une &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt; car elle fait &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; évaluation sur &amp;lt;math&amp;gt; log(n)&amp;lt;/math&amp;gt; récursions. Dans le cadre de notre algorithme, nous devons faire la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; de nos deux facteurs. Soit &amp;lt;math&amp;gt;2\times \mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Pour le produit terme à terme, nous faisons naturellement &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&lt;br /&gt;
&lt;br /&gt;
Pour finir, l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; a la même complexité que la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt;. Dans le cadre de notre algorithme nous l&#039;emploierons qu&#039;une seule fois, ce qui fait une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Après addition de toutes ces complexités, nous obtenons la complexité réelle de &amp;lt;math&amp;gt;3\mathcal{O}(n\times logn) + \mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
D&#039;après le Master Theorem, nous avons donc &amp;lt;math&amp;gt; f(n) = 3(n\times logn)+n &amp;lt;/math&amp;gt;, cherchons &amp;lt;math&amp;gt;f(n)\in\mathcal{O}(g(n))&amp;lt;/math&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
Nous pouvons ignorer les expressions linéaires ainsi que les constantes multiplicatrices, nous obtenons donc &amp;lt;math&amp;gt;\mathcal{O}(g(n)) = \mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT_image.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre ne provient pas du fruit de nos recherches personnelles mais illustre bien la comparaison entre la complexité de la FFT et des autres algorithmes.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la FFT est très efficace et devance de loin les autres algorithmes en terme de complexité. &amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est l&#039;un des plus performants.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
 &lt;br /&gt;
L&#039;étude de ces algorithmes de multiplication nous a clairement montré l&#039;évolution de la complexité algorithmique permettant de gagner en efficacité lorsque la taille des données augmente.&lt;br /&gt;
&lt;br /&gt;
La multiplication classiqe, en &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;, résulte d&#039;une approche naïve qui devient rapidement inefficace pour les grands entiers ou les grandes matrices. L&#039;algorithme de Karatsuba nous montre qu&#039;organiser de manière intelligente ses calculs algébrique permet parfois de gagner beaucoup de multiplications, et donc de temps. Nous avons ainsi réussi à passer d&#039;une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt; à &amp;lt;math&amp;gt;\mathcal{O}(n^{log_2(3)})&amp;lt;/math&amp;gt;, soit environ &amp;lt;math&amp;gt;\mathcal{O}(n^{1/585})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Des méthodes encore plus avancées comme utilise l&#039;algorithme de Toom-Cook-3 continue dans cette idées de divisions d&#039;entiers en blocs plus petits, tandis que la transformée de Fourier rapide change complètement de point de vue. En effet la FFT n&#039;utilise pas la représentation classique des entiers mais fait appel à une vision polynomiale, ce qui transforme la convolution classique en multiplication terme à terme, ce qui lui permet d&#039;atteindre une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Dans le cas des matrices, les mêmes logiques apparaissent comme par exemple avec l&#039;algorithme de Strassen qui se rapproche très fortement de Karatsuba en combinant de manière astucieuse les parties d&#039;entiers qui avaient été précédemment découpée.&lt;br /&gt;
&lt;br /&gt;
On remarque néanmoins que la majorité des algorithmes efficaces s&#039;orientent vers une approche récursive et non itérative. Néanmoins, nous avons pu trouver certaines de ces implémentations (comme par exemple la FFT) en itérative. &lt;br /&gt;
&lt;br /&gt;
Avec ces recherches, nous comprenons que la réorganisations des calculs algébriques peuvent aider à gagner en complexité mais que pour faire de réels progrès, il nous faudra surement changer notre point de vue une nouvelle fois, et aborder le problème sous un angle nouveau comme le font dors et déjà les algorithmes les plus performants.&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
Utilisation du site de production graphique &amp;quot;Desmos&amp;quot; : https://www.desmos.com/calculator?lang=fr&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : &lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=PZ2gdWdKHAA &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&amp;lt;ul&amp;gt; &lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://fr.wikipedia.org/wiki/Interpolation_polynomiale &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17080</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17080"/>
		<updated>2026-05-11T21:07:01Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* B) Graphique */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calculs a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrées qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs étroitement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calculs nécessaires en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt; f(n) &amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cet algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; m &amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt;f(x) = x^2&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karatsuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt; b &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 &amp;lt;math&amp;gt;Résultat = w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice pairs et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(x)=P_{\text{pair}}(x^2) + x \cdot P_{\text{impair}}(x^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 1 : Ecrire les deux facteurs sous la forme de polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 2 : Effectuer la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; des deux polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 3 : Faire le produit terme à terme des &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; obtenues&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 4 : Faire l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; du produit&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 5 : Gérer les retenues et écrire le nombre qui en découle&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or, parmi toutes ces étapes, certaines sont négligeable comme l&#039;étape 1 et 5.&lt;br /&gt;
&lt;br /&gt;
Pour connaitre la complexité de l&#039;algorithme, additionnons les complexités des étapes restantes :&lt;br /&gt;
&lt;br /&gt;
Une &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt; car elle fait &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; évaluation sur &amp;lt;math&amp;gt; log(n)&amp;lt;/math&amp;gt; récursions. Dans le cadre de notre algorithme, nous devons faire la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; de nos deux facteurs. Soit &amp;lt;math&amp;gt;2\times \mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Pour le produit terme à terme, nous faisons naturellement &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&lt;br /&gt;
&lt;br /&gt;
Pour finir, l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; a la même complexité que la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt;. Dans le cadre de notre algorithme nous l&#039;emploierons qu&#039;une seule fois, ce qui fait une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Après addition de toutes ces complexités, nous obtenons la complexité réelle de &amp;lt;math&amp;gt;3\mathcal{O}(n\times logn) + \mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
D&#039;après le Master Theorem, nous avons donc &amp;lt;math&amp;gt; f(n) = 3(n\times logn)+n &amp;lt;/math&amp;gt;, cherchons &amp;lt;math&amp;gt;f(n)\in\mathcal{O}(g(n))&amp;lt;/math&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
Nous pouvons ignorer les expressions linéaires ainsi que les constantes multiplicatrices, nous obtenons donc &amp;lt;math&amp;gt;\mathcal{O}(g(n)) = \mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT_image.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre ne provient pas du fruit de nos recherches personnelles mais illustre bien la comparaison entre la complexité de la FFT et des autres algorithmes.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la FFT est très efficace et devance de loin les autres algorithmes en terme de complexité. &amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est l&#039;un des plus performants.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
 &lt;br /&gt;
L&#039;étude de ces algorithmes de multiplication nous a clairement montré l&#039;évolution de la complexité algorithmique permettant de gagner en efficacité lorsque la taille des données augmente.&lt;br /&gt;
&lt;br /&gt;
La multiplication classiqe, en &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;, résulte d&#039;une approche naïve qui devient rapidement inefficace pour les grands entiers ou les grandes matrices. L&#039;algorithme de Karatsuba nous montre qu&#039;organiser de manière intelligente ses calculs algébrique permet parfois de gagner beaucoup de multiplications, et donc de temps. Nous avons ainsi réussi à passer d&#039;une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt; à &amp;lt;math&amp;gt;\mathcal{O}(n^{log_2(3)})&amp;lt;/math&amp;gt;, soit environ &amp;lt;math&amp;gt;\mathcal{O}(n^{1/585})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Des méthodes encore plus avancées comme utilise l&#039;algorithme de Toom-Cook-3 continue dans cette idées de divisions d&#039;entiers en blocs plus petits, tandis que la transformée de Fourier rapide change complètement de point de vue. En effet la FFT n&#039;utilise pas la représentation classique des entiers mais fait appel à une vision polynomiale, ce qui transforme la convolution classique en multiplication terme à terme, ce qui lui permet d&#039;atteindre une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Dans le cas des matrices, les mêmes logiques apparaissent comme par exemple avec l&#039;algorithme de Strassen qui se rapproche très fortement de Karatsuba en combinant de manière astucieuse les parties d&#039;entiers qui avaient été précédemment découpée.&lt;br /&gt;
&lt;br /&gt;
On remarque néanmoins que la majorité des algorithmes efficaces s&#039;orientent vers une approche récursive et non itérative. Néanmoins, nous avons pu trouver certaines de ces implémentations (comme par exemple la FFT) en itérative. &lt;br /&gt;
&lt;br /&gt;
Avec ces recherches, nous comprenons que la réorganisations des calculs algébriques peuvent aider à gagner en complexité mais que pour faire de réels progrès, il nous faudra surement changer notre point de vue une nouvelle fois, et aborder le problème sous un angle nouveau comme le font dors et déjà les algorithmes les plus performants.&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
Utilisation du site de production graphique &amp;quot;Desmos&amp;quot; : https://www.desmos.com/calculator?lang=fr&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : &lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=PZ2gdWdKHAA &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&amp;lt;ul&amp;gt; &lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://fr.wikipedia.org/wiki/Interpolation_polynomiale &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17079</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17079"/>
		<updated>2026-05-11T21:04:12Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* 3) La multiplication karatsuba */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calculs a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrées qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs étroitement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calculs nécessaires en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt; f(n) &amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cet algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; m &amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt;f(x) = x^2&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karatsuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt; b &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 &amp;lt;math&amp;gt;Résultat = w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice pairs et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(x)=P_{\text{pair}}(x^2) + x \cdot P_{\text{impair}}(x^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 1 : Ecrire les deux facteurs sous la forme de polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 2 : Effectuer la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; des deux polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 3 : Faire le produit terme à terme des &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; obtenues&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 4 : Faire l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; du produit&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 5 : Gérer les retenues et écrire le nombre qui en découle&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or, parmi toutes ces étapes, certaines sont négligeable comme l&#039;étape 1 et 5.&lt;br /&gt;
&lt;br /&gt;
Pour connaitre la complexité de l&#039;algorithme, additionnons les complexités des étapes restantes :&lt;br /&gt;
&lt;br /&gt;
Une &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt; car elle fait &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; évaluation sur &amp;lt;math&amp;gt; log(n)&amp;lt;/math&amp;gt; récursions. Dans le cadre de notre algorithme, nous devons faire la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; de nos deux facteurs. Soit &amp;lt;math&amp;gt;2\times \mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Pour le produit terme à terme, nous faisons naturellement &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&lt;br /&gt;
&lt;br /&gt;
Pour finir, l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; a la même complexité que la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt;. Dans le cadre de notre algorithme nous l&#039;emploierons qu&#039;une seule fois, ce qui fait une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Après addition de toutes ces complexités, nous obtenons la complexité réelle de &amp;lt;math&amp;gt;3\mathcal{O}(n\times logn) + \mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
D&#039;après le Master Theorem, nous avons donc &amp;lt;math&amp;gt; f(n) = 3(n\times logn)+n &amp;lt;/math&amp;gt;, cherchons &amp;lt;math&amp;gt;f(n)\in\mathcal{O}(g(n))&amp;lt;/math&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
Nous pouvons ignorer les expressions linéaires ainsi que les constantes multiplicatrices, nous obtenons donc &amp;lt;math&amp;gt;\mathcal{O}(g(n)) = \mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT_image.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre ne provient pas du fruit de nos recherches personnelles mais &amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
 &lt;br /&gt;
L&#039;étude de ces algorithmes de multiplication nous a clairement montré l&#039;évolution de la complexité algorithmique permettant de gagner en efficacité lorsque la taille des données augmente.&lt;br /&gt;
&lt;br /&gt;
La multiplication classiqe, en &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;, résulte d&#039;une approche naïve qui devient rapidement inefficace pour les grands entiers ou les grandes matrices. L&#039;algorithme de Karatsuba nous montre qu&#039;organiser de manière intelligente ses calculs algébrique permet parfois de gagner beaucoup de multiplications, et donc de temps. Nous avons ainsi réussi à passer d&#039;une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt; à &amp;lt;math&amp;gt;\mathcal{O}(n^{log_2(3)})&amp;lt;/math&amp;gt;, soit environ &amp;lt;math&amp;gt;\mathcal{O}(n^{1/585})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Des méthodes encore plus avancées comme utilise l&#039;algorithme de Toom-Cook-3 continue dans cette idées de divisions d&#039;entiers en blocs plus petits, tandis que la transformée de Fourier rapide change complètement de point de vue. En effet la FFT n&#039;utilise pas la représentation classique des entiers mais fait appel à une vision polynomiale, ce qui transforme la convolution classique en multiplication terme à terme, ce qui lui permet d&#039;atteindre une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Dans le cas des matrices, les mêmes logiques apparaissent comme par exemple avec l&#039;algorithme de Strassen qui se rapproche très fortement de Karatsuba en combinant de manière astucieuse les parties d&#039;entiers qui avaient été précédemment découpée.&lt;br /&gt;
&lt;br /&gt;
On remarque néanmoins que la majorité des algorithmes efficaces s&#039;orientent vers une approche récursive et non itérative. Néanmoins, nous avons pu trouver certaines de ces implémentations (comme par exemple la FFT) en itérative. &lt;br /&gt;
&lt;br /&gt;
Avec ces recherches, nous comprenons que la réorganisations des calculs algébriques peuvent aider à gagner en complexité mais que pour faire de réels progrès, il nous faudra surement changer notre point de vue une nouvelle fois, et aborder le problème sous un angle nouveau comme le font dors et déjà les algorithmes les plus performants.&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
Utilisation du site de production graphique &amp;quot;Desmos&amp;quot; : https://www.desmos.com/calculator?lang=fr&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : &lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=PZ2gdWdKHAA &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&amp;lt;ul&amp;gt; &lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://fr.wikipedia.org/wiki/Interpolation_polynomiale &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17078</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17078"/>
		<updated>2026-05-11T19:54:40Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* 2) La multiplication naïve */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calculs a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrées qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs étroitement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calculs nécessaires en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt; f(n) &amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cet algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; m &amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt;f(x) = x^2&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karatsuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt; b &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 &amp;lt;math&amp;gt;Résultat = w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice pairs et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(x)=P_{\text{pair}}(x^2) + x \cdot P_{\text{impair}}(x^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 1 : Ecrire les deux facteurs sous la forme de polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 2 : Effectuer la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; des deux polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 3 : Faire le produit terme à terme des &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; obtenues&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 4 : Faire l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; du produit&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 5 : Gérer les retenues et écrire le nombre qui en découle&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or, parmi toutes ces étapes, certaines sont négligeable comme l&#039;étape 1 et 5.&lt;br /&gt;
&lt;br /&gt;
Pour connaitre la complexité de l&#039;algorithme, additionnons les complexités des étapes restantes :&lt;br /&gt;
&lt;br /&gt;
Une &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt; car elle fait &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; évaluation sur &amp;lt;math&amp;gt; log(n)&amp;lt;/math&amp;gt; récursions. Dans le cadre de notre algorithme, nous devons faire la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; de nos deux facteurs. Soit &amp;lt;math&amp;gt;2\times \mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Pour le produit terme à terme, nous faisons naturellement &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&lt;br /&gt;
&lt;br /&gt;
Pour finir, l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; a la même complexité que la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt;. Dans le cadre de notre algorithme nous l&#039;emploierons qu&#039;une seule fois, ce qui fait une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Après addition de toutes ces complexités, nous obtenons la complexité réelle de &amp;lt;math&amp;gt;3\mathcal{O}(n\times logn) + \mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
D&#039;après le Master Theorem, nous avons donc &amp;lt;math&amp;gt; f(n) = 3(n\times logn)+n &amp;lt;/math&amp;gt;, cherchons &amp;lt;math&amp;gt;f(n)\in\mathcal{O}(g(n))&amp;lt;/math&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
Nous pouvons ignorer les expressions linéaires ainsi que les constantes multiplicatrices, nous obtenons donc &amp;lt;math&amp;gt;\mathcal{O}(g(n)) = \mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT_image.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre ne provient pas du fruit de nos recherches personnelles mais &amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
 &lt;br /&gt;
L&#039;étude de ces algorithmes de multiplication nous a clairement montré l&#039;évolution de la complexité algorithmique permettant de gagner en efficacité lorsque la taille des données augmente.&lt;br /&gt;
&lt;br /&gt;
La multiplication classiqe, en &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;, résulte d&#039;une approche naïve qui devient rapidement inefficace pour les grands entiers ou les grandes matrices. L&#039;algorithme de Karatsuba nous montre qu&#039;organiser de manière intelligente ses calculs algébrique permet parfois de gagner beaucoup de multiplications, et donc de temps. Nous avons ainsi réussi à passer d&#039;une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt; à &amp;lt;math&amp;gt;\mathcal{O}(n^{log_2(3)})&amp;lt;/math&amp;gt;, soit environ &amp;lt;math&amp;gt;\mathcal{O}(n^{1/585})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Des méthodes encore plus avancées comme utilise l&#039;algorithme de Toom-Cook-3 continue dans cette idées de divisions d&#039;entiers en blocs plus petits, tandis que la transformée de Fourier rapide change complètement de point de vue. En effet la FFT n&#039;utilise pas la représentation classique des entiers mais fait appel à une vision polynomiale, ce qui transforme la convolution classique en multiplication terme à terme, ce qui lui permet d&#039;atteindre une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Dans le cas des matrices, les mêmes logiques apparaissent comme par exemple avec l&#039;algorithme de Strassen qui se rapproche très fortement de Karatsuba en combinant de manière astucieuse les parties d&#039;entiers qui avaient été précédemment découpée.&lt;br /&gt;
&lt;br /&gt;
On remarque néanmoins que la majorité des algorithmes efficaces s&#039;orientent vers une approche récursive et non itérative. Néanmoins, nous avons pu trouver certaines de ces implémentations (comme par exemple la FFT) en itérative. &lt;br /&gt;
&lt;br /&gt;
Avec ces recherches, nous comprenons que la réorganisations des calculs algébriques peuvent aider à gagner en complexité mais que pour faire de réels progrès, il nous faudra surement changer notre point de vue une nouvelle fois, et aborder le problème sous un angle nouveau comme le font dors et déjà les algorithmes les plus performants.&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
Utilisation du site de production graphique &amp;quot;Desmos&amp;quot; : https://www.desmos.com/calculator?lang=fr&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : &lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=PZ2gdWdKHAA &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&amp;lt;ul&amp;gt; &lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://fr.wikipedia.org/wiki/Interpolation_polynomiale &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17077</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17077"/>
		<updated>2026-05-11T19:39:16Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* Mentions */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calculs a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrées qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs étroitement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calculs nécessaires en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt; f(n) &amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cet algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt;f(x) = x^2&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karatsuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt; b &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 &amp;lt;math&amp;gt;Résultat = w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice pairs et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(x)=P_{\text{pair}}(x^2) + x \cdot P_{\text{impair}}(x^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 1 : Ecrire les deux facteurs sous la forme de polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 2 : Effectuer la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; des deux polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 3 : Faire le produit terme à terme des &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; obtenues&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 4 : Faire l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; du produit&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 5 : Gérer les retenues et écrire le nombre qui en découle&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or, parmi toutes ces étapes, certaines sont négligeable comme l&#039;étape 1 et 5.&lt;br /&gt;
&lt;br /&gt;
Pour connaitre la complexité de l&#039;algorithme, additionnons les complexités des étapes restantes :&lt;br /&gt;
&lt;br /&gt;
Une &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt; car elle fait &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; évaluation sur &amp;lt;math&amp;gt; log(n)&amp;lt;/math&amp;gt; récursions. Dans le cadre de notre algorithme, nous devons faire la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; de nos deux facteurs. Soit &amp;lt;math&amp;gt;2\times \mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Pour le produit terme à terme, nous faisons naturellement &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&lt;br /&gt;
&lt;br /&gt;
Pour finir, l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; a la même complexité que la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt;. Dans le cadre de notre algorithme nous l&#039;emploierons qu&#039;une seule fois, ce qui fait une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Après addition de toutes ces complexités, nous obtenons la complexité réelle de &amp;lt;math&amp;gt;3\mathcal{O}(n\times logn) + \mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
D&#039;après le Master Theorem, nous avons donc &amp;lt;math&amp;gt; f(n) = 3(n\times logn)+n &amp;lt;/math&amp;gt;, cherchons &amp;lt;math&amp;gt;f(n)\in\mathcal{O}(g(n))&amp;lt;/math&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
Nous pouvons ignorer les expressions linéaires ainsi que les constantes multiplicatrices, nous obtenons donc &amp;lt;math&amp;gt;\mathcal{O}(g(n)) = \mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT_image.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre ne provient pas du fruit de nos recherches personnelles mais &amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
 &lt;br /&gt;
L&#039;étude de ces algorithmes de multiplication nous a clairement montré l&#039;évolution de la complexité algorithmique permettant de gagner en efficacité lorsque la taille des données augmente.&lt;br /&gt;
&lt;br /&gt;
La multiplication classiqe, en &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;, résulte d&#039;une approche naïve qui devient rapidement inefficace pour les grands entiers ou les grandes matrices. L&#039;algorithme de Karatsuba nous montre qu&#039;organiser de manière intelligente ses calculs algébrique permet parfois de gagner beaucoup de multiplications, et donc de temps. Nous avons ainsi réussi à passer d&#039;une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt; à &amp;lt;math&amp;gt;\mathcal{O}(n^{log_2(3)})&amp;lt;/math&amp;gt;, soit environ &amp;lt;math&amp;gt;\mathcal{O}(n^{1/585})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Des méthodes encore plus avancées comme utilise l&#039;algorithme de Toom-Cook-3 continue dans cette idées de divisions d&#039;entiers en blocs plus petits, tandis que la transformée de Fourier rapide change complètement de point de vue. En effet la FFT n&#039;utilise pas la représentation classique des entiers mais fait appel à une vision polynomiale, ce qui transforme la convolution classique en multiplication terme à terme, ce qui lui permet d&#039;atteindre une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Dans le cas des matrices, les mêmes logiques apparaissent comme par exemple avec l&#039;algorithme de Strassen qui se rapproche très fortement de Karatsuba en combinant de manière astucieuse les parties d&#039;entiers qui avaient été précédemment découpée.&lt;br /&gt;
&lt;br /&gt;
On remarque néanmoins que la majorité des algorithmes efficaces s&#039;orientent vers une approche récursive et non itérative. Néanmoins, nous avons pu trouver certaines de ces implémentations (comme par exemple la FFT) en itérative. &lt;br /&gt;
&lt;br /&gt;
Avec ces recherches, nous comprenons que la réorganisations des calculs algébriques peuvent aider à gagner en complexité mais que pour faire de réels progrès, il nous faudra surement changer notre point de vue une nouvelle fois, et aborder le problème sous un angle nouveau comme le font dors et déjà les algorithmes les plus performants.&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
Utilisation du site de production graphique &amp;quot;Desmos&amp;quot; : https://www.desmos.com/calculator?lang=fr&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : &lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=PZ2gdWdKHAA &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&amp;lt;ul&amp;gt; &lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://fr.wikipedia.org/wiki/Interpolation_polynomiale &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17044</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17044"/>
		<updated>2026-05-11T18:54:03Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* Conclusion */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calculs a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrées qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs étroitement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calculs nécessaires en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt; f(n) &amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cet algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt;f(x) = x^2&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karatsuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt; b &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 &amp;lt;math&amp;gt;Résultat = w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice pairs et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(x)=P_{\text{pair}}(x^2) + x \cdot P_{\text{impair}}(x^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 1 : Ecrire les deux facteurs sous la forme de polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 2 : Effectuer la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; des deux polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 3 : Faire le produit terme à terme des &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; obtenues&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 4 : Faire l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; du produit&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 5 : Gérer les retenues et écrire le nombre qui en découle&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or, parmi toutes ces étapes, certaines sont négligeable comme l&#039;étape 1 et 5.&lt;br /&gt;
&lt;br /&gt;
Pour connaitre la complexité de l&#039;algorithme, additionnons les complexités des étapes restantes :&lt;br /&gt;
&lt;br /&gt;
Une &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt; car elle fait &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; évaluation sur &amp;lt;math&amp;gt; log(n)&amp;lt;/math&amp;gt; récursions. Dans le cadre de notre algorithme, nous devons faire la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; de nos deux facteurs. Soit &amp;lt;math&amp;gt;2\times \mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Pour le produit terme à terme, nous faisons naturellement &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&lt;br /&gt;
&lt;br /&gt;
Pour finir, l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; a la même complexité que la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt;. Dans le cadre de notre algorithme nous l&#039;emploierons qu&#039;une seule fois, ce qui fait une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Après addition de toutes ces complexités, nous obtenons la complexité réelle de &amp;lt;math&amp;gt;3\mathcal{O}(n\times logn) + \mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
D&#039;après le Master Theorem, nous avons donc &amp;lt;math&amp;gt; f(n) = 3(n\times logn)+n &amp;lt;/math&amp;gt;, cherchons &amp;lt;math&amp;gt;f(n)\in\mathcal{O}(g(n))&amp;lt;/math&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
Nous pouvons ignorer les expressions linéaires ainsi que les constantes multiplicatrices, nous obtenons donc &amp;lt;math&amp;gt;\mathcal{O}(g(n)) = \mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT_image.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre ne provient pas du fruit de nos recherches personnelles mais &amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
 &lt;br /&gt;
L&#039;étude de ces algorithmes de multiplication nous a clairement montré l&#039;évolution de la complexité algorithmique permettant de gagner en efficacité lorsque la taille des données augmente.&lt;br /&gt;
&lt;br /&gt;
La multiplication classiqe, en &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;, résulte d&#039;une approche naïve qui devient rapidement inefficace pour les grands entiers ou les grandes matrices. L&#039;algorithme de Karatsuba nous montre qu&#039;organiser de manière intelligente ses calculs algébrique permet parfois de gagner beaucoup de multiplications, et donc de temps. Nous avons ainsi réussi à passer d&#039;une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt; à &amp;lt;math&amp;gt;\mathcal{O}(n^{log_2(3)})&amp;lt;/math&amp;gt;, soit environ &amp;lt;math&amp;gt;\mathcal{O}(n^{1/585})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Des méthodes encore plus avancées comme utilise l&#039;algorithme de Toom-Cook-3 continue dans cette idées de divisions d&#039;entiers en blocs plus petits, tandis que la transformée de Fourier rapide change complètement de point de vue. En effet la FFT n&#039;utilise pas la représentation classique des entiers mais fait appel à une vision polynomiale, ce qui transforme la convolution classique en multiplication terme à terme, ce qui lui permet d&#039;atteindre une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Dans le cas des matrices, les mêmes logiques apparaissent comme par exemple avec l&#039;algorithme de Strassen qui se rapproche très fortement de Karatsuba en combinant de manière astucieuse les parties d&#039;entiers qui avaient été précédemment découpée.&lt;br /&gt;
&lt;br /&gt;
On remarque néanmoins que la majorité des algorithmes efficaces s&#039;orientent vers une approche récursive et non itérative. Néanmoins, nous avons pu trouver certaines de ces implémentations (comme par exemple la FFT) en itérative. &lt;br /&gt;
&lt;br /&gt;
Avec ces recherches, nous comprenons que la réorganisations des calculs algébriques peuvent aider à gagner en complexité mais que pour faire de réels progrès, il nous faudra surement changer notre point de vue une nouvelle fois, et aborder le problème sous un angle nouveau comme le font dors et déjà les algorithmes les plus performants.&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : &lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=PZ2gdWdKHAA &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&amp;lt;ul&amp;gt; &lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://fr.wikipedia.org/wiki/Interpolation_polynomiale &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17043</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17043"/>
		<updated>2026-05-11T18:53:29Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* Conclusion */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calculs a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrées qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs étroitement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calculs nécessaires en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt; f(n) &amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cet algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt;f(x) = x^2&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karatsuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt; b &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 &amp;lt;math&amp;gt;Résultat = w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice pairs et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(x)=P_{\text{pair}}(x^2) + x \cdot P_{\text{impair}}(x^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 1 : Ecrire les deux facteurs sous la forme de polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 2 : Effectuer la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; des deux polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 3 : Faire le produit terme à terme des &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; obtenues&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 4 : Faire l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; du produit&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 5 : Gérer les retenues et écrire le nombre qui en découle&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or, parmi toutes ces étapes, certaines sont négligeable comme l&#039;étape 1 et 5.&lt;br /&gt;
&lt;br /&gt;
Pour connaitre la complexité de l&#039;algorithme, additionnons les complexités des étapes restantes :&lt;br /&gt;
&lt;br /&gt;
Une &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt; car elle fait &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; évaluation sur &amp;lt;math&amp;gt; log(n)&amp;lt;/math&amp;gt; récursions. Dans le cadre de notre algorithme, nous devons faire la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; de nos deux facteurs. Soit &amp;lt;math&amp;gt;2\times \mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Pour le produit terme à terme, nous faisons naturellement &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&lt;br /&gt;
&lt;br /&gt;
Pour finir, l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; a la même complexité que la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt;. Dans le cadre de notre algorithme nous l&#039;emploierons qu&#039;une seule fois, ce qui fait une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Après addition de toutes ces complexités, nous obtenons la complexité réelle de &amp;lt;math&amp;gt;3\mathcal{O}(n\times logn) + \mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
D&#039;après le Master Theorem, nous avons donc &amp;lt;math&amp;gt; f(n) = 3(n\times logn)+n &amp;lt;/math&amp;gt;, cherchons &amp;lt;math&amp;gt;f(n)\in\mathcal{O}(g(n))&amp;lt;/math&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
Nous pouvons ignorer les expressions linéaires ainsi que les constantes multiplicatrices, nous obtenons donc &amp;lt;math&amp;gt;\mathcal{O}(g(n)) = \mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT_image.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre ne provient pas du fruit de nos recherches personnelles mais &amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
 &lt;br /&gt;
L&#039;étude de ces algorithmes de multiplication nous a clairement montré l&#039;évolution de la complexité algorithmique permettant de gagner en efficacité lorsque la taille des données augmente.&lt;br /&gt;
&lt;br /&gt;
La multiplication classiqe, en &amp;lt;math&amp;gt; \mathcal{O}(n^2) &amp;lt;/math&amp;gt;, résulte d&#039;une approche naïve qui devient rapidement inefficace pour les grands entiers ou les grandes matrices. L&#039;algorithme de Karatsuba nous montre qu&#039;organiser de manière intelligente ses calculs algébrique permet parfois de gagner beaucoup de multiplications, et donc de temps. Nous avons ainsi réussi à passer d&#039;une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt; à &amp;lt;math&amp;gt;\mathcal{O}(n^{log_2(3)})&amp;lt;/math&amp;gt;, soit environ &amp;lt;math&amp;gt;\mathcal{O}(n^{1/585})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Des méthodes encore plus avancées comme utilise l&#039;algorithme de Toom-Cook-3 continue dans cette idées de divisions d&#039;entiers en blocs plus petits, tandis que la transformée de Fourier rapide change complètement de point de vue. En effet la FFT n&#039;utilise pas la représentation classique des entiers mais fait appel à une vision polynomiale, ce qui transforme la convolution classique en multiplication terme à terme, ce qui lui permet d&#039;atteindre une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Dans le cas des matrices, les mêmes logiques apparaissent comme par exemple avec l&#039;algorithme de Strassen qui se rapproche très fortement de Karatsuba en combinant de manière astucieuse les parties d&#039;entiers qui avaient été précédemment découpée.&lt;br /&gt;
&lt;br /&gt;
On remarque néanmoins que la majorité des algorithmes efficaces s&#039;orientent vers une approche récursive et non itérative. Néanmoins, nous avons pu trouver certaines de ces implémentations (comme par exemple la FFT) en itérative. &lt;br /&gt;
&lt;br /&gt;
Avec ces recherches, nous comprenons que la réorganisations des calculs algébriques peuvent aider à gagner en complexité mais que pour faire de réels progrès, il nous faudra surement changer notre point de vue une nouvelle fois, et aborder le problème sous un angle nouveau comme le font dors et déjà les algorithmes les plus performants.&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : &lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=PZ2gdWdKHAA &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&amp;lt;ul&amp;gt; &lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://fr.wikipedia.org/wiki/Interpolation_polynomiale &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17042</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17042"/>
		<updated>2026-05-11T18:38:10Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* Notation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calculs a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrées qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs étroitement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calculs nécessaires en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt; f(n) &amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cet algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt;f(x) = x^2&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karatsuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt; b &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 &amp;lt;math&amp;gt;Résultat = w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice pairs et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(x)=P_{\text{pair}}(x^2) + x \cdot P_{\text{impair}}(x^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 1 : Ecrire les deux facteurs sous la forme de polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 2 : Effectuer la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; des deux polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 3 : Faire le produit terme à terme des &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; obtenues&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 4 : Faire l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; du produit&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 5 : Gérer les retenues et écrire le nombre qui en découle&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or, parmi toutes ces étapes, certaines sont négligeable comme l&#039;étape 1 et 5.&lt;br /&gt;
&lt;br /&gt;
Pour connaitre la complexité de l&#039;algorithme, additionnons les complexités des étapes restantes :&lt;br /&gt;
&lt;br /&gt;
Une &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt; car elle fait &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; évaluation sur &amp;lt;math&amp;gt; log(n)&amp;lt;/math&amp;gt; récursions. Dans le cadre de notre algorithme, nous devons faire la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; de nos deux facteurs. Soit &amp;lt;math&amp;gt;2\times \mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Pour le produit terme à terme, nous faisons naturellement &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&lt;br /&gt;
&lt;br /&gt;
Pour finir, l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; a la même complexité que la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt;. Dans le cadre de notre algorithme nous l&#039;emploierons qu&#039;une seule fois, ce qui fait une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Après addition de toutes ces complexités, nous obtenons la complexité réelle de &amp;lt;math&amp;gt;3\mathcal{O}(n\times logn) + \mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
D&#039;après le Master Theorem, nous avons donc &amp;lt;math&amp;gt; f(n) = 3(n\times logn)+n &amp;lt;/math&amp;gt;, cherchons &amp;lt;math&amp;gt;f(n)\in\mathcal{O}(g(n))&amp;lt;/math&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
Nous pouvons ignorer les expressions linéaires ainsi que les constantes multiplicatrices, nous obtenons donc &amp;lt;math&amp;gt;\mathcal{O}(g(n)) = \mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT_image.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre ne provient pas du fruit de nos recherches personnelles mais &amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
 &lt;br /&gt;
L&#039;étude de ces algorithmes de multiplication nous a clairement montré l&#039;évolution de la complexité algorithmique permettant de gagner en efficacité lorsque la taille des données augmente.&lt;br /&gt;
&lt;br /&gt;
La multiplication classiqe, en &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;, résulte d&#039;une approche naïve qui devient rapidement inefficace pour les grands entiers ou les grandes matrices. L&#039;algorithme de Karatsuba nous montre qu&#039;organiser de manière intelligente ses calculs algébrique permet parfois de gagner beaucoup de multiplications, et donc de temps. Nous avons ainsi réussi à passer d&#039;une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt; à &amp;lt;math&amp;gt;\mathcal{O}(n^{log_2(3)})&amp;lt;/math&amp;gt;, soit environ &amp;lt;math&amp;gt;\mathcal{O}(n^{1/585})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Des méthodes encore plus avancées comme utilise l&#039;algorithme de Toom-Cook-3 continue dans cette idées de divisions d&#039;entiers en blocs plus petits, tandis que la transformée de Fourier rapide change complètement de point de vue. En effet la FFT n&#039;utilise pas la représentation classique des entiers mais fait appel à une vision polynomiale, ce qui transforme la convolution classique en multiplication terme à terme, ce qui lui permet d&#039;atteindre une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Dans le cas des matrices, les mêmes logiques apparaissent comme par exemple avec l&#039;algorithme de Strassen qui se rapproche très fortement de Karatsuba en combinant de manière astucieuse les parties d&#039;entiers qui avaient été précédemment découpée.&lt;br /&gt;
&lt;br /&gt;
On remarque néanmoins que la majorité des algorithmes efficaces s&#039;orientent vers une approche récursive et non itérative. Néanmoins, nous avons pu trouver certaines de ces implémentations (comme par exemple la FFT) en itérative. &lt;br /&gt;
&lt;br /&gt;
Avec ces recherches, nous comprenons que la réorganisations des calculs algébriques peuvent aider à gagner en complexité mais que pour faire de réels progrès, il nous faudra surement changer notre point de vue une nouvelle fois, et aborder le problème sous un angle nouveau comme le font dors et déjà les algorithmes les plus performants.&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : &lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=PZ2gdWdKHAA &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&amp;lt;ul&amp;gt; &lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://fr.wikipedia.org/wiki/Interpolation_polynomiale &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17041</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17041"/>
		<updated>2026-05-11T18:37:53Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* Sources */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calculs a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrées qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs étroitement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calculs nécessaires en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cet algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt;f(x) = x^2&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karatsuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt; b &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 &amp;lt;math&amp;gt;Résultat = w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice pairs et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(x)=P_{\text{pair}}(x^2) + x \cdot P_{\text{impair}}(x^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 1 : Ecrire les deux facteurs sous la forme de polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 2 : Effectuer la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; des deux polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 3 : Faire le produit terme à terme des &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; obtenues&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 4 : Faire l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; du produit&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 5 : Gérer les retenues et écrire le nombre qui en découle&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or, parmi toutes ces étapes, certaines sont négligeable comme l&#039;étape 1 et 5.&lt;br /&gt;
&lt;br /&gt;
Pour connaitre la complexité de l&#039;algorithme, additionnons les complexités des étapes restantes :&lt;br /&gt;
&lt;br /&gt;
Une &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt; car elle fait &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; évaluation sur &amp;lt;math&amp;gt; log(n)&amp;lt;/math&amp;gt; récursions. Dans le cadre de notre algorithme, nous devons faire la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; de nos deux facteurs. Soit &amp;lt;math&amp;gt;2\times \mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Pour le produit terme à terme, nous faisons naturellement &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&lt;br /&gt;
&lt;br /&gt;
Pour finir, l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; a la même complexité que la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt;. Dans le cadre de notre algorithme nous l&#039;emploierons qu&#039;une seule fois, ce qui fait une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Après addition de toutes ces complexités, nous obtenons la complexité réelle de &amp;lt;math&amp;gt;3\mathcal{O}(n\times logn) + \mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
D&#039;après le Master Theorem, nous avons donc &amp;lt;math&amp;gt; f(n) = 3(n\times logn)+n &amp;lt;/math&amp;gt;, cherchons &amp;lt;math&amp;gt;f(n)\in\mathcal{O}(g(n))&amp;lt;/math&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
Nous pouvons ignorer les expressions linéaires ainsi que les constantes multiplicatrices, nous obtenons donc &amp;lt;math&amp;gt;\mathcal{O}(g(n)) = \mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT_image.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre ne provient pas du fruit de nos recherches personnelles mais &amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
 &lt;br /&gt;
L&#039;étude de ces algorithmes de multiplication nous a clairement montré l&#039;évolution de la complexité algorithmique permettant de gagner en efficacité lorsque la taille des données augmente.&lt;br /&gt;
&lt;br /&gt;
La multiplication classiqe, en &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;, résulte d&#039;une approche naïve qui devient rapidement inefficace pour les grands entiers ou les grandes matrices. L&#039;algorithme de Karatsuba nous montre qu&#039;organiser de manière intelligente ses calculs algébrique permet parfois de gagner beaucoup de multiplications, et donc de temps. Nous avons ainsi réussi à passer d&#039;une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt; à &amp;lt;math&amp;gt;\mathcal{O}(n^{log_2(3)})&amp;lt;/math&amp;gt;, soit environ &amp;lt;math&amp;gt;\mathcal{O}(n^{1/585})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Des méthodes encore plus avancées comme utilise l&#039;algorithme de Toom-Cook-3 continue dans cette idées de divisions d&#039;entiers en blocs plus petits, tandis que la transformée de Fourier rapide change complètement de point de vue. En effet la FFT n&#039;utilise pas la représentation classique des entiers mais fait appel à une vision polynomiale, ce qui transforme la convolution classique en multiplication terme à terme, ce qui lui permet d&#039;atteindre une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Dans le cas des matrices, les mêmes logiques apparaissent comme par exemple avec l&#039;algorithme de Strassen qui se rapproche très fortement de Karatsuba en combinant de manière astucieuse les parties d&#039;entiers qui avaient été précédemment découpée.&lt;br /&gt;
&lt;br /&gt;
On remarque néanmoins que la majorité des algorithmes efficaces s&#039;orientent vers une approche récursive et non itérative. Néanmoins, nous avons pu trouver certaines de ces implémentations (comme par exemple la FFT) en itérative. &lt;br /&gt;
&lt;br /&gt;
Avec ces recherches, nous comprenons que la réorganisations des calculs algébriques peuvent aider à gagner en complexité mais que pour faire de réels progrès, il nous faudra surement changer notre point de vue une nouvelle fois, et aborder le problème sous un angle nouveau comme le font dors et déjà les algorithmes les plus performants.&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : &lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=PZ2gdWdKHAA &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&amp;lt;ul&amp;gt; &lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://fr.wikipedia.org/wiki/Interpolation_polynomiale &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17040</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17040"/>
		<updated>2026-05-11T18:37:12Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* A) Fonctionnement */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calculs a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrées qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs étroitement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calculs nécessaires en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cet algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt;f(x) = x^2&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karatsuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt; b &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 &amp;lt;math&amp;gt;Résultat = w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice pairs et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(x)=P_{\text{pair}}(x^2) + x \cdot P_{\text{impair}}(x^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 1 : Ecrire les deux facteurs sous la forme de polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 2 : Effectuer la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; des deux polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 3 : Faire le produit terme à terme des &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; obtenues&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 4 : Faire l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; du produit&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 5 : Gérer les retenues et écrire le nombre qui en découle&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or, parmi toutes ces étapes, certaines sont négligeable comme l&#039;étape 1 et 5.&lt;br /&gt;
&lt;br /&gt;
Pour connaitre la complexité de l&#039;algorithme, additionnons les complexités des étapes restantes :&lt;br /&gt;
&lt;br /&gt;
Une &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt; car elle fait &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; évaluation sur &amp;lt;math&amp;gt; log(n)&amp;lt;/math&amp;gt; récursions. Dans le cadre de notre algorithme, nous devons faire la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; de nos deux facteurs. Soit &amp;lt;math&amp;gt;2\times \mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Pour le produit terme à terme, nous faisons naturellement &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&lt;br /&gt;
&lt;br /&gt;
Pour finir, l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; a la même complexité que la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt;. Dans le cadre de notre algorithme nous l&#039;emploierons qu&#039;une seule fois, ce qui fait une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Après addition de toutes ces complexités, nous obtenons la complexité réelle de &amp;lt;math&amp;gt;3\mathcal{O}(n\times logn) + \mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
D&#039;après le Master Theorem, nous avons donc &amp;lt;math&amp;gt; f(n) = 3(n\times logn)+n &amp;lt;/math&amp;gt;, cherchons &amp;lt;math&amp;gt;f(n)\in\mathcal{O}(g(n))&amp;lt;/math&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
Nous pouvons ignorer les expressions linéaires ainsi que les constantes multiplicatrices, nous obtenons donc &amp;lt;math&amp;gt;\mathcal{O}(g(n)) = \mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT_image.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre ne provient pas du fruit de nos recherches personnelles mais &amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
 &lt;br /&gt;
L&#039;étude de ces algorithmes de multiplication nous a clairement montré l&#039;évolution de la complexité algorithmique permettant de gagner en efficacité lorsque la taille des données augmente.&lt;br /&gt;
&lt;br /&gt;
La multiplication classiqe, en &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;, résulte d&#039;une approche naïve qui devient rapidement inefficace pour les grands entiers ou les grandes matrices. L&#039;algorithme de Karatsuba nous montre qu&#039;organiser de manière intelligente ses calculs algébrique permet parfois de gagner beaucoup de multiplications, et donc de temps. Nous avons ainsi réussi à passer d&#039;une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt; à &amp;lt;math&amp;gt;\mathcal{O}(n^{log_2(3)})&amp;lt;/math&amp;gt;, soit environ &amp;lt;math&amp;gt;\mathcal{O}(n^{1/585})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Des méthodes encore plus avancées comme utilise l&#039;algorithme de Toom-Cook-3 continue dans cette idées de divisions d&#039;entiers en blocs plus petits, tandis que la transformée de Fourier rapide change complètement de point de vue. En effet la FFT n&#039;utilise pas la représentation classique des entiers mais fait appel à une vision polynomiale, ce qui transforme la convolution classique en multiplication terme à terme, ce qui lui permet d&#039;atteindre une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Dans le cas des matrices, les mêmes logiques apparaissent comme par exemple avec l&#039;algorithme de Strassen qui se rapproche très fortement de Karatsuba en combinant de manière astucieuse les parties d&#039;entiers qui avaient été précédemment découpée.&lt;br /&gt;
&lt;br /&gt;
On remarque néanmoins que la majorité des algorithmes efficaces s&#039;orientent vers une approche récursive et non itérative. Néanmoins, nous avons pu trouver certaines de ces implémentations (comme par exemple la FFT) en itérative. &lt;br /&gt;
&lt;br /&gt;
Avec ces recherches, nous comprenons que la réorganisations des calculs algébriques peuvent aider à gagner en complexité mais que pour faire de réels progrès, il nous faudra surement changer notre point de vue une nouvelle fois, et aborder le problème sous un angle nouveau comme le font dors et déjà les algorithmes les plus performants.&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : https://www.youtube.com/watch?v=PZ2gdWdKHAA&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&amp;lt;ul&amp;gt; &lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://fr.wikipedia.org/wiki/Interpolation_polynomiale &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17039</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17039"/>
		<updated>2026-05-11T18:36:49Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* A) Fonctionnement */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calculs a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrées qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs étroitement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calculs nécessaires en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cet algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; m &amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt;f(x) = x^2&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karatsuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt; b &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 &amp;lt;math&amp;gt;Résultat = w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice pairs et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(x)=P_{\text{pair}}(x^2) + x \cdot P_{\text{impair}}(x^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 1 : Ecrire les deux facteurs sous la forme de polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 2 : Effectuer la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; des deux polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 3 : Faire le produit terme à terme des &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; obtenues&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 4 : Faire l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; du produit&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 5 : Gérer les retenues et écrire le nombre qui en découle&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or, parmi toutes ces étapes, certaines sont négligeable comme l&#039;étape 1 et 5.&lt;br /&gt;
&lt;br /&gt;
Pour connaitre la complexité de l&#039;algorithme, additionnons les complexités des étapes restantes :&lt;br /&gt;
&lt;br /&gt;
Une &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt; car elle fait &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; évaluation sur &amp;lt;math&amp;gt; log(n)&amp;lt;/math&amp;gt; récursions. Dans le cadre de notre algorithme, nous devons faire la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; de nos deux facteurs. Soit &amp;lt;math&amp;gt;2\times \mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Pour le produit terme à terme, nous faisons naturellement &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&lt;br /&gt;
&lt;br /&gt;
Pour finir, l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; a la même complexité que la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt;. Dans le cadre de notre algorithme nous l&#039;emploierons qu&#039;une seule fois, ce qui fait une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Après addition de toutes ces complexités, nous obtenons la complexité réelle de &amp;lt;math&amp;gt;3\mathcal{O}(n\times logn) + \mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
D&#039;après le Master Theorem, nous avons donc &amp;lt;math&amp;gt; f(n) = 3(n\times logn)+n &amp;lt;/math&amp;gt;, cherchons &amp;lt;math&amp;gt;f(n)\in\mathcal{O}(g(n))&amp;lt;/math&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
Nous pouvons ignorer les expressions linéaires ainsi que les constantes multiplicatrices, nous obtenons donc &amp;lt;math&amp;gt;\mathcal{O}(g(n)) = \mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT_image.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre ne provient pas du fruit de nos recherches personnelles mais &amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
 &lt;br /&gt;
L&#039;étude de ces algorithmes de multiplication nous a clairement montré l&#039;évolution de la complexité algorithmique permettant de gagner en efficacité lorsque la taille des données augmente.&lt;br /&gt;
&lt;br /&gt;
La multiplication classiqe, en &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;, résulte d&#039;une approche naïve qui devient rapidement inefficace pour les grands entiers ou les grandes matrices. L&#039;algorithme de Karatsuba nous montre qu&#039;organiser de manière intelligente ses calculs algébrique permet parfois de gagner beaucoup de multiplications, et donc de temps. Nous avons ainsi réussi à passer d&#039;une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt; à &amp;lt;math&amp;gt;\mathcal{O}(n^{log_2(3)})&amp;lt;/math&amp;gt;, soit environ &amp;lt;math&amp;gt;\mathcal{O}(n^{1/585})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Des méthodes encore plus avancées comme utilise l&#039;algorithme de Toom-Cook-3 continue dans cette idées de divisions d&#039;entiers en blocs plus petits, tandis que la transformée de Fourier rapide change complètement de point de vue. En effet la FFT n&#039;utilise pas la représentation classique des entiers mais fait appel à une vision polynomiale, ce qui transforme la convolution classique en multiplication terme à terme, ce qui lui permet d&#039;atteindre une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Dans le cas des matrices, les mêmes logiques apparaissent comme par exemple avec l&#039;algorithme de Strassen qui se rapproche très fortement de Karatsuba en combinant de manière astucieuse les parties d&#039;entiers qui avaient été précédemment découpée.&lt;br /&gt;
&lt;br /&gt;
On remarque néanmoins que la majorité des algorithmes efficaces s&#039;orientent vers une approche récursive et non itérative. Néanmoins, nous avons pu trouver certaines de ces implémentations (comme par exemple la FFT) en itérative. &lt;br /&gt;
&lt;br /&gt;
Avec ces recherches, nous comprenons que la réorganisations des calculs algébriques peuvent aider à gagner en complexité mais que pour faire de réels progrès, il nous faudra surement changer notre point de vue une nouvelle fois, et aborder le problème sous un angle nouveau comme le font dors et déjà les algorithmes les plus performants.&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : https://www.youtube.com/watch?v=PZ2gdWdKHAA&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&amp;lt;ul&amp;gt; &lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://fr.wikipedia.org/wiki/Interpolation_polynomiale &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17028</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17028"/>
		<updated>2026-05-11T15:41:45Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* A) Fonctionnement */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calculs a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrées qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs étroitement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calculs nécessaires en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cet algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; m &amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt;f(x) = x^2&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karatsuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt; b &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 &amp;lt;math&amp;gt;Résultat = w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice pairs et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(x)=P_{\text{pair}}(x^2) + x \cdot P_{\text{impair}}(x^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 1 : Ecrire les deux facteurs sous la forme de polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 2 : Effectuer la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; des deux polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 3 : Faire le produit terme à terme des &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; obtenues&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 4 : Faire l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; du produit&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 5 : Gérer les retenues et écrire le nombre qui en découle&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or, parmi toutes ces étapes, certaines sont négligeable comme l&#039;étape 1 et 5.&lt;br /&gt;
&lt;br /&gt;
Pour connaitre la complexité de l&#039;algorithme, additionnons les complexités des étapes restantes :&lt;br /&gt;
&lt;br /&gt;
Une &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt; car elle fait &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; évaluation sur &amp;lt;math&amp;gt; log(n)&amp;lt;/math&amp;gt; récursions. Dans le cadre de notre algorithme, nous devons faire la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; de nos deux facteurs. Soit &amp;lt;math&amp;gt;2\times \mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Pour le produit terme à terme, nous faisons naturellement &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&lt;br /&gt;
&lt;br /&gt;
Pour finir, l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; a la même complexité que la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt;. Dans le cadre de notre algorithme nous l&#039;emploierons qu&#039;une seule fois, ce qui fait une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Après addition de toutes ces complexités, nous obtenons la complexité réelle de &amp;lt;math&amp;gt;3\mathcal{O}(n\times logn) + \mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
D&#039;après le Master Theorem, nous avons donc &amp;lt;math&amp;gt; f(n) = 3(n\times logn)+n &amp;lt;/math&amp;gt;, cherchons &amp;lt;math&amp;gt;f(n)\in\mathcal{O}(g(n))&amp;lt;/math&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
Nous pouvons ignorer les expressions linéaires ainsi que les constantes multiplicatrices, nous obtenons donc &amp;lt;math&amp;gt;\mathcal{O}(g(n)) = \mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT_image.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre ne provient pas du fruit de nos recherches personnelles mais &amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
 &lt;br /&gt;
L&#039;étude de ces algorithmes de multiplication nous a clairement montré l&#039;évolution de la complexité algorithmique permettant de gagner en efficacité lorsque la taille des données augmente.&lt;br /&gt;
&lt;br /&gt;
La multiplication classiqe, en &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;, résulte d&#039;une approche naïve qui devient rapidement inefficace pour les grands entiers ou les grandes matrices. L&#039;algorithme de Karatsuba nous montre qu&#039;organiser de manière intelligente ses calculs algébrique permet parfois de gagner beaucoup de multiplications, et donc de temps. Nous avons ainsi réussi à passer d&#039;une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt; à &amp;lt;math&amp;gt;\mathcal{O}(n^{log_2(3)})&amp;lt;/math&amp;gt;, soit environ &amp;lt;math&amp;gt;\mathcal{O}(n^{1/585})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Des méthodes encore plus avancées comme utilise l&#039;algorithme de Toom-Cook-3 continue dans cette idées de divisions d&#039;entiers en blocs plus petits, tandis que la transformée de Fourier rapide change complètement de point de vue. En effet la FFT n&#039;utilise pas la représentation classique des entiers mais fait appel à une vision polynomiale, ce qui transforme la convolution classique en multiplication terme à terme, ce qui lui permet d&#039;atteindre une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Dans le cas des matrices, les mêmes logiques apparaissent comme par exemple avec l&#039;algorithme de Strassen qui se rapproche très fortement de Karatsuba en combinant de manière astucieuse les parties d&#039;entiers qui avaient été précédemment découpée.&lt;br /&gt;
&lt;br /&gt;
On remarque néanmoins que la majorité des algorithmes efficaces s&#039;orientent vers une approche récursive et non itérative. Néanmoins, nous avons pu trouver certaines de ces implémentations (comme par exemple la FFT) en itérative. &lt;br /&gt;
&lt;br /&gt;
Avec ces recherches, nous comprenons que la réorganisations des calculs algébriques peuvent aider à gagner en complexité mais que pour faire de réels progrès, il nous faudra surement changer notre point de vue une nouvelle fois, et aborder le problème sous un angle nouveau comme le font dors et déjà les algorithmes les plus performants.&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : https://www.youtube.com/watch?v=PZ2gdWdKHAA&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&amp;lt;ul&amp;gt; &lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://fr.wikipedia.org/wiki/Interpolation_polynomiale &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17027</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17027"/>
		<updated>2026-05-11T15:40:48Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* Conclusion */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calculs a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrées qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs étroitement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calculs nécessaires en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cet algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; m &amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt;f(x) = x^2&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karatsuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt;b&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 &amp;lt;math&amp;gt;Résultat = w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice pairs et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(x)=P_{\text{pair}}(x^2) + x \cdot P_{\text{impair}}(x^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 1 : Ecrire les deux facteurs sous la forme de polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 2 : Effectuer la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; des deux polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 3 : Faire le produit terme à terme des &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; obtenues&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 4 : Faire l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; du produit&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 5 : Gérer les retenues et écrire le nombre qui en découle&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or, parmi toutes ces étapes, certaines sont négligeable comme l&#039;étape 1 et 5.&lt;br /&gt;
&lt;br /&gt;
Pour connaitre la complexité de l&#039;algorithme, additionnons les complexités des étapes restantes :&lt;br /&gt;
&lt;br /&gt;
Une &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt; car elle fait &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; évaluation sur &amp;lt;math&amp;gt; log(n)&amp;lt;/math&amp;gt; récursions. Dans le cadre de notre algorithme, nous devons faire la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; de nos deux facteurs. Soit &amp;lt;math&amp;gt;2\times \mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Pour le produit terme à terme, nous faisons naturellement &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&lt;br /&gt;
&lt;br /&gt;
Pour finir, l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; a la même complexité que la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt;. Dans le cadre de notre algorithme nous l&#039;emploierons qu&#039;une seule fois, ce qui fait une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Après addition de toutes ces complexités, nous obtenons la complexité réelle de &amp;lt;math&amp;gt;3\mathcal{O}(n\times logn) + \mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
D&#039;après le Master Theorem, nous avons donc &amp;lt;math&amp;gt; f(n) = 3(n\times logn)+n &amp;lt;/math&amp;gt;, cherchons &amp;lt;math&amp;gt;f(n)\in\mathcal{O}(g(n))&amp;lt;/math&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
Nous pouvons ignorer les expressions linéaires ainsi que les constantes multiplicatrices, nous obtenons donc &amp;lt;math&amp;gt;\mathcal{O}(g(n)) = \mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT_image.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre ne provient pas du fruit de nos recherches personnelles mais &amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
 &lt;br /&gt;
L&#039;étude de ces algorithmes de multiplication nous a clairement montré l&#039;évolution de la complexité algorithmique permettant de gagner en efficacité lorsque la taille des données augmente.&lt;br /&gt;
&lt;br /&gt;
La multiplication classiqe, en &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;, résulte d&#039;une approche naïve qui devient rapidement inefficace pour les grands entiers ou les grandes matrices. L&#039;algorithme de Karatsuba nous montre qu&#039;organiser de manière intelligente ses calculs algébrique permet parfois de gagner beaucoup de multiplications, et donc de temps. Nous avons ainsi réussi à passer d&#039;une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt; à &amp;lt;math&amp;gt;\mathcal{O}(n^{log_2(3)})&amp;lt;/math&amp;gt;, soit environ &amp;lt;math&amp;gt;\mathcal{O}(n^{1/585})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Des méthodes encore plus avancées comme utilise l&#039;algorithme de Toom-Cook-3 continue dans cette idées de divisions d&#039;entiers en blocs plus petits, tandis que la transformée de Fourier rapide change complètement de point de vue. En effet la FFT n&#039;utilise pas la représentation classique des entiers mais fait appel à une vision polynomiale, ce qui transforme la convolution classique en multiplication terme à terme, ce qui lui permet d&#039;atteindre une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Dans le cas des matrices, les mêmes logiques apparaissent comme par exemple avec l&#039;algorithme de Strassen qui se rapproche très fortement de Karatsuba en combinant de manière astucieuse les parties d&#039;entiers qui avaient été précédemment découpée.&lt;br /&gt;
&lt;br /&gt;
On remarque néanmoins que la majorité des algorithmes efficaces s&#039;orientent vers une approche récursive et non itérative. Néanmoins, nous avons pu trouver certaines de ces implémentations (comme par exemple la FFT) en itérative. &lt;br /&gt;
&lt;br /&gt;
Avec ces recherches, nous comprenons que la réorganisations des calculs algébriques peuvent aider à gagner en complexité mais que pour faire de réels progrès, il nous faudra surement changer notre point de vue une nouvelle fois, et aborder le problème sous un angle nouveau comme le font dors et déjà les algorithmes les plus performants.&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : https://www.youtube.com/watch?v=PZ2gdWdKHAA&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&amp;lt;ul&amp;gt; &lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://fr.wikipedia.org/wiki/Interpolation_polynomiale &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17026</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17026"/>
		<updated>2026-05-11T15:23:48Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* Conclusion */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calculs a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrées qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs étroitement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calculs nécessaires en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cet algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; m &amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt;f(x) = x^2&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karatsuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt;b&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 &amp;lt;math&amp;gt;Résultat = w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice pairs et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(x)=P_{\text{pair}}(x^2) + x \cdot P_{\text{impair}}(x^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 1 : Ecrire les deux facteurs sous la forme de polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 2 : Effectuer la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; des deux polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 3 : Faire le produit terme à terme des &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; obtenues&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 4 : Faire l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; du produit&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 5 : Gérer les retenues et écrire le nombre qui en découle&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or, parmi toutes ces étapes, certaines sont négligeable comme l&#039;étape 1 et 5.&lt;br /&gt;
&lt;br /&gt;
Pour connaitre la complexité de l&#039;algorithme, additionnons les complexités des étapes restantes :&lt;br /&gt;
&lt;br /&gt;
Une &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt; car elle fait &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; évaluation sur &amp;lt;math&amp;gt; log(n)&amp;lt;/math&amp;gt; récursions. Dans le cadre de notre algorithme, nous devons faire la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; de nos deux facteurs. Soit &amp;lt;math&amp;gt;2\times \mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Pour le produit terme à terme, nous faisons naturellement &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&lt;br /&gt;
&lt;br /&gt;
Pour finir, l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; a la même complexité que la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt;. Dans le cadre de notre algorithme nous l&#039;emploierons qu&#039;une seule fois, ce qui fait une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Après addition de toutes ces complexités, nous obtenons la complexité réelle de &amp;lt;math&amp;gt;3\mathcal{O}(n\times logn) + \mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
D&#039;après le Master Theorem, nous avons donc &amp;lt;math&amp;gt; f(n) = 3(n\times logn)+n &amp;lt;/math&amp;gt;, cherchons &amp;lt;math&amp;gt;f(n)\in\mathcal{O}(g(n))&amp;lt;/math&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
Nous pouvons ignorer les expressions linéaires ainsi que les constantes multiplicatrices, nous obtenons donc &amp;lt;math&amp;gt;\mathcal{O}(g(n)) = \mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT_image.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre ne provient pas du fruit de nos recherches personnelles mais &amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
 &lt;br /&gt;
L&#039;étude de ces algorithmes de multiplication nous a clairement montré l&#039;évolution de la complexité algorithmique permettant de gagner en efficacité lorsque la taille des données augmente.&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve, en&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : https://www.youtube.com/watch?v=PZ2gdWdKHAA&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&amp;lt;ul&amp;gt; &lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://fr.wikipedia.org/wiki/Interpolation_polynomiale &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17025</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17025"/>
		<updated>2026-05-11T15:21:16Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* 3) La multiplication karastuba */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calculs a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrées qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs étroitement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calculs nécessaires en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cet algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; m &amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt;f(x) = x^2&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karatsuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt;b&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 &amp;lt;math&amp;gt;Résultat = w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice pairs et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(x)=P_{\text{pair}}(x^2) + x \cdot P_{\text{impair}}(x^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 1 : Ecrire les deux facteurs sous la forme de polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 2 : Effectuer la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; des deux polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 3 : Faire le produit terme à terme des &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; obtenues&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 4 : Faire l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; du produit&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 5 : Gérer les retenues et écrire le nombre qui en découle&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or, parmi toutes ces étapes, certaines sont négligeable comme l&#039;étape 1 et 5.&lt;br /&gt;
&lt;br /&gt;
Pour connaitre la complexité de l&#039;algorithme, additionnons les complexités des étapes restantes :&lt;br /&gt;
&lt;br /&gt;
Une &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt; car elle fait &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; évaluation sur &amp;lt;math&amp;gt; log(n)&amp;lt;/math&amp;gt; récursions. Dans le cadre de notre algorithme, nous devons faire la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; de nos deux facteurs. Soit &amp;lt;math&amp;gt;2\times \mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Pour le produit terme à terme, nous faisons naturellement &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&lt;br /&gt;
&lt;br /&gt;
Pour finir, l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; a la même complexité que la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt;. Dans le cadre de notre algorithme nous l&#039;emploierons qu&#039;une seule fois, ce qui fait une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Après addition de toutes ces complexités, nous obtenons la complexité réelle de &amp;lt;math&amp;gt;3\mathcal{O}(n\times logn) + \mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
D&#039;après le Master Theorem, nous avons donc &amp;lt;math&amp;gt; f(n) = 3(n\times logn)+n &amp;lt;/math&amp;gt;, cherchons &amp;lt;math&amp;gt;f(n)\in\mathcal{O}(g(n))&amp;lt;/math&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
Nous pouvons ignorer les expressions linéaires ainsi que les constantes multiplicatrices, nous obtenons donc &amp;lt;math&amp;gt;\mathcal{O}(g(n)) = \mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT_image.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre ne provient pas du fruit de nos recherches personnelles mais &amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
&lt;br /&gt;
( A FAIRE )&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : https://www.youtube.com/watch?v=PZ2gdWdKHAA&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&amp;lt;ul&amp;gt; &lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://fr.wikipedia.org/wiki/Interpolation_polynomiale &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17024</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17024"/>
		<updated>2026-05-11T15:19:52Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* A) Fonctionnement */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calculs a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrées qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs étroitement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calculs nécessaires en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cet algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; m &amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt;f(x) = x^2&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karastuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt;b&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 &amp;lt;math&amp;gt;Résultat = w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice pairs et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(x)=P_{\text{pair}}(x^2) + x \cdot P_{\text{impair}}(x^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 1 : Ecrire les deux facteurs sous la forme de polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 2 : Effectuer la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; des deux polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 3 : Faire le produit terme à terme des &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; obtenues&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 4 : Faire l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; du produit&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 5 : Gérer les retenues et écrire le nombre qui en découle&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or, parmi toutes ces étapes, certaines sont négligeable comme l&#039;étape 1 et 5.&lt;br /&gt;
&lt;br /&gt;
Pour connaitre la complexité de l&#039;algorithme, additionnons les complexités des étapes restantes :&lt;br /&gt;
&lt;br /&gt;
Une &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt; car elle fait &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; évaluation sur &amp;lt;math&amp;gt; log(n)&amp;lt;/math&amp;gt; récursions. Dans le cadre de notre algorithme, nous devons faire la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; de nos deux facteurs. Soit &amp;lt;math&amp;gt;2\times \mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Pour le produit terme à terme, nous faisons naturellement &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&lt;br /&gt;
&lt;br /&gt;
Pour finir, l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; a la même complexité que la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt;. Dans le cadre de notre algorithme nous l&#039;emploierons qu&#039;une seule fois, ce qui fait une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Après addition de toutes ces complexités, nous obtenons la complexité réelle de &amp;lt;math&amp;gt;3\mathcal{O}(n\times logn) + \mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
D&#039;après le Master Theorem, nous avons donc &amp;lt;math&amp;gt; f(n) = 3(n\times logn)+n &amp;lt;/math&amp;gt;, cherchons &amp;lt;math&amp;gt;f(n)\in\mathcal{O}(g(n))&amp;lt;/math&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
Nous pouvons ignorer les expressions linéaires ainsi que les constantes multiplicatrices, nous obtenons donc &amp;lt;math&amp;gt;\mathcal{O}(g(n)) = \mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT_image.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre ne provient pas du fruit de nos recherches personnelles mais &amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
&lt;br /&gt;
( A FAIRE )&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : https://www.youtube.com/watch?v=PZ2gdWdKHAA&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&amp;lt;ul&amp;gt; &lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://fr.wikipedia.org/wiki/Interpolation_polynomiale &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17023</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17023"/>
		<updated>2026-05-11T15:18:02Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* Exemple */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calculs a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrées qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs étroitement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calculs nécessaires en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cet algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; m &amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt;f(x) = x^2&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karastuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt;b&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 &amp;lt;math&amp;gt;Résultat = w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice paires et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(x)=P_{\text{pair}}(x^2) + x \cdot P_{\text{impair}}(x^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 1 : Ecrire les deux facteurs sous la forme de polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 2 : Effectuer la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; des deux polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 3 : Faire le produit terme à terme des &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; obtenues&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 4 : Faire l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; du produit&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 5 : Gérer les retenues et écrire le nombre qui en découle&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or, parmi toutes ces étapes, certaines sont négligeable comme l&#039;étape 1 et 5.&lt;br /&gt;
&lt;br /&gt;
Pour connaitre la complexité de l&#039;algorithme, additionnons les complexités des étapes restantes :&lt;br /&gt;
&lt;br /&gt;
Une &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt; car elle fait &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; évaluation sur &amp;lt;math&amp;gt; log(n)&amp;lt;/math&amp;gt; récursions. Dans le cadre de notre algorithme, nous devons faire la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; de nos deux facteurs. Soit &amp;lt;math&amp;gt;2\times \mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Pour le produit terme à terme, nous faisons naturellement &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&lt;br /&gt;
&lt;br /&gt;
Pour finir, l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; a la même complexité que la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt;. Dans le cadre de notre algorithme nous l&#039;emploierons qu&#039;une seule fois, ce qui fait une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Après addition de toutes ces complexités, nous obtenons la complexité réelle de &amp;lt;math&amp;gt;3\mathcal{O}(n\times logn) + \mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
D&#039;après le Master Theorem, nous avons donc &amp;lt;math&amp;gt; f(n) = 3(n\times logn)+n &amp;lt;/math&amp;gt;, cherchons &amp;lt;math&amp;gt;f(n)\in\mathcal{O}(g(n))&amp;lt;/math&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
Nous pouvons ignorer les expressions linéaires ainsi que les constantes multiplicatrices, nous obtenons donc &amp;lt;math&amp;gt;\mathcal{O}(g(n)) = \mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT_image.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre ne provient pas du fruit de nos recherches personnelles mais &amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
&lt;br /&gt;
( A FAIRE )&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : https://www.youtube.com/watch?v=PZ2gdWdKHAA&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&amp;lt;ul&amp;gt; &lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://fr.wikipedia.org/wiki/Interpolation_polynomiale &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17022</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17022"/>
		<updated>2026-05-11T15:17:43Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* Exemple */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calculs a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrées qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs étroitement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calculs nécessaires en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cette algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; m &amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt;f(x) = x^2&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karastuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt;b&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 &amp;lt;math&amp;gt;Résultat = w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice paires et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(x)=P_{\text{pair}}(x^2) + x \cdot P_{\text{impair}}(x^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 1 : Ecrire les deux facteurs sous la forme de polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 2 : Effectuer la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; des deux polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 3 : Faire le produit terme à terme des &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; obtenues&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 4 : Faire l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; du produit&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 5 : Gérer les retenues et écrire le nombre qui en découle&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or, parmi toutes ces étapes, certaines sont négligeable comme l&#039;étape 1 et 5.&lt;br /&gt;
&lt;br /&gt;
Pour connaitre la complexité de l&#039;algorithme, additionnons les complexités des étapes restantes :&lt;br /&gt;
&lt;br /&gt;
Une &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt; car elle fait &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; évaluation sur &amp;lt;math&amp;gt; log(n)&amp;lt;/math&amp;gt; récursions. Dans le cadre de notre algorithme, nous devons faire la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; de nos deux facteurs. Soit &amp;lt;math&amp;gt;2\times \mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Pour le produit terme à terme, nous faisons naturellement &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&lt;br /&gt;
&lt;br /&gt;
Pour finir, l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; a la même complexité que la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt;. Dans le cadre de notre algorithme nous l&#039;emploierons qu&#039;une seule fois, ce qui fait une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Après addition de toutes ces complexités, nous obtenons la complexité réelle de &amp;lt;math&amp;gt;3\mathcal{O}(n\times logn) + \mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
D&#039;après le Master Theorem, nous avons donc &amp;lt;math&amp;gt; f(n) = 3(n\times logn)+n &amp;lt;/math&amp;gt;, cherchons &amp;lt;math&amp;gt;f(n)\in\mathcal{O}(g(n))&amp;lt;/math&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
Nous pouvons ignorer les expressions linéaires ainsi que les constantes multiplicatrices, nous obtenons donc &amp;lt;math&amp;gt;\mathcal{O}(g(n)) = \mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT_image.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre ne provient pas du fruit de nos recherches personnelles mais &amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
&lt;br /&gt;
( A FAIRE )&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : https://www.youtube.com/watch?v=PZ2gdWdKHAA&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&amp;lt;ul&amp;gt; &lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://fr.wikipedia.org/wiki/Interpolation_polynomiale &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17021</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17021"/>
		<updated>2026-05-11T15:16:58Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* Définition */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calculs a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrées qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs étroitement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calculs nécessaires en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces deux nombres.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cette algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; m &amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt;f(x) = x^2&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karastuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt;b&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 &amp;lt;math&amp;gt;Résultat = w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice paires et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(x)=P_{\text{pair}}(x^2) + x \cdot P_{\text{impair}}(x^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 1 : Ecrire les deux facteurs sous la forme de polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 2 : Effectuer la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; des deux polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 3 : Faire le produit terme à terme des &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; obtenues&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 4 : Faire l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; du produit&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 5 : Gérer les retenues et écrire le nombre qui en découle&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or, parmi toutes ces étapes, certaines sont négligeable comme l&#039;étape 1 et 5.&lt;br /&gt;
&lt;br /&gt;
Pour connaitre la complexité de l&#039;algorithme, additionnons les complexités des étapes restantes :&lt;br /&gt;
&lt;br /&gt;
Une &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt; car elle fait &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; évaluation sur &amp;lt;math&amp;gt; log(n)&amp;lt;/math&amp;gt; récursions. Dans le cadre de notre algorithme, nous devons faire la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; de nos deux facteurs. Soit &amp;lt;math&amp;gt;2\times \mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Pour le produit terme à terme, nous faisons naturellement &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&lt;br /&gt;
&lt;br /&gt;
Pour finir, l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; a la même complexité que la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt;. Dans le cadre de notre algorithme nous l&#039;emploierons qu&#039;une seule fois, ce qui fait une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Après addition de toutes ces complexités, nous obtenons la complexité réelle de &amp;lt;math&amp;gt;3\mathcal{O}(n\times logn) + \mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
D&#039;après le Master Theorem, nous avons donc &amp;lt;math&amp;gt; f(n) = 3(n\times logn)+n &amp;lt;/math&amp;gt;, cherchons &amp;lt;math&amp;gt;f(n)\in\mathcal{O}(g(n))&amp;lt;/math&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
Nous pouvons ignorer les expressions linéaires ainsi que les constantes multiplicatrices, nous obtenons donc &amp;lt;math&amp;gt;\mathcal{O}(g(n)) = \mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT_image.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre ne provient pas du fruit de nos recherches personnelles mais &amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
&lt;br /&gt;
( A FAIRE )&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : https://www.youtube.com/watch?v=PZ2gdWdKHAA&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&amp;lt;ul&amp;gt; &lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://fr.wikipedia.org/wiki/Interpolation_polynomiale &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17020</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17020"/>
		<updated>2026-05-11T15:16:07Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* Définition */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calculs a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrées qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs étroitement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calcul nécessaire en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces deux nombres.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cette algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; m &amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt;f(x) = x^2&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karastuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt;b&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 &amp;lt;math&amp;gt;Résultat = w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice paires et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(x)=P_{\text{pair}}(x^2) + x \cdot P_{\text{impair}}(x^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 1 : Ecrire les deux facteurs sous la forme de polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 2 : Effectuer la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; des deux polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 3 : Faire le produit terme à terme des &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; obtenues&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 4 : Faire l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; du produit&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 5 : Gérer les retenues et écrire le nombre qui en découle&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or, parmi toutes ces étapes, certaines sont négligeable comme l&#039;étape 1 et 5.&lt;br /&gt;
&lt;br /&gt;
Pour connaitre la complexité de l&#039;algorithme, additionnons les complexités des étapes restantes :&lt;br /&gt;
&lt;br /&gt;
Une &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt; car elle fait &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; évaluation sur &amp;lt;math&amp;gt; log(n)&amp;lt;/math&amp;gt; récursions. Dans le cadre de notre algorithme, nous devons faire la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; de nos deux facteurs. Soit &amp;lt;math&amp;gt;2\times \mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Pour le produit terme à terme, nous faisons naturellement &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&lt;br /&gt;
&lt;br /&gt;
Pour finir, l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; a la même complexité que la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt;. Dans le cadre de notre algorithme nous l&#039;emploierons qu&#039;une seule fois, ce qui fait une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Après addition de toutes ces complexités, nous obtenons la complexité réelle de &amp;lt;math&amp;gt;3\mathcal{O}(n\times logn) + \mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
D&#039;après le Master Theorem, nous avons donc &amp;lt;math&amp;gt; f(n) = 3(n\times logn)+n &amp;lt;/math&amp;gt;, cherchons &amp;lt;math&amp;gt;f(n)\in\mathcal{O}(g(n))&amp;lt;/math&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
Nous pouvons ignorer les expressions linéaires ainsi que les constantes multiplicatrices, nous obtenons donc &amp;lt;math&amp;gt;\mathcal{O}(g(n)) = \mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT_image.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre ne provient pas du fruit de nos recherches personnelles mais &amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
&lt;br /&gt;
( A FAIRE )&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : https://www.youtube.com/watch?v=PZ2gdWdKHAA&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&amp;lt;ul&amp;gt; &lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://fr.wikipedia.org/wiki/Interpolation_polynomiale &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17019</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17019"/>
		<updated>2026-05-11T15:15:36Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* Définition */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calculs a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrées qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs fortement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calcul nécessaire en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces deux nombres.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cette algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; m &amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt;f(x) = x^2&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karastuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt;b&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 &amp;lt;math&amp;gt;Résultat = w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice paires et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(x)=P_{\text{pair}}(x^2) + x \cdot P_{\text{impair}}(x^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 1 : Ecrire les deux facteurs sous la forme de polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 2 : Effectuer la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; des deux polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 3 : Faire le produit terme à terme des &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; obtenues&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 4 : Faire l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; du produit&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 5 : Gérer les retenues et écrire le nombre qui en découle&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or, parmi toutes ces étapes, certaines sont négligeable comme l&#039;étape 1 et 5.&lt;br /&gt;
&lt;br /&gt;
Pour connaitre la complexité de l&#039;algorithme, additionnons les complexités des étapes restantes :&lt;br /&gt;
&lt;br /&gt;
Une &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt; car elle fait &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; évaluation sur &amp;lt;math&amp;gt; log(n)&amp;lt;/math&amp;gt; récursions. Dans le cadre de notre algorithme, nous devons faire la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; de nos deux facteurs. Soit &amp;lt;math&amp;gt;2\times \mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Pour le produit terme à terme, nous faisons naturellement &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&lt;br /&gt;
&lt;br /&gt;
Pour finir, l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; a la même complexité que la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt;. Dans le cadre de notre algorithme nous l&#039;emploierons qu&#039;une seule fois, ce qui fait une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Après addition de toutes ces complexités, nous obtenons la complexité réelle de &amp;lt;math&amp;gt;3\mathcal{O}(n\times logn) + \mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
D&#039;après le Master Theorem, nous avons donc &amp;lt;math&amp;gt; f(n) = 3(n\times logn)+n &amp;lt;/math&amp;gt;, cherchons &amp;lt;math&amp;gt;f(n)\in\mathcal{O}(g(n))&amp;lt;/math&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
Nous pouvons ignorer les expressions linéaires ainsi que les constantes multiplicatrices, nous obtenons donc &amp;lt;math&amp;gt;\mathcal{O}(g(n)) = \mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT_image.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre ne provient pas du fruit de nos recherches personnelles mais &amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
&lt;br /&gt;
( A FAIRE )&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : https://www.youtube.com/watch?v=PZ2gdWdKHAA&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&amp;lt;ul&amp;gt; &lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://fr.wikipedia.org/wiki/Interpolation_polynomiale &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17018</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17018"/>
		<updated>2026-05-11T15:14:48Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* Introduction */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calculs a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrée qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs fortement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calcul nécessaire en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces deux nombres.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cette algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; m &amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt;f(x) = x^2&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karastuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt;b&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 &amp;lt;math&amp;gt;Résultat = w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice paires et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(x)=P_{\text{pair}}(x^2) + x \cdot P_{\text{impair}}(x^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 1 : Ecrire les deux facteurs sous la forme de polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 2 : Effectuer la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; des deux polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 3 : Faire le produit terme à terme des &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; obtenues&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 4 : Faire l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; du produit&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 5 : Gérer les retenues et écrire le nombre qui en découle&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or, parmi toutes ces étapes, certaines sont négligeable comme l&#039;étape 1 et 5.&lt;br /&gt;
&lt;br /&gt;
Pour connaitre la complexité de l&#039;algorithme, additionnons les complexités des étapes restantes :&lt;br /&gt;
&lt;br /&gt;
Une &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt; car elle fait &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; évaluation sur &amp;lt;math&amp;gt; log(n)&amp;lt;/math&amp;gt; récursions. Dans le cadre de notre algorithme, nous devons faire la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; de nos deux facteurs. Soit &amp;lt;math&amp;gt;2\times \mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Pour le produit terme à terme, nous faisons naturellement &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&lt;br /&gt;
&lt;br /&gt;
Pour finir, l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; a la même complexité que la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt;. Dans le cadre de notre algorithme nous l&#039;emploierons qu&#039;une seule fois, ce qui fait une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Après addition de toutes ces complexités, nous obtenons la complexité réelle de &amp;lt;math&amp;gt;3\mathcal{O}(n\times logn) + \mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
D&#039;après le Master Theorem, nous avons donc &amp;lt;math&amp;gt; f(n) = 3(n\times logn)+n &amp;lt;/math&amp;gt;, cherchons &amp;lt;math&amp;gt;f(n)\in\mathcal{O}(g(n))&amp;lt;/math&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
Nous pouvons ignorer les expressions linéaires ainsi que les constantes multiplicatrices, nous obtenons donc &amp;lt;math&amp;gt;\mathcal{O}(g(n)) = \mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT_image.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre ne provient pas du fruit de nos recherches personnelles mais &amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
&lt;br /&gt;
( A FAIRE )&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : https://www.youtube.com/watch?v=PZ2gdWdKHAA&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&amp;lt;ul&amp;gt; &lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://fr.wikipedia.org/wiki/Interpolation_polynomiale &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17015</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17015"/>
		<updated>2026-05-11T15:12:49Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* A) Fonctionnement */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calcul a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrée qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs fortement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calcul nécessaire en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces deux nombres.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cette algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; m &amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt;f(x) = x^2&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karastuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt;b&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 &amp;lt;math&amp;gt;Résultat = w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice paires et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(x)=P_{\text{pair}}(x^2) + x \cdot P_{\text{impair}}(x^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 1 : Ecrire les deux facteurs sous la forme de polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 2 : Effectuer la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; des deux polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 3 : Faire le produit terme à terme des &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; obtenues&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 4 : Faire l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; du produit&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 5 : Gérer les retenues et écrire le nombre qui en découle&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or, parmi toutes ces étapes, certaines sont négligeable comme l&#039;étape 1 et 5.&lt;br /&gt;
&lt;br /&gt;
Pour connaitre la complexité de l&#039;algorithme, additionnons les complexités des étapes restantes :&lt;br /&gt;
&lt;br /&gt;
Une &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt; car elle fait &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; évaluation sur &amp;lt;math&amp;gt; log(n)&amp;lt;/math&amp;gt; récursions. Dans le cadre de notre algorithme, nous devons faire la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; de nos deux facteurs. Soit &amp;lt;math&amp;gt;2\times \mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Pour le produit terme à terme, nous faisons naturellement &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&lt;br /&gt;
&lt;br /&gt;
Pour finir, l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; a la même complexité que la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt;. Dans le cadre de notre algorithme nous l&#039;emploierons qu&#039;une seule fois, ce qui fait une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Après addition de toutes ces complexités, nous obtenons la complexité réelle de &amp;lt;math&amp;gt;3\mathcal{O}(n\times logn) + \mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
D&#039;après le Master Theorem, nous avons donc &amp;lt;math&amp;gt; f(n) = 3(n\times logn)+n &amp;lt;/math&amp;gt;, cherchons &amp;lt;math&amp;gt;f(n)\in\mathcal{O}(g(n))&amp;lt;/math&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
Nous pouvons ignorer les expressions linéaires ainsi que les constantes multiplicatrices, nous obtenons donc &amp;lt;math&amp;gt;\mathcal{O}(g(n)) = \mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT_image.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre ne provient pas du fruit de nos recherches personnelles mais &amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
&lt;br /&gt;
( A FAIRE )&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : https://www.youtube.com/watch?v=PZ2gdWdKHAA&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&amp;lt;ul&amp;gt; &lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://fr.wikipedia.org/wiki/Interpolation_polynomiale &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17014</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17014"/>
		<updated>2026-05-11T15:10:07Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* B) Graphique */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calcul a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrée qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs fortement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calcul nécessaire en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces deux nombres.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cette algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; m &amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt;f(x) = x^2&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karastuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt;b&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 &amp;lt;math&amp;gt;Résultat = w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice paires et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x)=P_{pair}​x^2 + x \times P_{impair​}x^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 1 : Ecrire les deux facteurs sous la forme de polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 2 : Effectuer la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; des deux polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 3 : Faire le produit terme à terme des &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; obtenues&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 4 : Faire l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; du produit&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 5 : Gérer les retenues et écrire le nombre qui en découle&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or, parmi toutes ces étapes, certaines sont négligeable comme l&#039;étape 1 et 5.&lt;br /&gt;
&lt;br /&gt;
Pour connaitre la complexité de l&#039;algorithme, additionnons les complexités des étapes restantes :&lt;br /&gt;
&lt;br /&gt;
Une &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt; car elle fait &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; évaluation sur &amp;lt;math&amp;gt; log(n)&amp;lt;/math&amp;gt; récursions. Dans le cadre de notre algorithme, nous devons faire la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; de nos deux facteurs. Soit &amp;lt;math&amp;gt;2\times \mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Pour le produit terme à terme, nous faisons naturellement &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&lt;br /&gt;
&lt;br /&gt;
Pour finir, l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; a la même complexité que la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt;. Dans le cadre de notre algorithme nous l&#039;emploierons qu&#039;une seule fois, ce qui fait une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Après addition de toutes ces complexités, nous obtenons la complexité réelle de &amp;lt;math&amp;gt;3\mathcal{O}(n\times logn) + \mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
D&#039;après le Master Theorem, nous avons donc &amp;lt;math&amp;gt; f(n) = 3(n\times logn)+n &amp;lt;/math&amp;gt;, cherchons &amp;lt;math&amp;gt;f(n)\in\mathcal{O}(g(n))&amp;lt;/math&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
Nous pouvons ignorer les expressions linéaires ainsi que les constantes multiplicatrices, nous obtenons donc &amp;lt;math&amp;gt;\mathcal{O}(g(n)) = \mathcal{O}(n \times log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT_image.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre ne provient pas du fruit de nos recherches personnelles mais &amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
&lt;br /&gt;
( A FAIRE )&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : https://www.youtube.com/watch?v=PZ2gdWdKHAA&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&amp;lt;ul&amp;gt; &lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://fr.wikipedia.org/wiki/Interpolation_polynomiale &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17009</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=17009"/>
		<updated>2026-05-11T15:05:04Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* 5) La transformée de Fourier rapide */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calcul a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrée qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs fortement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calcul nécessaire en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces deux nombres.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cette algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; m &amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt;f(x) = x^2&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karastuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt;b&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 &amp;lt;math&amp;gt;Résultat = w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice paires et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x)=P_{pair}​x^2 + x \times P_{impair​}x^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 1 : Ecrire les deux facteurs sous la forme de polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 2 : Effectuer la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; des deux polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 3 : Faire le produit terme à terme des &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; obtenues&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 4 : Faire l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; du produit&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 5 : Gérer les retenues et écrire le nombre qui en découle&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or, parmi toutes ces étapes, certaines sont négligeable comme l&#039;étape 1 et 5.&lt;br /&gt;
&lt;br /&gt;
Pour connaitre la complexité de l&#039;algorithme, additionnons les complexités des étapes restantes :&lt;br /&gt;
&lt;br /&gt;
Une &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt; car elle fait &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; évaluation sur &amp;lt;math&amp;gt; log(n)&amp;lt;/math&amp;gt; récursions. Dans le cadre de notre algorithme, nous devons faire la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; de nos deux facteurs. Soit &amp;lt;math&amp;gt;2\times \mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Pour le produit terme à terme, nous faisons naturellement &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&lt;br /&gt;
&lt;br /&gt;
Pour finir, l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; a la même complexité que la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt;. Dans le cadre de notre algorithme nous l&#039;emploierons qu&#039;une seule fois, ce qui fait une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n\times logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Après addition de toutes ces complexités, nous obtenons la complexité réelle de &amp;lt;math&amp;gt;3\mathcal{O}(n\times logn) + \mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
D&#039;après le Master Theorem, nous avons donc &amp;lt;math&amp;gt; f(x) = 3(x\timeslogx)+x &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT_image.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
&lt;br /&gt;
( A FAIRE )&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : https://www.youtube.com/watch?v=PZ2gdWdKHAA&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&amp;lt;ul&amp;gt; &lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://fr.wikipedia.org/wiki/Interpolation_polynomiale &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=16985</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=16985"/>
		<updated>2026-05-11T12:18:18Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* B) Graphique */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calcul a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrée qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs fortement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calcul nécessaire en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces deux nombres.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cette algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; m &amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt;f(x) = x^2&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karastuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt;b&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 &amp;lt;math&amp;gt;Résultat = w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice paires et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x)=P_{pair}​x^2 + x \times P_{impair​}x^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 1 : Ecrire les deux facteurs sous la forme de polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 2 : Effectuer la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; des deux polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 3 : Faire le produit terme à terme des &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; obtenues&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 4 : Faire l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; du produit&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Etape 5 : Gérer les retenues et écrire le nombre qui en découle&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or, parmi toutes ces étapes, certaines sont négligeable comme l&#039;étape 1 et 5.&lt;br /&gt;
&lt;br /&gt;
Pour connaitre la complexité de l&#039;algorithme, additionnons les complexités des étapes restantes :&lt;br /&gt;
&lt;br /&gt;
Une &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; a une complexité de REMPLIRRRRRR car elle fait &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; évaluation sur REMPLIRRRRRR couches. Dans le cadre de notre algorithme, nous devons faire la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; de nos deux facteurs. Soit 2(REMPLIRRRRRRR).&lt;br /&gt;
&lt;br /&gt;
Pour le produit terme à terme, nous faisons naturellement &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&lt;br /&gt;
&lt;br /&gt;
Pour finir, l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; a la même complexité que la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt;. Dans le cadre de notre algorithme nous l&#039;emploierons qu&#039;une seule fois, ce qui fait une complexité de REMPLIRRRRRR.&lt;br /&gt;
&lt;br /&gt;
Après addition de toutes ces complexités, nous obtenons la complexité réelle de REMPLIRRRRRRR.&lt;br /&gt;
&lt;br /&gt;
D&#039;après le Master Theorem, nous avons &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT_image.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
&lt;br /&gt;
( A FAIRE )&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : https://www.youtube.com/watch?v=PZ2gdWdKHAA&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&amp;lt;ul&amp;gt; &lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://fr.wikipedia.org/wiki/Interpolation_polynomiale &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=16981</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=16981"/>
		<updated>2026-05-11T12:05:54Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* B) Graphique */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calcul a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrée qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs fortement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calcul nécessaire en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces deux nombres.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cette algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; m &amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt;f(x) = x^2&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karastuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt;b&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 &amp;lt;math&amp;gt;Résultat = w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice paires et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x)=P_{pair}​x^2 + x \times P_{impair​}x^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Ecrire les deux facteurs sous la forme de polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Effectuer la &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; des deux polynômes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Faire le produit terme à terme des &amp;lt;math&amp;gt;FFT&amp;lt;/math&amp;gt; obtenues&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Faire l&#039;&amp;lt;math&amp;gt;IFFT&amp;lt;/math&amp;gt; du produit&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Gérer les retenues et écrire le nombre qui en découle&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT_image.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
&lt;br /&gt;
( A FAIRE )&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : https://www.youtube.com/watch?v=PZ2gdWdKHAA&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&amp;lt;ul&amp;gt; &lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://fr.wikipedia.org/wiki/Interpolation_polynomiale &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=16980</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=16980"/>
		<updated>2026-05-11T12:02:58Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* B) Graphique */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calcul a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrée qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs fortement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calcul nécessaire en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces deux nombres.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cette algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; m &amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt;f(x) = x^2&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karastuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt;b&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 &amp;lt;math&amp;gt;Résultat = w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice paires et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x)=P_{pair}​x^2 + x \times P_{impair​}x^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;passer deux nombre en polynomes&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;faire la fft&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;faire le produit terme a terme&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;faire l&#039;ifft&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;retenue&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT_image.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
&lt;br /&gt;
( A FAIRE )&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : https://www.youtube.com/watch?v=PZ2gdWdKHAA&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&amp;lt;ul&amp;gt; &lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://fr.wikipedia.org/wiki/Interpolation_polynomiale &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=16979</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=16979"/>
		<updated>2026-05-11T12:01:58Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* Sources */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calcul a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrée qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs fortement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calcul nécessaire en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces deux nombres.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cette algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; m &amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt;f(x) = x^2&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karastuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt;b&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 &amp;lt;math&amp;gt;Résultat = w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice paires et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x)=P_{pair}​x^2 + x \times P_{impair​}x^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&lt;br /&gt;
passer deux nombre en polynomes&lt;br /&gt;
faire la fft&lt;br /&gt;
faire le produit terme a terme&lt;br /&gt;
faire l&#039;ifft&lt;br /&gt;
retenue&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT_image.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
&lt;br /&gt;
( A FAIRE )&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : https://www.youtube.com/watch?v=PZ2gdWdKHAA&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&amp;lt;ul&amp;gt; &lt;br /&gt;
&amp;lt;li&amp;gt; https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; https://fr.wikipedia.org/wiki/Interpolation_polynomiale &amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=16978</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=16978"/>
		<updated>2026-05-11T11:59:23Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* B) Graphique */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calcul a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrée qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs fortement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calcul nécessaire en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces deux nombres.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cette algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; m &amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt;f(x) = x^2&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karastuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt;b&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 &amp;lt;math&amp;gt;Résultat = w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice paires et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x)=P_{pair}​x^2 + x \times P_{impair​}x^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Intéressons nous maintenant à la complexité de l&#039;algorithme utilisant la transformée de Fourier rapide. On sait que l&#039;algorithme passe par 5 étapes importantes :&lt;br /&gt;
&lt;br /&gt;
passer deux nombre en polynomes&lt;br /&gt;
faire la fft&lt;br /&gt;
faire le produit terme a terme&lt;br /&gt;
faire l&#039;ifft&lt;br /&gt;
retenue&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT_image.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
&lt;br /&gt;
( A FAIRE )&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : https://www.youtube.com/watch?v=PZ2gdWdKHAA&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s&lt;br /&gt;
&lt;br /&gt;
https://fr.wikipedia.org/wiki/Interpolation_polynomiale&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=16976</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=16976"/>
		<updated>2026-05-11T11:56:35Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* B) Graphique */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calcul a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrée qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs fortement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calcul nécessaire en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces deux nombres.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cette algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; m &amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt;f(x) = x^2&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karastuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt;b&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 &amp;lt;math&amp;gt;Résultat = w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice paires et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x)=P_{pair}​x^2 + x \times P_{impair​}x^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:FFT_image.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
&lt;br /&gt;
( A FAIRE )&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : https://www.youtube.com/watch?v=PZ2gdWdKHAA&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s&lt;br /&gt;
&lt;br /&gt;
https://fr.wikipedia.org/wiki/Interpolation_polynomiale&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=16975</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=16975"/>
		<updated>2026-05-11T11:56:02Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* B) Graphique */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calcul a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrée qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs fortement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calcul nécessaire en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces deux nombres.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cette algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; m &amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt;f(x) = x^2&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karastuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt;b&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 &amp;lt;math&amp;gt;Résultat = w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice paires et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x)=P_{pair}​x^2 + x \times P_{impair​}x^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
( A FAIRE )&lt;br /&gt;
(on aura pas de courbe par contre)&lt;br /&gt;
FFT_image.webp&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
&lt;br /&gt;
( A FAIRE )&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : https://www.youtube.com/watch?v=PZ2gdWdKHAA&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s&lt;br /&gt;
&lt;br /&gt;
https://fr.wikipedia.org/wiki/Interpolation_polynomiale&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Fichier:FFT_image.webp&amp;diff=16974</id>
		<title>Fichier:FFT image.webp</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Fichier:FFT_image.webp&amp;diff=16974"/>
		<updated>2026-05-11T11:55:46Z</updated>

		<summary type="html">&lt;p&gt;Mangala : &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=16973</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=16973"/>
		<updated>2026-05-11T11:51:17Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* A) Fonctionnement */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calcul a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrée qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs fortement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calcul nécessaire en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces deux nombres.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cette algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; m &amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt;f(x) = x^2&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karastuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt;b&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 &amp;lt;math&amp;gt;Résultat = w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice paires et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x)=P_{pair}​x^2 + x \times P_{impair​}x^2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
( A FAIRE )&lt;br /&gt;
(on aura pas de courbe par contre)&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
&lt;br /&gt;
( A FAIRE )&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : https://www.youtube.com/watch?v=PZ2gdWdKHAA&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s&lt;br /&gt;
&lt;br /&gt;
https://fr.wikipedia.org/wiki/Interpolation_polynomiale&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=16972</id>
		<title>Algorithmes de multiplications d&#039;entiers et de matrices</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Algorithmes_de_multiplications_d%27entiers_et_de_matrices&amp;diff=16972"/>
		<updated>2026-05-11T11:45:44Z</updated>

		<summary type="html">&lt;p&gt;Mangala : /* A) Fonctionnement */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Depuis l&#039;apparition des ordinateurs modernes, une quantité faramineuse de calcul a dû être effectuée. Les progressions technologiques nous poussent à envisager la puissance de calcul comme une ressource qu&#039;il faut optimiser dans les moindres détails. C&#039;est ainsi que l&#039;optimisation des algorithmes de calcul est devenue cruciale, surtout lorsqu&#039;il nous faut effectuer des opérations sur de très grands entiers par exemple.&lt;br /&gt;
&lt;br /&gt;
= Objectif =&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif majeur de ce projet est de comprendre de manière plus approfondie ce qu&#039;est la &#039;&#039;&#039;complexité algorithmique&#039;&#039;&#039;. &lt;br /&gt;
Pour atteindre cet objectif, nous implémenterons plusieurs algorithmes de multiplication qui auront pour but de calculer le produit de grands entiers ou de matrices.&lt;br /&gt;
&lt;br /&gt;
= Point important : complexité =&lt;br /&gt;
&lt;br /&gt;
=== Définition ===&lt;br /&gt;
La complexité d&#039;un algorithme est la quantité des ressources qu&#039;il utilise en fonction de la taille des entrée qu&#039;on lui demande de traiter. On peut par exemple se concentrer sur le temps qu&#039;un algorithme prend pour renvoyer un résultat ou sur la mémoire dont il a besoin. Dans le cadre de notre projet, nous nous attarderons plus particulièrement sur la quantité de calcul effectuée en fonction de la taille des entrées, ce qui est par ailleurs fortement liée à la notion de temps. De manière générale, plus un algorithme doit faire de calcul, plus il est lent. (Attention, toutes les opérations arithmétiques de base ne se valent pas)&lt;br /&gt;
&lt;br /&gt;
De manière plus familière, on dira que la complexité d&#039;un algorithme pourrait s&#039;apparenter à son comportement lorsqu&#039;il doit traiter des données plus ou moins grandes. &lt;br /&gt;
Chaque algorithme a une complexité, et on l&#039;exprime par une fonction qui adopte un comportement similaire au nombre de calcul nécessaire en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
=== Notation ===&lt;br /&gt;
La complexité réelle d&#039;un algorithme peut être décrite par une fonction &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; représentant le nombre d&#039;opérations effectuées en fonction de la taille &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; des données d&#039;entrée.&lt;br /&gt;
&lt;br /&gt;
Cependant, afin de simplifier l&#039;étude de cette croissance, on utilise généralement une borne asymptotique notée &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;, où &amp;lt;math&amp;gt;g(n)&amp;lt;/math&amp;gt; est une fonction ayant le même ordre de grandeur que &amp;lt;math&amp;gt;f(n)&amp;lt;/math&amp;gt; lorsque &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; devient grand.&lt;br /&gt;
&lt;br /&gt;
On dit alors que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
 f(n)\in\mathcal{O}(g(n))&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
si et seulement s&#039;il existe une constante &amp;lt;math&amp;gt;C&amp;gt;0&amp;lt;/math&amp;gt; et un entier &amp;lt;math&amp;gt;n_0&amp;lt;/math&amp;gt; tels que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;&lt;br /&gt;
 \forall n\ge n_0,\ f(n)\le Cg(n)&lt;br /&gt;
 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette notation &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt; permettra de comparer beaucoup plus facilement les algorithmes entre eux.&lt;br /&gt;
&lt;br /&gt;
=== Master Theorem ===&lt;br /&gt;
&lt;br /&gt;
Afin de déterminer la complexité de certains algorithmes récursifs, nous utiliserons le &amp;quot;Master Theorem&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Ce théorème permet d&#039;obtenir directement l&#039;ordre de grandeur de nombreuses relations de récurrence apparaissant dans l&#039;analyse d&#039;algorithmes de type « diviser pour régner », comme l&#039;algorithme de Karatsuba ou celui de Strassen.&lt;br /&gt;
&lt;br /&gt;
La démonstration complète de ce théorème dépasse le cadre de notre projet et nécessite des connaissances mathématiques plus avancées. Nous l&#039;utiliserons donc principalement comme un outil nous permettant de déterminer efficacement la complexité asymptotique des algorithmes étudiés.&lt;br /&gt;
&lt;br /&gt;
=== Graphiques ===&lt;br /&gt;
&lt;br /&gt;
Les complexités étant des fonctions, nous pouvons les représenter par un graphique qui affichera le temps d&#039;exécution (ou nombre d&#039;opérations) en fonction de la taille des entrées.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:30px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:complexite.webp|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Graphiques des complexités&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ce graphique ci-contre compare les complexités en fonction de la taille des d&#039;entrées.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On comprend ainsi que certaines complexités ne sont pas raisonnable dans la cadre de la multiplication de grands entiers.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est pourquoi l&#039;étude de ces algorithmes s&#039;avère nécessaire.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
L&#039;algorithme naïf d&#039;addition que nous avons tous appris à l&#039;école a une complexité de &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Soit n la taille des nombres en entrée, l&#039;algorithme doit faire environ n addition pour arriver au résultat demandé.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Addition.gif|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Addition naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Comme le montre l&#039;exemple ci-contre, 786 et 467 font 3 chiffres de long donc n sera de 3.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Il nous faudra 3 opérations pour additionner ces deux nombres.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;C&#039;est ainsi qu&#039;avec une taille d&#039;entrée n, l&#039;algorithme de l&#039;addition naïve va devoir faire n opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Cette algorithme a donc une complexité &amp;lt;math&amp;gt;f(n) = n&amp;lt;/math&amp;gt; qui n&#039;a pas besoin d&#039;être approximée.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi sa complexité finale sera &amp;lt;math&amp;gt;\mathcal{O}(n)&amp;lt;/math&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Problématique =&lt;br /&gt;
&lt;br /&gt;
Le calcul du produit de deux entiers de manière naïve a une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^2)&amp;lt;/math&amp;gt;, et celui de deux matrices une complexité de &amp;lt;math&amp;gt; \mathcal{O}(n^3)&amp;lt;/math&amp;gt;. Afin de mieux comprendre ce qu&#039;est la complexité algorithmique, nous devrons implémenter des algorithmes ayant le même objectif, mais étant plus efficaces. Ces algorithmes s&#039;avèreront plus complexes mais devrait aboutir à un calcul plus rapide du produit demandé.&lt;br /&gt;
&lt;br /&gt;
Nous séparerons notre wiki en deux grandes parties, la première concernera nos recherche sur [[#I - Produit d&#039;entiers|la multiplication d&#039;entiers]], la deuxième sur [[#II - Produit de matrices|la multiplication de matrices]].&lt;br /&gt;
&lt;br /&gt;
= I - Produit d&#039;entiers =&lt;br /&gt;
&lt;br /&gt;
Notre départ commence avec l&#039;algorithme de multiplication d&#039;entier naïf. &lt;br /&gt;
En effet il s&#039;agit de l&#039;algorithme de multiplication le plus connu, et nous l&#039;utiliserons comme référence pour le comparer aux futurs algorithmes plus efficaces.&lt;br /&gt;
Intéressons nous à sa complexité.&lt;br /&gt;
&lt;br /&gt;
== 1) Nos implémentations des grands entiers ==&lt;br /&gt;
&lt;br /&gt;
Qui dit produit de grands entiers dit implémentation de grands entiers.&lt;br /&gt;
&lt;br /&gt;
Dans le cadre de nos recherches, nous allons devoir implémenter des algorithmes qui vont manipuler des grands entiers. &lt;br /&gt;
&lt;br /&gt;
Nous avons donc choisi de créer deux représentations différentes de ceux-ci (car nous travaillons sur différents langages de programmation), nous permettant à la fois une implémentation simplifié, mais aussi de nous rapprocher d&#039;une implémentation bas niveau.&lt;br /&gt;
&lt;br /&gt;
=== A) En python ===&lt;br /&gt;
En python, l&#039;utilisation des &amp;quot;bytes&amp;quot; m&#039;a grandement servi à comprendre comment les entiers étaient manipulés à bas niveau. En effet, un des objectifs secondaires de nos recherches était de manipuler des entiers directement avec leur formes stockées sur l&#039;ordinateur, et non pas avec des int python.&lt;br /&gt;
En effet l&#039;utilisation des int python nous aurait permit de passer les étapes de retenues, de signes... D&#039;autant plus que les bytes permettent de stocker un nombre en base 256, ce qui permet de visualiser plus facilement les grands entiers.&lt;br /&gt;
&lt;br /&gt;
La forme finale de la représentation des grands entiers en python sera donc la suivante :&lt;br /&gt;
&lt;br /&gt;
 [ signe , bytes , bytes , (...) , bytes ]&lt;br /&gt;
&lt;br /&gt;
Le signe est représenté par un entier, 1 si le nombre est positif, 0 sinon.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes.&lt;br /&gt;
&lt;br /&gt;
=== B) En C++ ===&lt;br /&gt;
&lt;br /&gt;
En C++, pour avoir une représentation de grand entiers j&#039;ai du définir ma propre structure pour m&#039;affranchir de la limite de taille imposé par les entiers standards: (int32 ou int64). Pour cela, j&#039;ai une structure qui possède l&#039;équivalent d&#039;un array de byte (ce qui nous permet d&#039;être en base 256 au lieu de la base 10 habituelle), et pour traiter le signe j&#039;utilise un booléen, ainsi en mémoire j&#039;ai une structure de n*Octets + 1 octet en taille.&lt;br /&gt;
&lt;br /&gt;
 [ bytes , bytes , (...) , bytes ] [ signe ]&lt;br /&gt;
&lt;br /&gt;
Le booléen prend 1 octet de place mais ne touche pas aux octets qui encode le grand entier.&lt;br /&gt;
Cette configuration rendra plus simple la gestion des signes dans le cadre des multiplication.&lt;br /&gt;
&lt;br /&gt;
== 2) La multiplication naïve ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
Dans le cas de la multiplication d&#039;entiers, nous allons prendre deux nombres qui n&#039;ont pas nécessairement la même longueur. Soit les nombre a et b, de longueur respective &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; m &amp;lt;/math&amp;gt;.&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplication.png|220px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Prenons deux nombres de longueur 3 et 2, et cherchons combien d&#039;opérations l&#039;algorithme de multiplication naïve va devoir faire pour nous renvoyer leur produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un premier temps, on remarque que l&#039;on va devoir multiplier chaque chiffre du nombre du haut par le chiffre des unités du bas, qui est 6. Cela va représenter &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations. (6x5, 6x1 et 6x4)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Dans un deuxième temps nous constatons que nous allons devoir faire la même chose avec le chiffre des dizaines du nombre du bas. Cela nous ajoute de nouveau &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On arrive assez facilement à la conclusion que pour faire notre produit, il va nous falloir faire &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; fois &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; opérations.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, la complexité de la multiplication naïve est &amp;lt;math&amp;gt;\mathcal{O}(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Il est en de même avec la multiplication naïve récursive.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
Montrons maintenant sa courbe sur un graphique :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Multiplicationnaive.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Les points noirs sont des repères pour que les autres courbes aient une forme cohérente.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ils sont tous sur l&#039;axe des abscisses.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque que la courbe a un visuel très similaire à la fonction &amp;lt;math&amp;gt;f(x) = x^2&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Naif_recursif.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Multiplication naïve récursive&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;La courbe rouge représente la complexité de la multiplication naïve récursive.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Elle sera utilisé pour comparer les complexités entre algorithmes car, comme tous récursifs, elle a été codé avec les mêmes biais d&#039;implémentation qu&#039;eux.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Théoriquement les deux courbes ci-contre ont la même complexité, mais encore une fois, notre implémentation n&#039;est pas parfaitement optimale et donc ne respecte pas de manière minutieuse le Master Theorem.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3) La multiplication karastuba ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de karatsuba est un algorithme récursif de type diviser pour régner.&lt;br /&gt;
&lt;br /&gt;
L&#039;idée générale de l&#039;algorithme est de passer de 4 multiplication à 3. En effet ces opérations étant moins couteuses, il est préférable d&#039;en ajouter si cela permet d&#039;enlever des multiplications.&lt;br /&gt;
&lt;br /&gt;
L&#039;algorithme de multiplication naif récursif utilise la segmentation des facteurs en deux de manière successive.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; deux facteurs, nous avons les égalités suivantes :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x = a \times B^{n/2} + b&amp;lt;/math&amp;gt; &lt;br /&gt;
 &amp;lt;math&amp;gt;y = c \times B^{n/2} + d&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; les premières moitiés des facteurs &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;,&lt;br /&gt;
et &amp;lt;math&amp;gt;b&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; les dernières moitiés de ces facteurs ainsi que &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; la base d&#039;écriture du nombre (Nous sommes en base 256 avec les bytes).&lt;br /&gt;
&lt;br /&gt;
Cette présentation des deux facteurs de notre multiplication n&#039;est que la résultante d&#039;un découpage.&lt;br /&gt;
Le [[#Master Theorem|Master Theorem]] nous montre que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n) = 4T(n/2) + O(n)&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Puisque nous avons après découpage 4 entiers de longueur &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ainsi, ce découpage ne permet pas de gagner du temps d&#039;après le [[#Master Theorem|Master Theorem]].&lt;br /&gt;
&lt;br /&gt;
Cependant, l&#039;algorithme de Karatsuba réutilise ce principe en le modifiant, ce qui nous permet de baisser la complexité globale de la multiplication dans son entièreté.&lt;br /&gt;
&lt;br /&gt;
Avec l&#039;écriture ci-dessus des nombres &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt;, on peut facilement en déduire l&#039;égalité suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;x \times y = (ac) \times B^n + (ad + bc) \times B^{n/2} + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
On remarque 4 multiplications longues à faire dans cette expression. Les multiplications dont &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; est l&#039;un des facteurs sont très rapides puisqu&#039;il s&#039;agit de la base dans laquelle nous travaillons. De cela en résulte un décalage plutôt qu&#039;un vrai calcul à faire.&lt;br /&gt;
&lt;br /&gt;
Karatsuba développe une partie de cette expression pour pouvoir négocier une multiplication au prix d&#039;additions et soustractions.&lt;br /&gt;
&lt;br /&gt;
En effet, nous savons déjà que :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d) = ac + ad + bc + bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En manipulant l&#039;égalité nous obtenons :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ad + bc = (a + b)(c + d) - ac - bd&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ac et bd ayant déjà été calculé précédemment, il nous reste uniquement la multiplication &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi de calculer &amp;lt;math&amp;gt;ad + bc&amp;lt;/math&amp;gt; en seulement une multiplication. C&#039;est la que repose le gain de temps de la méthode Karatsuba par rapport à la multiplication naïve.&lt;br /&gt;
&lt;br /&gt;
En effet, l&#039;algorithme n&#039;effectuera alors plus que 3 multiplications :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;ac&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;bd&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;(a + b)(c + d)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La relation de récurrence devient donc :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)=3T(n/2)+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et le Master Theorem nous dit que :&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 20px;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;T(n)\in O(n^{\log_2(3)})&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or &amp;lt;math&amp;gt;\log_2(3)\approx 1.585&amp;lt;/math&amp;gt;, donc la complexité de l&#039;algorithme de Karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt;. Ce qui est bien plus efficace que la multiplication classique, surtout lorsque les facteurs deviennent très grands.&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Karatsuba.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Sur le graphique ci-contre nous avons utilisé la courbe de la multiplication naïve récursive (rouge) à titre de comparaison.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On remarque graphiquement que Karatsuba (jaune) prend moins de temps à chaque calcul de produit.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;On en déduit donc expérimentalement que Karatsuba à une meilleure complexité que la multiplication naïve.&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Ainsi nous vérifions donc ce que le Master Theorem prouve, c&#039;est à dire que la complexité de karatsuba est de &amp;lt;math&amp;gt; \mathcal{O}(n^{1.585})&amp;lt;/math&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4) La multiplication toom-cook-3 ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
L&#039;objectif est de diviser les grands entiers X et Y en trois blocs de taille égale k. &lt;br /&gt;
On les traite alors comme des polynômes de degré 2:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(z) = X_2 \cdot z^2 + X_1 \cdot z + X_0&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;Q(z) = Y_2 \cdot z^2 + Y_1 \cdot z + Y_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On choisit 5 points distincts pour évaluer nos polynômes. &lt;br /&gt;
Ce choix de 5 points est crucial car le produit de deux polynômes de degré 2 est un polynôme de degré 4, qui nécessite 5 points pour être défini. &lt;br /&gt;
Les points standards sont :&lt;br /&gt;
&lt;br /&gt;
 P(0) = X0&lt;br /&gt;
 P(1) = X0 + X1 + X2&lt;br /&gt;
 P(-1) = X0 - X1 + X2&lt;br /&gt;
 P(2) = X0 + 2*X1 + 4*X2&lt;br /&gt;
 P(inf) = X2&lt;br /&gt;
&lt;br /&gt;
(On procède de manière similaire pour Q.)&lt;br /&gt;
&lt;br /&gt;
Ensuite nous multiplions récursivement chaque points de P et de Q ensemble.&lt;br /&gt;
On effectue ainsi 5 multiplications de nombres plus petits au lieu des 9 multiplications requises par la méthode classique.&lt;br /&gt;
&lt;br /&gt;
Après, nous effectuons une interpolation, cela permet de retrouver les 5 coefficients (w_0, w_1, w_2, w_3, w_4) du polynôme produit R(z) à partir des valeurs évaluées calculées précédemment.&lt;br /&gt;
On résout un système d&#039;équations linéaires : &amp;lt;math&amp;gt; R(z) = w_4 z^4 + w_3 z^3 + w_2 z^2 + w_1 z + w_0 &amp;lt;/math&amp;gt;&lt;br /&gt;
Finalement nous pouvons recomposer notre grand entier en positionnant les coefficients w_i aux bons rangs (en les décalant) et à gérer les retenues lors de l&#039;addition finale:&lt;br /&gt;
 &amp;lt;math&amp;gt;Résultat = w_4 B^{4k} + w_3 B^{3k} + w_2 B^{2k} + w_1 B^k + w_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:flex; flex-direction: row; align-items: center; justify-content: center&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div style=&amp;quot;display:inline-block; margin:20px; text-align:center;&amp;quot;&amp;gt;&lt;br /&gt;
        [[Fichier:Toomcook.png|500px]]&lt;br /&gt;
        &amp;lt;div&amp;gt;&amp;lt;b&amp;gt;Karatsuba&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;   &lt;br /&gt;
     &amp;lt;div style=&amp;quot;max-width:800px;&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on a reprit multi récursive (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;on voit que Toom (vert) en dessous de karatsuba (jaune) en dessous de multi classique (rouge)&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;meilleur complexité&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5) La transformée de Fourier rapide  ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
La transformation de Fourier rapide étant plus complexe que les autres algorithmes, nous allons essayer d&#039;expliquer son fonctionnement malgré ne pas avoir réussi à l&#039;implémenter.&lt;br /&gt;
&lt;br /&gt;
Il faut comprendre que cet algorithme va considérer les facteurs comme des polynômes. &lt;br /&gt;
&lt;br /&gt;
D&#039;après le théorème de l&#039;unisolvance, il n&#039;existe qu&#039;un seul polynôme de degré inférieur ou égal à &amp;lt;math&amp;gt; n &amp;lt;/math&amp;gt; défini par &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; points distincts. Cela implique non seulement que nous pouvons représenter chaque entier par un polynôme, mais également que si nous pouvons retrouver &amp;lt;math&amp;gt;n + m + 1&amp;lt;/math&amp;gt; points (avec &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; la longueur des facteurs) du polynômes issue du produit entre deux facteurs, nous pourrons le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit un entier quelconque, de forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;a_n a_{n-1} (...) a_1 a_0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;a_i&amp;lt;/math&amp;gt;, un chiffre composant le nombre en base 10, qui vaut en réalité dans le nombre : &amp;lt;math&amp;gt;a_i \times 10^i&amp;lt;/math&amp;gt;. On peut ainsi l&#039;écrire sous la forme :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x) = a_0 + a_1x + a_2x^2 + \dots + a_{n-1}x^{n-1} + a_nx^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Avec &amp;lt;math&amp;gt;x = 10&amp;lt;/math&amp;gt; car nous sommes en base 10, nous obtenons :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;P(10) = a_0 + a_1 \times 10 + \dots + a_{n-1}10^{n-1} + a_n10^n &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous venons ainsi d&#039;écrire notre nombre sous la forme d&#039;un polynôme unique. Pour nous permettre d&#039;évaluer en un nombre suffisant de points, nous allons devoir ajouter des 0 pour la récursion. Il nous faudra ajouter assez de 0 pour atteindre la prochaine puissance de 2 qui sera supérieure ou égale à &amp;lt;math&amp;gt;2n + 1&amp;lt;/math&amp;gt;. L&#039;ajout de 0 ne change pas le nombre car &amp;lt;math&amp;gt;0 \times x^n&amp;lt;/math&amp;gt; n&#039;ajoute pas de coefficient. &lt;br /&gt;
&lt;br /&gt;
Cet algorithme est récursif et va segmenter en deux parties nos polynômes à chaque récursion. D&#039;un coté les indice paires et d&#039;un coté les impaires.&lt;br /&gt;
&lt;br /&gt;
Ainsi prenons les nombres 1234 et 5678, ces nombres sont respectivement représentés par les polynômes  &amp;lt;math&amp;gt; P(x) = 4 + 3x + 2x^2 + x^3 &amp;lt;/math&amp;gt; et &amp;lt;math&amp;gt; P(x) = 8 + 7x + 6x^2 + 5^3 &amp;lt;/math&amp;gt;. Ce sont des polynômes de degré 3, ainsi leur produit donnera lieu à un polynôme de degré 6. Il nous faudra donc au minimum 7 points d&#039;évaluation pour retrouve ce produit. De ce fait, en les représentant de la manière suivante :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt;[4, 3, 2, 1, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;[8, 7, 6, 5, 0, 0, 0, 0]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nous aurons assez de récursions possible pour les évaluer en un nombre suffisant de points différents car nous auront 3 récursions à faire pour atteindre notre cas de base. A chaque récursion, nous allons diviser nos polynômes en deux parties ce qui nous fait 8 feuilles à la dernière récursion.&lt;br /&gt;
&lt;br /&gt;
En effet à chaque étape de la récursion nous allons séparer nos polynômes en deux tel que :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; P(x)=P_{pair}​(x^2) + x \times P_{impair​}(x^2) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durant les récursions, nous segmentons les polynômes mais ne les évaluons pas. C&#039;est à la remonté des récursions que nous allons réellement évaluer nos polynômes. Lorsque l&#039;on atteint notre cas de base, c&#039;est à dire des polynômes de degré 0, nous remarquons qu&#039;ils sont constants, nous n&#039;avons donc pas besoin de les évaluer. En effet, peut importe le point en lequel nous voudrons l&#039;évaluer, le polynôme renverra la constante. Nous la renvoyons donc seule.&lt;br /&gt;
Ensuite commence la remontée des récursions. A l&#039;étape 1 de notre remontée nous allons reformer le polynôme précédemment séparé pour obtenir un polynôme de degré 1. Puis, nous évaluons ce polynôme avec les racines seconde de l&#039;unité. Nous faisons à nouveau remonté les évaluations et recombinons à nouveau le polynôme qui sera ainsi de degré 2.&lt;br /&gt;
Nous l&#039;évaluons maintenant avec les racines 4eme de l&#039;unité. A chaque évaluation, nous réutilisons les résultats précédents, cela nous est permit par les racines de l&#039;unité qui ont une structure récursive exploitable.&lt;br /&gt;
&lt;br /&gt;
Finalement, nous arrivons à la fin de notre FFT, avec une représentation fréquentielle de la forme :&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;math&amp;gt; [P(W_0), P(W_1), (...), P(W_n-1), P(W_n)] &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette représentation fréquentielle n&#039;est autre que les évaluations du polynôme P aux racines nièmes de l&#039;unité. Nous avons donc maintenant au minimum &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; évaluations des polynômes facteurs. Il nous faut maintenant trouver les évaluations du produit final, c&#039;est à dire les évaluations concernant le polynôme qui sera issue de la multiplication. On pourra ainsi le retrouver.&lt;br /&gt;
&lt;br /&gt;
Soit &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; un polynome tel que &amp;lt;math&amp;gt; C = A \times B &amp;lt;/math&amp;gt;, alors on a :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;math&amp;gt; C(x) = A(x) \times B(x) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi on en déduit que &amp;lt;math&amp;gt; C(W_n^k) = A(W_n^k) \times B(W_n^k) &amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
On comprend donc que &amp;lt;math&amp;gt;FFT(C) = FFT(A) \times FFT(B)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Il faut préciser que l&#039;on fait le produit de FFT(A) et FFT(B) terme à terme, soit évaluation par évaluation.&lt;br /&gt;
&lt;br /&gt;
Une fois en possession de &amp;lt;math&amp;gt;FFT(C)&amp;lt;/math&amp;gt;, qui n&#039;est autre que l&#039;évaluation de notre polynôme final en un nombre suffisant de points pour le retrouver, nous pouvons faire l&#039;&amp;lt;math&amp;gt;IFFT(FFT(C))&amp;lt;/math&amp;gt;, qui va nous permettre de retrouver les coefficients de notre polynôme en faisant l&#039;exacte inverse de la FFT.&lt;br /&gt;
&lt;br /&gt;
Une fois les coefficients retrouvés, il nous suffit de gérer les retenues, et nous retrouvons un entier de la forme :  &amp;lt;math&amp;gt;a_0 a_1 (...)  a_{n-1} a_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
( A FAIRE )&lt;br /&gt;
(on aura pas de courbe par contre)&lt;br /&gt;
&lt;br /&gt;
= II - Produit de matrices =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1.1) naïve ==&lt;br /&gt;
&lt;br /&gt;
La multiplication naïve de matrice requiert d&#039;avoir des tailles de lignes/colonne semblable, ensuite pour chaque membre de la matrice résultat, on doit multiplier les composantes ligne de la première matrice par les composantes colonne de la deuxième et le tout sommé.&lt;br /&gt;
&lt;br /&gt;
On en déduit la formule suivante: &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{i,j} = \sum_{k=1}^{n} (A_{i,k} \times B_{k,j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et visuellement:&lt;br /&gt;
&lt;br /&gt;
 [[Fichier:Matricenaive.jpeg]]&lt;br /&gt;
&lt;br /&gt;
On a donc un algorithme d&#039;ordre de complexité &amp;lt;math&amp;gt;\mathcal{O}(g(n))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 1.2) naïve récursive ==&lt;br /&gt;
&lt;br /&gt;
Dans un premier temps on doit découper nos deux matrices en quatres sous matrices de plus petite taille.&lt;br /&gt;
 &amp;lt;math&amp;gt;A = \begin{pmatrix} A_{11} &amp;amp; A_{12} \ A_{21} &amp;amp; A_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;B = \begin{pmatrix} B_{11} &amp;amp; B_{12} \ B_{21} &amp;amp; B_{22} \end{pmatrix}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite on vient calculer la matrice résultat. &lt;br /&gt;
 &amp;lt;math&amp;gt;C_{11} = (A_{11} \times B_{11}) + (A_{12} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{12} = (A_{11} \times B_{12}) + (A_{12} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{21} = (A_{21} \times B_{11}) + (A_{22} \times B_{21})&amp;lt;/math&amp;gt;&lt;br /&gt;
 &amp;lt;math&amp;gt;C_{22} = (A_{21} \times B_{12}) + (A_{22} \times B_{22})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le côté récursif est utile que pour les matrices de tailles &amp;lt;= 32 (32 valeurs stockées dedans),&lt;br /&gt;
si on est au dessus de cette taille alors on divisera à nouveau nos matrices en quatres.&lt;br /&gt;
On aura 8 multiplications mais de plus petite taille au final.&lt;br /&gt;
&lt;br /&gt;
== 2) strassen ==&lt;br /&gt;
&lt;br /&gt;
=== A) Fonctionnement ===&lt;br /&gt;
&lt;br /&gt;
Strassen consiste à définir 7 produits intermédiaires qui, par des jeux de sommes et de différences, permettent de reconstruire les quadrants de la matrice résultante. On passe ainsi d&#039;une complexité de O(n^3) à environ O(n^2.81)&lt;br /&gt;
&lt;br /&gt;
=== B) Graphique ===&lt;br /&gt;
&lt;br /&gt;
On voit bien la différence de complexité sur la multiplication de grande matrice entre l&#039;approche naïve et l&#039;approche récursive qui est beaucoup plus rapide. &lt;br /&gt;
&lt;br /&gt;
[[Fichier:Analyse matrices.png]]&lt;br /&gt;
&lt;br /&gt;
= Conclusion =&lt;br /&gt;
&lt;br /&gt;
( A FAIRE )&lt;br /&gt;
&lt;br /&gt;
= Mentions =&lt;br /&gt;
 &lt;br /&gt;
Le premier gif est le résultat du travail de &#039;Walké&#039;, licence ci-contre : https://fr.wikipedia.org/wiki/Fichier:Addition.gif&lt;br /&gt;
&lt;br /&gt;
= Sources =&lt;br /&gt;
&lt;br /&gt;
Pour karatsuba : https://www.youtube.com/watch?v=PZ2gdWdKHAA&lt;br /&gt;
&lt;br /&gt;
Pour mieux comprendre la FFT, nous avons utilisé plusieurs sources d&#039;informations :&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
https://www.youtube.com/watch?v=h7apO7q16V0&amp;amp;list=WL&amp;amp;index=1&amp;amp;t=886s&lt;br /&gt;
&lt;br /&gt;
https://fr.wikipedia.org/wiki/Interpolation_polynomiale&lt;/div&gt;</summary>
		<author><name>Mangala</name></author>
	</entry>
</feed>