Archive mensuelle 22 mars 2019

Ensemble de Mandelbrot et Python

Pour la faire courte, l’ensemble de Mandelbrot est l’ensemble des points du plan complexe d’affixe c tels que la suite définie par \( \left\lbrace\begin{array}{l} z_0=0\\z_{n+1}=z_n^2+c\end{array}\right. \) est bornée.

Cet ensemble peut être construit à l’aide de Python, et de son module pygame. La vitesse à laquelle l’ensemble est construit est remarquable! Sur mon ordinateur (16 Mo RAM, sous Windows, processeur Intel Core i5), cela ne met pas plus de 10 secondes pour afficher ceci:

Ensemble de Mandelbrot réalisé avec Python

Avec une autre suite, j’ai obtenu:

Fractale réalisée avec Python

Je vous l’accorde, elle est nettement moins esthétique que la première, mais je ne suis pas Julia, ni Fatou 🙂

Les abonné.e.s de mathweb.fr trouveront les codes Python de ces deux fractales sur cette page.

Initiation à la complexité algorithmique

Loin de moi l’idée de faire un article complet sur la notion de complexité, mais en travaillant sur le nouveau programme de NSI (qui entre en vigueur à la rentrée 2019), je me suis aperçu que cette notion allait pointer le bout de son petit museau perfide… Je voudrais donc par cet article familiariser les élèves avec elle.

C’est quoi la complexité d’un algorithme ?

C’est le nombre d’opérations et d’affectations faites. On appelle cela des opérations élémentaires. Prenons le programme Python suivant:

def convert(n):
    h = n // 3600
    m = (n - 3600*h) // 60
    s = n % 60
    return h,m,s

n = 102152

print("{} secondes = {} h {} min {} sec.".format(n,convert(n)[0],convert(n)[1],convert(n)[2]))

Dans la fonction “convert”, il y a :

  • 3 affectations (h, m et s)
  • 5 opérations (n//3600, 3600*h, n-3600*h, (n-3600*h)//60 et n%60)

La complexité de cette fonction est donc égale à 8.

Exercice 1

Montrer que la complexité de la fonction suivante est égale à 5.

def myFunction(n):
    if n%3 == 0:
        p = n/3 + 2
    else:
        p = n*2 + 1
    return p

Solution

il y a d’abord un test (if) dans lequel il y a une opération (n%3), ce qui nous fait pour le moment une complexité de 2.

Ensuite, quelle que soit l’issue du test, il y a 1 affectation (p) et 2 opérations (pour calculer p), soit une complexité augmentée de 3.

Au final, on obtient bien une complexité de 5.

Structures itératives

Dans un programme, il y a souvent des boucles. Nous allons voir un exemple avec une boucle “for”:

def recherche(l,x):
    for i in range(len(l)):
        if l[i]==x:
            return i
    return -1

l = "Bonjour."
x = "p"

print(recherche(l,x))

La fonction recherche a pour but d’afficher le rang où se trouve le caractère “x” dans la chaîne de caractères “l”, et affiche “-1” si ce dernier n’est pas trouvé.

  • Au niveau de la boucle for, il y a 1 addition (sur i), une affectation (i) et une comparaison (test si i<len(l));
  • dans la boucle for, il y a un test (if l[i]==x).

Au final, si n est la longueur de la chaîne de caractères, il y a 4n opérations élémentaires, qui correspond à la complexité de la fonction recherche.

Exercice 2

Calculer la complexité de la fonction somme définie dans ce programme:

def somme(n):
    s = 0
    for i in range(n+1):
        s += i
    return s

print(somme(100))

Solution

Il y a :

  • au début, 1 affectation (s = 0);
  • au niveau de la boucle for, 3 opérations élémentaires (affectation sur i, opération d’incrémentation sur i, test sur i);
  • dans la boucle for, il y a 2 opérations élémentaires (1 affectation sur s et une opération sur s).

Ainsi, il y a 5 opérations élémentaires dans la boucle, répétées n fois, plus la première (hors boucle). La complexité de la fonction somme est donc égale à 5n+1.

En général, on appelle T la complexité (initiale de Time).

Exercice 3

Calculer la complexité de la fonction mystere du programme suivant:

def mystere(n):
    m = 0
    for i in range(n):
        for j in range(i):
            m += i+j
    return m

print(mystere(100))

Solution

  • Il y a avant tout 1 affectation dès le début (m=0);
  • ensuite, au niveau de la boucle sur i, 3 opérations élémentaires;
  • au niveau de la boucle sur j, il y a 3 opérations élémentaires répétées i fois;
  • dans la boucle sur j, il y a 3 opérations élémentaires (1 affectation sur m, une somme sur m et une somme de i et j).

Ainsi, à part la première affectation, il y a 9 opérations élémentaires pour chaque valeur de j possible. Il y a:

  • 1 valeur possible de j quand i=0 (j=0);
  • 1 valeur possible de j quand i=1 (j=0);
  • 2 valeurs possibles de j pour i=2 (j=0 et j=1):
  • etc.
  • n-1 valeurs possibles de j pour i=n-1.

La complexité est donc égale à:$$\begin{aligned} & 1+9\times(1+1+2+3+\cdots+(n-1))\\=\ & 10+\frac{9n(n-1)}{2}\\=\ & \frac{1}{2}(9n^2-9n+20).\end{aligned}$$

Ordre de grandeur de la complexité

Nous le voyons dans l’exemple précédent, la complexité peut s’exprimer par un polynôme. Dans un tel cas, si n est relativement grand, on pourra assimiler la complexité à son ordre de grandeur. Par exemple ici, la complexité de la dernière fonction est de l’ordre de \(n^2\). On écrira alors:$$T_n=O(n^2).$$On dira ici que la complexité est quadratique.

Pour l’exercice 2, \(T_n=5n+1=O(n)\). On dira que la complexité est linéaire.

Fonction récursive

Une des fonctions récursives la plus simple est celle qui permet de calculer n! (la factorielle de n), c’est-à-dire \(2\times3\times\cdots\times n\).

def factorielle(n):
    if n == 0:
        return 1
    else:
        return n*factorielle(n-1)

Il y a un test pour commencer (sur n). Si le test est vrai, on s’arrête, sinon on effectue une opération (un produit) en faisant appel à la même fonction. Ainsi, si n est non nul, il y a 2 opérations élémentaires + le nombre d’opérations élémentaires de la fonction au rang n – 1.

Si \(T_n\) représente la complexité de la fonction factorielle(n) alors:$$T_n=T_{n-1}+2,\quad T_0=1.$$

La complexité est donc donnée par une suite arithmético-géométrique. C’est toujours le cas pour les fonctions récursives.

On peut alors montrer que \(T_n=2n+1=O(n).\)

Généralités

Si on considère une fonction toto(n) dans laquelle on fait appel à cette même fonction a fois et dans laquelle il y a b opérations élémentaires, si on note \(T_n\) sa complexité alors:$$T_n=aT_{n-1}+b.$$Ainsi, \(T_n=O(a^n)\). La complexité est alors exponentielle.

Exercice 4

Quelle est la complexité de la fonction suivante:

def fibo(n):
    if n<2:
        return n
    else:
        return fibo(n-1) + fibo(n-2)

Solution

Il y a 2 opérations élémentaires (un test sur n et une addition de fibo(n-1) et fibo(n-1)) donc:$$T_n=T_{n-1}+T_{n-2}+2,\quad T_0=T_1=1.$$ La complexité est donc une suite linéaire d’ordre 2 et d’équation caractéristique:$$r^2-r-1=0$$ de solutions \(r_1=\frac{1+\sqrt5}{2}\) et \(r_2=\frac{1-\sqrt5}{2}\). Comme \(|r_1|>|r_2|\), une propriété nous dit que la complexité de la fonction fibo(n) est:$$T_n=O(r_1^n).$$La complexité est alors exponentielle.

Python, turtle et un arbre de Pythagore

Un arbre de Pythagore est un arbre qui ressemble à ça:

Arbre de Pythagore réalisé avec Python

Python, turtle et un arbre

Je ne suis pas très fan du module turtle, car je préfère la programmation numérique, mais il faut bien avouer que ce module est pratique pour dessiner des schémas récursifs, comme les fractales.

La formule de Viète sur les polynômes

Cet article est accessible aux élèves de lycée dès la classe de Terminale.

Vous savez ce qu’est un polynôme ? C’est une expression de la forme:$$P(x)=\sum_{k=0}^n a_kx^k=a_nx^n+a_{n-1}x^{n-1}+\cdots+a_1x+a_0.$$

Vous savez ce qu’est une racine d’un polynôme ? C’est une valeur r telle que P(r)=0.

Vous savez ce que sont les nombres complexes ? Ce sont des nombres qui s’écrivent sous la forme a + ib, où i² = -1. Ce sont des nombres imaginaires.

La formule de Viète, du nom du mathématicien français du XVIème siècle François Viète, nous dit que la somme des racines complexes du polynôme P est égale à \(-\frac{a_{n-1}}{a_n}\).

Démonstration

La démonstration de cette formule est assez simple si l’on connaît le théorème de Gauss stipulant que tout polynôme de degré n admet exactement n racines complexes. Ainsi, tout polynôme de degré n peut se factoriser sous la forme : $$P(x)=a_n(x-r_1)(x-r_2)(x-r_3)\cdots(x-r_{n-1})(x-r_n)$$ où \(r_1,\ r_2,\ \ldots,\ r_n\) représentent les n racines complexes du polynôme.

En développant partiellement la forme factorisée, on obtient:$$P(x)=a_nx^n-a_n(r_1+r_2+\cdots+r_n)x^{n-1}+\cdots+(-1)^na_nr_1r_2\cdots r_n.$$Par identification avec la forme développée:$$P(x)=a_nx^n+a_{n-1}x^{n-1}+\cdots+a_1x+a_0,$$les coefficients des \(x^{n-1}\) doivent être égaux, et donc:$$a_{n-1}=-a_n(r_1+r_2+\cdots+r_n)$$ce qui donne:$$r_1+r_2+\cdots+r_n=-\frac{a_{n-1}}{a_n}.$$

On peut même affirmer de la même façon que:$$a_0=(-1)^na_nr_1r_2\cdots r_n$$soit:$$r_1r_2\cdots r_n=(-1)^n\frac{a_0}{a_n}.$$

Python et le nombre d’or

Voici un article qui est abordable dès le lycée.

La suite de Fibonacci

Imaginons une suite de nombre qui commence par “1” et “1”.

On souhaite que le nombre qui vient juste après soit égal à la somme des deux derniers nombres. Ainsi, le 3ème nombre est égal à 1+1, soit “2”. Après “2”, il y a 2+1=3, puis après ce “3”, il y a 3+2=5.

Nombre de chiffres d’un nombre

Il arrive parfois qu’un nombre s’écrive de manière très condensée mais que le nombre de chiffres qui le compose soit très grand.

Par exemple, le nombre \(9^{8^7}\) ne s’affiche même pas avec Xcas… tellement le nombre de chiffres qui le composent est grand. Mais comment savoir ce nombre de chiffres ?

En base décimale

Notons:$$N=\sum_{k=0}^{n-1}a_k\times10^k.$$

Ce nombre est composé de \(n\) chiffres : \(a_0,\ a_1,\ a_2,\ \ldots,\ a_{n-1}\). On l’écrit:$$N=\overline{a_{n-1}a_{n-2}\cdots a_2a_1a_0}^{10}.$$Un nombre à \(n\) chiffres est nécessairement compris entre \(10^{n-1}\) et \(10^n\), donc:$$10^{n-1}\leq N < 10^n.$$En composant par le logarithme décimal, on obtient l’encadrement:$$\log(10^{n-1}) \leq \log(N) < \log(10^n),$$soit:$$(n-1)\log(10)\leq\ln(N)<n\log(10).$$Or, par définition, \(\log(10)=1\) d’où finalement:$$n-1\leq\log(N)<n.$$On en déduit alors que \(n\) est l’entier immédiatement supérieur (ou égal) à \(\log(N)\).

Par exemple, $$\log\left(9^{8^7}\right)=8^7\log(9)\approx4607913,91681$$ donc le nombre de chiffres de \(9^{8^7}\) est 4607914.

En binaire

Le principe est le même. On considère un nombre:$$N=\overline{a_{n-1}\cdots a_1a_0}^{2}=\sum_{k=0}^{n-1}a_k\times2^k\,,\,\ a_{n-1}\neq0.$$Alors, pour \(N\) exprimé en décimal, $$2^{n-1} \leq N< 2^n$$ soit: $$\frac{\ln(2^{n-1})}{\ln2} \leq \frac{\ln(N)}{\ln2} < \frac{\ln(2^n)}{\ln2},$$ d’où: $$n-1 \leq \frac{\ln(N)}{\ln2} < n.$$ Ainsi, le nombre \(n\) de chiffres (en binaire) du nombre \(N\) est-il égal à l’entier immédiatement supérieur (ou égal) à \( \frac{\ln(N)}{\ln2} \).

Par exemple, \( \overline{1101}^{2}=\overline{13}^{10}\), et \( ENT\left( \frac{\ln(13)}{\ln2}\right)+1 =4\). Il y a bien 4 chiffres dans le nombre binaire correspondant à 13 (en base décimale).

Généralités

On peut bien entendu généraliser cette formule en disant que si \(N\) est un nombre décimal alors le nombre de chiffres du nombre en base \(a\) correspondant est égal à:$$n=ENT\left(\frac{\ln(N)}{\ln(a)}\right)+1.$$

Supportscreen tag