Branches conditionnelles et boucles

Table des matières

1. Branches conditionnelles et assertions

1.1. if ... else ...

L'extrait ci-dessous implémente une fonction maximum.

def maximum(a,b):
    if a >= b:   # Si a≥ b
        return a # renvoyer a
    else:        # sinon
        return b # renvoyer b

\hspace{0.4cm} Règles de syntaxe pour l'utilisation if :

  • Le if est suivi une expression (a>=b) qui s'évalue en un booléen, suivi de « : ».
  • Les instructions qui sont à exécuter sous cette condition sont indentées.
  • Le else:, optionnel, est au même niveau d'indentation que le if.
  • Les instructions à exécuter sous le else sont indentées.

Le if signifie «si» et le else «sinon». On peut utiliser une branche if sans la compléter d'une branche else.

Une fonction max est déjà prédéfinie en Python et supporte un nombre arbitraire d'arguments.

max(-4,5,2) renvoie $5$.

Écrire une fonction valeur_absolue, qui renvoie la valeur absolue de son argument.

Python prédéfinit une fonction abs qui calcule la valeur absolue.

1.2. Opérateurs de comparaison et opérations sur les booléens

Tableau 1 : Opérateurs de comparaison
Opérateur Signification Exemple
>, < supérieur/inférieur stricte 3 ** 2 > 2 ** 3
>=, <= supérieur/inférieur ou égal x * x >= 0
== égal 7 // 2 == 3
!= différent 7 % 2 != 0

Ces opérateurs renvoient un booléen, c'est-à-dire True ou False. Pour chaque exemple plus haut, préciser à droite s'il renvoie True ou False.

Écrire une fonction est_impair qui prend un entier en argument et qui renvoie True s'il est impair, et False sinon.

Tableau 2 : Opérations sur les booléens
Opérateur Signification Exemple
not négation (not a>b) $\ssi$ a<=b
and \hspace{0.2cm} ( & ) conjonction (et) (a>=b and a<=b) $\ssi$ a==b
or \hspace{0.4cm} ( $\mathbb{\vert}$ ) disjonction (ou) (a>b or a==b) $\ssi$ a>=b
In [1]: (7**8 > 8**7) or (7**8 < 8**7)
Out[1]: True # Rmq : les parenthèses sont inutiles
In [2]: (7**7) % 2 == 1 and not (7**7) % 3 == 0
Out[2]: True # Idem

Écrire une fonction est_grand_impair de deux lignes qui prend un entier en argument et qui renvoie True s'il est impair et supérieur à $100$, et False sinon.

Évaluation paresseuse

En Python, l'évaluation de conditions booléennes contenant des opérateurs est paresseuse. Par exemple, pour évaluer cond1 and cond2, l'interpréteur va commencer par évaluer cond1, et si cond1 est False, il n'évaluera pas cond2. C'est utile lorsque l'évaluation de cond2 donnerait une erreur si cond1 n'était pas vérifiée.

# La fonction divise renvoie True si a divise b
# On suppose que b∞nℕ* et a∞nℕ
def divise(a,b):
    # L'instruction b % a donne une erreur si a == 0
    # mais ici, elle n'est exécutée que si a != 0
    return a != 0 and b % a == 0



l = [7, 1, 0, 3, 0]
i, c = 0, 0
# Ici, l[i] renverrait une erreur
# lorsque i = len(l)
# On cherche le nb d'éléments de l avant le premier élément nul
while i < len(l) and l[i] != 0:
    c += 1
    i += 1
print(c)

Sachant que $0$ divise $0$, écrire une nouvelle fonction divise de deux lignes qui soit correcte pour tout $b\in\N$.

1.3. Schémas de retours

Dans une fonction, l'instruction return interrompt l'exécution du corps de la fonction et peut simplifier la logique.

Les deux schémas suivants sont équivalents.

def fn(…):
    if C:
        return A
    else:
        return B
def fn(…):
    if C:
        return A
    return B

Le schéma de gauche suivant est à éviter, étant équivalent à celui de droite.

def fn(…):
    if C:
        return True
    else:
        return False
def fn(…):
    return C



Qu'affichent les extraits suivants, et que renvoient-ils ?

def f(a):
    if a % 2 > 0:
        print("impair")
        return a
    print("pair")

f(5)
def g(a):
    if a % 2 > 0:
        return(a)
        print("impair")
    print("pair")

g(5)
def h(a):
    if a % 2 > 0:
        print("impair")
    print("pair")


h(5)

\vspace*{2\baselineskip}

1.4. Branches imbriquées et disjonctions de cas

La fonction positifs_non_nuls prend deux arguments et renvoie True si ses deux arguments sont positifs, et non tous les deux nuls. Les trois extraits suivants sont équivalents.

def positifs_non_nuls(a,b):
    if a > 0:
        if b >= 0:
            return True
        else:
            return False
    if a >= 0:
        if b > 0:
            return True
    return False
def positifs_non_nuls(a,b):
    if a > 0:
        return b >= 0
    if a == 0:
        return b > 0
    return False

def positifs_non_nuls(a,b):
    return (a >= 0 and b > 0) or (a > 0 and b >= 0)

Écrire une fonction fizzbuzz qui prend un argument un entier n et qui imprime «Fizz» si n est divisible par $3$, «Buzz» si n est divisible par $5$, «FizzBuzz» si n est à la fois divisible par $3$ et $5$, et simplement l'entier n s'il n'est divisible ni par $3$, ni par $5$.

La fonction print passe à la ligne, donc les instructions successives print("Fizz"); print("Buzz") n'affiche pas «FizzBuzz».

Branche elif

Plutôt qu'imbriquer des branches les unes dans les autres, on peut utiliser la syntaxe elif pour «Sinon si».

Les deux extraits suivants sont équivalents.

if note > 16:
    print("Félicitations")
else:
    if note > 13:
        print("Bien")
    else:
        if note > 10:
            print("Admis")
        else:
            print("Non admis")
if note > 16:
    print("Félicitations")
elif note > 13:
    print("Bien")
elif note > 10:
    print("Admis")
else:
    print("Non admis")


Écrire une fonction nb_grands qui prend en argument trois nombres a,b,c et qui renvoie le nombre de ses arguments qui sont $\geq 100$. Par exemple, l'appel nb_grands(11, 120, 25) renverra $1$.

Indication : On pourra éventuellement utiliser une fonction intermédiaire ne prenant que deux arguments, ou 1.

1.5. Assertions et jeux de tests

Les assertions sont des instructions particulières qui prennent la forme assert(condition). Si condition s'évalue en True, cette instruction ne fait rien, et si condition est False, l'instruction interrompt l'exécution du programme et signale une erreur.

Dans le shell, essayer les commandes assert(1+1==2) et assert(1+1==3).

On utilisera les assertions

  1. pour tester les valeurs renvoyées par une fonction sur des exemples,
  2. éventuellement pour signaler des conditions qui doivent être vérifiées par les arguments d'une fonction.

La fonction suivante prend en argument trois entiers a,b,c, avec $a\neq 0$, et renvoie le nombre de solutions réelles de l'équation $ax^2 + bx + c = 0$ en calculant le discriminant du polynôme.

### Nombre de solutions d'un trinôme

def nb_sols(a,b,c):
    assert(a != 0)  # si a=0, ce n'est pas un trinôme
    d = b*b - 4*a*c # Δ = b² − 4ac
    if d > 0:
        return 2    # early return : le reste n'est pas évalué
    if d == 0:
        return 1
    return 0        # dans ce cas, c'est que Δ < 0
# Les assertions suivantes sont en dehors du corps de la fonction
# Elles testent le comportement de la fonction
assert(nb_sols(1,0,1) == 0)  # x² + 1 = 0 n'a pas de solutions
assert(nb_sols(1,2,1) == 1)  # x² + 2x + 1 = 0 a une unique solution : −1
assert(nb_sols(1,0,-1) == 2) # x² − 1 = 0 a deux solutions : ± 1

La première ligne commençant par ### délimite une cellule, concept propre à certains IDE. Le raccourci Ctrl+Entrée permet d'évaluer tout le code de la cellule actuelle. En l'occurrence, cela évalue la définition de la fonction et les trois assertions (tests) qui suivent.

  1. Recopier le code précédent sans les commentaires, mais avec les assertions et l'évaluer.
  2. Dans le shell, essayer la commande nb_sols(0,1,1).
  3. Modifier légèrement le code de la fonction pour qu'elle ne soit plus correcte. Réévaluer le code et les tests.
  4. Réécrire (modifier dans l'éditeur) le corps de la fonction nb_sols en utilisant une première condition d >= 0 et une deuxième instruction if imbriquée. Évaluer à nouveau, pour vérifier que les tests passent.
  5. ★ Modifier la fonction nb_sols pour qu'elle ne soit plus correcte, mais que les tests passent quand même.

Un bon jeu de tests doit couvrir les différents cas généraux possibles, et d'éventuels cas critiques (arguments nuls, ou liste vide par exemple).

Le développement piloté par les tests (test-driven development), est une pratique de développement qui consiste à concevoir un programme (une fonction, un logiciel) en écrivant les tests avant d'écrire le code source. En plus d'assurer la correction de la fonction finale (si les tests passent), cette pratique force à s'interroger sur le comportement que devra avoir la fonction dans les cas critiques.

On veut réécrire la fonction nb_sols pour qu'elle fonctionne également si $a=0$. On suppose à la place que les trois entiers a,b,c sont non tous nuls, et elle doit renvoyer le nombre de solutions réelles de l'équation $ax^2 + bx + c = 0$.

  1. Écrire deux tests (en plus des trois précédents) qui couvrent les nouveaux cas.
  2. Écrire la nouvelle fonction nb_sols. On utilisera une assertion pour s'assurer les arguments vérifient la condition donnée.

1.6. Exercices

On veut écrire une fonction mediane3 qui prend trois entiers a,b,c en argument et qui renvoie la médiane de ces trois nombres.

  1. Proposer un jeu de deux ou trois tests.
  2. Écrire la fonction mediane3.

Les années bissextiles sont celles divisibles par 400, et aussi celles divisibles par 4 mais non divisible par 100. On veut écrire une fonction bissextile qui prend un entier en argument et renvoie True si l'année est bissextile, et False sinon.

  1. Proposer un jeu de deux ou trois tests.
  2. Écrire la fonction bissextile.
  3. ★ Quel le jour de la semaine était-on le 1ᵉʳ janvier de l'an 1000 ?

Écrire une fonction type_triangle qui prend en argument trois entiers strictement positifs et qui affiche le type de triangle dont ces entiers sont les longueurs des côtés.

Les types possibles sont «plat», «isocèle» (et non plat), «équilatéral», «scalène» (aucun des trois premiers), ou «impossible».

assert(type_triangle(1,1,2) == "plat")
assert(type_triangle(4,1,2) == "impossible")
assert(type_triangle(4,3,2) == "scalène")
assert(type_triangle(4,3,3) == "isocèle")
assert(type_triangle(3,3,3) == "équilatéral")

def type_triangle(a,b,c):
    if a == b+c or b == a+c or a == b+c:
        return "plat"
    if a > b+c or b > a+c or c > a+b:
        return "impossible"
    if a == b and b == c:
        return "équilatéral"
    if a == b or b == c or a == c:
        return "isocèle"
    return "scalène"

2. Boucles

2.1. Boucles for

Les boucles permettent de répéter un bloc d'instructions. La boucle for de Python permet de répéter des instructions un nombre prédéterminé $n$ de fois. On l'utilise avec l'instruction range(n) qui énumère les entiers de $\boxed{0 \text{ à }n-1}$ (il y en a $n$).

Dans les extraits suivants ( essayer), la variable i va prendre successivement les valeurs de l'énumération, et pour chaque valeur, les instructions à l'intérieur de la boucle seront exécutées.

for i in range(5):
    print("Coucou")
for i in range(5):
    print(i)

La fonction range peut également être appelée avec 2 ou 3 arguments :

  • L'instruction range(a,b) énumère les entiers de $a$ (inclus) à $b$ (exclus), donc les entiers de $\db{a,b-1}$.
  • $\xcancel{\heartsuit}$ L'instruction range(a,b,c) énumère les entiers de $a$ (inclus) à $b$ (exclus), par pas de $c$. C'est-à-dire les entiers $a,a+c, a+2c,\dots$.

    Le pas peut être négatif, auquel cas il faut choisir $b\lt a$.

La fonction suivante calcule la somme $S = \sum_{i=1}^n i$ des entiers de $1$ à $n$.

def somme(n):
    s = 0                   # La somme sera stockée dans la variable s
    for i in range(1,n+1):  # i prend les valeurs entières de 1 à n
        s = s + i           # On incrémente la variable s
    return s                # Noter le retour à l'indentation précédente

Noter les « : » obligatoires dans la ligne du for, et l'indentation qui suit. Ce sont les instructions sous l'indentation qui seront répétées.

Que vaut la variable s après l'exécution des extraits suivants ?

s = 0
for i in range(2,5):
    s = s + 1
s = s + 1
s = 0
for i in range(2,5):
    s = s + 1
    s = s + 1

L'incrémentation s = s + i peut également s'écrire s += i. Les opérateurs *=, /= et -= existent également.

Écrire une fonction factorielle, prenant un entier n en argument et renvoyant $n! = 1 \times 2 \times \dots \times n$.

Le module math contient une fonction math.factorial pour calculer la factorielle d'un entier.

Écrire une fonction u, prenant un entier n en argument et renvoyant le terme $u_n$ de la suite définie par $u_0=1$ et la relation de récurrence $\forall n\geq 0,\, u_{n+1} = 2 u_n - 5$.

On veut écrire une fonction somme_impairs qui prend en argument deux entiers relatifs $a,b$ et qui renvoie la somme des entiers impairs compris entre $a$ et $b$ (inclus).

  1. Écrire un jeu de tests sur la fonction somme_impairs.
  2. Implémenter somme_impairs. On écrira deux versions, l'une en utilisant le troisième argument de range, l'autre sans.

Si $(u_n)_{n\in\N^*}$ est une suite, on note $\sum_{k=1}^n u_k$ la somme des termes d'indice $k$ de la suite, pour $k$ variant de $1$ à $n$. Autrement dit, $\displaystyle\sum_{k=1}^n u_k = u_1 + u_2 + \dots + u_n$, et par convention $\displaystyle\sum_{k=1}^0 u_k = 0$.

\enlargethispage{1.5cm}

On considère une suite $(u_n)$ définie par $u_0 = 1$ et $\forall n\in\N,\,u_{n+1} = 2u_n + 1$. On note, pour $n\geq 0$, $$S_n = \sum\limits_{k=1}^{n} u_k = u_1 + u_{2} + \dots + u_{n} \et T_n = \sum\limits_{k=n}^{2n} u_k = u_n + u_{n+1} + \dots + u_{2n}.$$

  1. Écrire une fonction S d'argument $n\in\N$ qui renvoie $S_n$.
  2. Écrire une fonction T d'argument $n\in\N$ qui renvoie $T_n$.

2.2. Boucles while

Une boucle while (tant que) répète une série d'instruction tant qu'une condition est vérifiée.

  1. Comprendre, puis exécuter () le premier extrait.
  2. Quel résultat est-ce que l'exécution du deuxième extrait va produire ? Vérifier.

    Utiliser un des boutons en haut du shell pour interrompre l'exécution (l'éclair jaune).

i = 0
while i < 3: # tant que i< 3
    print(i)
    i = i+1  # ou i += 1
i = 0
while i < 5: # tant que i< 5
    print(i)
i = i+1      # ou i += 1

Le premier extrait est équivalent une boucle for i in range(3). D'une manière générale, toute boucle for peut être transformée en une boucle while, mais l'inverse n'est pas vrai.

Écrire des extraits de code affichant (avec print)

  1. le plus petit entier $n$ vérifiant $n! > 10^n$.

    ★ Justifier a priori son existence.

  2. le plus grand entier $n$ tel que $2^n \leq 10^7$.

    En donner une expression explicite.

2.3. Exemples

Quelques extraits

Pour chaque extrait suivant, indiquer la valeur de la variable a à la fin de l'exécution.

# Extrait 1
a = 0
for i in range(n):
    a += 1
# Extrait 2
a = 0
for i in range(1,n+1):
    a += 1
# Extrait 3
a = 1
for i in range(n):
    a = 2*a

# Extrait 4
a = 1
while a <= 10:
    a *= 2
# Extrait 5
a = 1
while 2*a <= 10:
    a = 2*a
# Extrait 6
a = 1
while a*a <= 100:
    a = a*a

# Extrait 7
a = 40
while a % 2 == 0:
    a = a // 2
# Extrait 8
a = 0
for i in range(100):
    a += (-1) ** i

Récurrence double

On considère une suite $(u_n)_{n\in\N}$ vérifiant une relation de récurrence double comme $\forall n\in\N,\,u_{n+2} = 2u_{n+1} + u_n$.

Pour calculer la valeur de $u_n$, on maintient deux variables $a, b$ qui contiennent deux valeurs successives de la suite, que l'on met à jour avec des affectations par couple, dont le fonctionnement est rappelé dans l'extrait suivant.

In [1]: a, b = 3, 5 # Initialisation de deux variables : a=3=u₀ et b=5 = u₁
In [2]: a, b = b, 2*b + a  # a = 5 et b = 2× 5+3
In [3]: a, b
Out[3]: (5, 13) # a = u₁ et b = u₂

Écrire une fonction fib qui prend en argument un entier $n\in\N^*$ et renvoie le terme $F_n$ de la suite de Fibonacci définie par $F_1 = F_2 = 1$ et $\forall n\in\N^*,\, F_{n+2} = F_{n+1} + F_n$.

On considère deux suites $x_n$ et $y_n$ définies par $\forall n\in\N,\,\begin{cases} x_{n+1} = x_n - 2 y_n \\ y_{n+1} = x_n + y_n \end{cases}$. Écrire une fonction xy qui prend un entier $n$, et les valeurs x0, y0 en argument et renvoie le couple $(x_n, y_n)$.

2.4. Boucles imbriquées

Dans l'extrait de gauche qui suit, la même boucle for i est exécutée deux fois. L'extrait de droite est donc équivalent.

s = 0
for i in range(3):
    s = s + i
for i in range(3):
    s = s + i
s = 0
for j in range(2):
    for i in range(3):
        s = s + i

Que vaut la variable s après l'évaluation d'un des deux extraits précédents ?

  1. Que valent les variables s1,s2,s3 après l'exécution des extraits suivants ?
  2. Dans chaque cas, combien de fois l'instruction d'incrémentation s = s + ... est-elle exécutée ?
s1 = 0
for i in range(3):     # i prend les valeurs 0,1,2
    for j in range(3): # j prend les valeurs 0,1,2
        s1 = s1 + j
s2 = 0
for i in range(3):
    for j in range(3):
        s2 = s2 + i + j
s3 = 0
for i in range(3):
    for j in range(i,3):
        s3 = s3 + i + j

Une itération avec deux boucles for imbriquée sur deux ensembles $I$, $J$ indépendant revient à itérer sur les couples du produit cartésien $I\times J$. En particulier, les instructions à l'intérieur sont exécutées $|I|\times |J|$ fois.

for i in range(n):
    for j in range(m):
        # Exécuté nm fois
        f(i, j)
for i in range(n):
    for j in range(n):
        # Exécuté n² fois
        f(i, j)
for i in range(n):
    for j in range(i, n):
        # Exécuté \frac{n(n+1)}{2} = O(n²) fois
        f(i, j)

  1. Écrire deux fonctions prenant en argument un entier $n$ et renvoyant les valeurs des sommes $$S_1 = \sum\limits_{i=1}^{n} \sum\limits_{j=1}^{n} \frac{1}{ij} = \sum_{1\leq i,j\leq n} \frac{1}{ij} \quad \et \quad S_2 = \sum\limits_{i=1}^{n} \sum\limits_{j=i+1}^{n} \frac{1}{ij} = \sum_{1\leq i\lt j\leq n} \frac{1}{ij}.$$
  2. Π Comment calculer chaque somme en utilisant au plus $O(n)$ opérations, c'est-à-dire un nombre d'opérations $\leq Kn$, pour une constante $K$ indépendante de $n$ ?
  1. On a $\sum_{i,j} a_{i}a_j = (\sum_i a_i)^2$
def S1(n):
    s = 0
    for i in range(1,n+1):
        for j in range(1,n+1):
            s += 1/(ij)
    return s

def S2(n):
    s = 0
    for i in range(1,n+1):
        for j in range(j+1,n+1):
            s += 1/(ij)
    return s

  1. En utilisant deux boucles imbriquées, écrire une fonction somme_deux_carres qui prend en argument un entier $n$ et détermine si $n$ est la somme de deux carrés, c'est-à-dire s'il existe deux entiers $i,j$ tels que $n = i^2 + j^2$.
  2. Pour déterminer si un entier n est le carré d'un autre entier, on peut utiliser l'expression

    math.floor(math.sqrt(n))**2 == n
    

    En utilisant celle-ci, réécrire la fonction précédente en utilisant une seule boucle.

    Grâce aux fonctions sur les flottants, cette nouvelle procédure est beaucoup plus rapide, mais elle n'est pas correcte pour des entiers très grands, car les opérations sur les flottants ne sont pas exactes.

  3. Imprimer les nombres premiers $\leq 100$ qui sont sommes de deux carrés, puis ceux qui ne le sont pas. Que conjecturer ?

Les nombres triangulaires sont les nombres de la forme $t_i = \frac{i(i+1)}{2}$, pour $i\in\N^*$. Les carrés parfaits sont les nombres de la forme $j^2$, pour $j\in\N$.

  1. En utilisant deux boucles imbriquées, écrire du code Python qui affiche tous les entiers $n\in\N^*$ inférieurs à 1000 qui sont à la fois des nombres triangulaires et des carrés parfaits.
  2. ★ Vérifier que $8t_n +1$ est un carré. En déduire qu'il existe une infinité d'entiers de $\N^*$ qui sont à la fois des nombres triangulaires et des carrés parfaits.

    Indication : Si $t_n$ est un carré parfait, en construire un autre.

  3. ★ Montrer que $1$ est le seul nombre triangulaire qui soit un cube parfait.

    On admettra la conjecture de Catalan, démontrée par Mihăilescu, selon laquelle l'équation $a^b - c^d = 1$ n'admet aucune solution entière non triviale autre que $3^2 - 2^3 = 1$.

# Affichage des nbs triangulaires qui sont des carrés
import math
for i in range(math.floor(math.sqrt(1000)) + 1):
    n = i ** 2
    j = 0
    while (j * (j+1)) // 2 < n:
        j += 1
    if (j * (j+1)) // 2 == n:
        print n

3. Éléments d'analyse des algorithmes : un exemple

La fonction ci-contre prend en argument deux entiers positifs $n,b$, avec $b\gt 0$. Que renvoie-t-elle ?

def mystere(n, b):
    m = n
    while m >= b:
        m = m - b
    return m

3.1. Terminaison de l'algorithme

Quand un algorithme utilise une boucle while, il faut vérifier que celle-ci termine, c'est-à-dire que la condition d'arrêt finit (ici m < b) par être vérifiée.

Pour cela, on utilise un variant de boucle. Dans la majorité des cas, c'est une variable prenant des valeurs entières positives dont la valeur décroît strictement à chaque itération de la boucle. Comme il n'existe pas de suite d'entiers naturels strictement décroissante, cela justifie que la boucle ne tourne qu'un nombre fini de fois, donc que la condition d'arrêt est atteinte.

Dans la fonction mystere, la variable m prend des valeurs entières positives (car la boucle s'arrête si $m\lt 0$) et strictement décroissantes (car $b\gt 0$). Cela justifie la terminaison de l'algorithme. On a également identifié l'hypothèse importante ($b\gt 0$).

3.2. Correction de l'algorithme

La fonction mystere détermine le reste de la division euclidienne de n par b.

Pour justifier qu'elle fait bien cela, on utilise un invariant de boucle. On identifie une quantité qui ne change pas de valeur, ou une assertion mathématique qui reste vraie à chaque itération de la boucle.

3.2.1. Un premier invariant de boucle

Durant l'exécution de la boucle de la fonction mystere, on a toujours $$m \equiv n [b].$$ Formellement, cela se démontre par récurrence.

  • C'est vrai au début de la boucle, car $m$ a pour valeur initiale $n$.
  • Si c'est vrai à la fin d'une itération de la boucle, en notant $m^+$ la nouvelle valeur de $m$ dans l'itération d'après, on a $m^+ = m - b$, donc $m^+ \equiv m- b \equiv m \equiv n [b]$, d'après l'hypothèse de récurrence.

3.2.2. Un second invariant

On peut énoncer un invariant plus précis, sous la forme d'une propriété qui dépend du nombre d'itération de la boucle : $$\text{Après la i-ème itération de la boucle, } m = n - ib$$

Cet invariant se justifie également par récurrence.

3.2.3. Preuve de la correction

On peut maintenant justifier la correction de l'algorithme.

  1. Une fois la boucle terminée, d'après la condition d'arrêt, on a $m\lt b$.
  2. Si on note $m^{-}$ la valeur précédente de la variable m, on a $m = m^{-} - b$, et comme $m^{-}\geq b$, on a $m\geq 0$.
  3. D'après le premier invariant de boucle, on a $m\equiv n [b]$

Ces trois points impliquent que la valeur finale de $m$, retournée par la fonction, est bien le reste de la division euclidienne de $n$ par $b$.

3.3. Complexité de l'algorithme

On appelle complexité d'un algorithme le nombre d'opérations élémentaires exécutées en fonction de la taille des paramètres d'entrée.

Comme le quotient de la division euclidienne de $n$ par $b$ est $\lfloor \frac{n}{b}\rfloor$, d'après le second invariant de boucle, la boucle de la fonction mystere sera exécutée $\lfloor \frac{n}{b}\rfloor$ fois.

À l'intérieur de la boucle, on exécute une soustraction et une affectation. La fonction réalise donc au total $\lfloor \frac{n}{b}\rfloor$ soustractions et affectations et $\lfloor \frac{n}{b}\rfloor + 1$ comparaisons.

On dira que la complexité est en $O(\frac{n}{b})$ opérations élémentaires, c'est-à-dire que le nombre d'opérations élémentaires est majoré par $K \frac{n}{b}$, pour une certaine constante $K$.

4. Algorithme d'Euclide

On rappelle le principe de l'algorithme d'Euclide, permettant de calculer le pgcd de deux entiers $a\geq b$.

  • On effectue la division euclidienne de $a$ par $b$, qui s'écrit $a = bq + r$, avec $r\in\db{0,b-1}$.
  • On recommence avec les entiers $b$ et $r$ : on écrit la division euclidienne de $b$ par $r$.
  • On s'arrête quand une des divisions euclidiennes donne $0$. Le pgcd de $a$ et $b$ est alors le dernier reste non nul trouvé.

Pour calculer le pgcd de $38$ et $16$ :

  1. $38 = 2\times 16 + 6$
  2. $16 = 2\times 6 + 4$
  3. $6 = 1 \times 4 + 2$
  4. $4 = 2\times 2 + 0$

Le pgcd est donc $2$.

Soient $a,b$ deux entiers et $a = bq + r$ la division euclidienne de $a$ par $b$. On note $\mc D(a,b)$ l'ensemble des diviseurs communs à $a$ et $b$. Alors $\mc D(a,b) = \mc D(b,r)$. En particulier, $\op{pgcd}(a,b) = \op{pgcd}(b,r)$.

  1. ♥ Écrire une fonction pgcd_euclide qui prend en argument deux entiers naturels a,b et renvoie leur pgcd.
  2. Justifier la terminaison de votre algorithme en explicitant un invariant de boucle.

    En déduire la correction de l'algorithme.

  3. ★ On considère la suite de Fibonacci définie par $F_1 = 1$, $F_2 = 1$ et $\forall n\in\N^*, F_{n+2} = F_{n+1} + F_n$. Montrer par récurrence d'ordre $2$ que si $a\leq F_n$ et $b\leq F_n$ alors l'algorithme d'Euclide termine en au plus $n$ étapes.
  4. ★ En déduire que la complexité de l'algorithme d'Euclide est logarithmique en $\min(a,b)$.

Algorithme d'Euclide étendu

Dans le calcul du pgcd de $a,b$ par l'algorithme d'Euclide, notons $a_n, b_n$ les entiers considérés à l'issue de la $n$-ième itération de la boucle, définis par $$\begin{cases}a_0 = a \\ b_0 = b \end{cases}\quad \et \quad \forall n\in\N,\,\begin{cases}a_{n+1} = b_n \\ b_{n+1} = r_n \end{cases}, \text{ où } a_n = b_n q_n + r_n \text{ est la division euclidienne.}$$

On se convainc aisément que pour tout $n\in\N$, $a_n$ et $b_n$ sont des combinaisons linéaires entières des entiers $a,b$ d'origine, c'est-à-dire qu'il existe $u_n,v_n\in\Z$ tels que $b_n = u_n a + v_n b$. En particulier, à la dernière étape, $\op{pgcd}(a,b)$ est lui-même une combinaison linéaire de $a,b$ :

Soient $a,b\in\N$. Il existe un couple $(u,v)\in\Z^2$ tel que $$\op{pgcd}(a,b) = au + bv.$$

L'ensemble $\{au + bv\,;\, (u,v)\in\Z^2\}$ est l'ensemble des multiples de $\op{pgcd}(a,b)$.

Écrire une fonction euclide_etendu qui prend en argument deux entiers $a,b \geq 1$ et renvoie un couple de Bézout de $a,b$, c'est-à-dire un couple d'entiers relatifs $(u,v)$ tel que $au + bv = \op{pgcd}(a,b)$.

def eucl_etendu(a0,b0):
    # Inutile d'échanger a et b si b> a, l'algorithme fonctionne
    # quand même, avec une étape qui ne fait justement qu'échanger a
    # et b.

    a, b = a0, b0 # On utilise d'autres variables, pour clarifier
    ua, va = 1, 0 # a = uₐ a₀ + vₐ b₀, invariant qui sera maintenu par la suite
    ub, vb = 0, 1 # b = u_b a₀ + v_b b₀, invariant

    while b != 0:
        r, q = a % b, a // b
        # On a r = a − bq = (uₐ a₀ + vₐ b₀) − q (u_b a₀ + v_b b₀)
        # Donc r = a₀ (uₐ − q u_b) + b₀ (vₐ − q v_b)
        a, b = b, r
        ur, vr = ua - q * ub, va - q * vb
        ua, va = ub, vb
        ub, vb = ur, vr
    # À l'issue de la boucle, le a est égal au pgcd
    return (ua, va)

5. Exercices

Déterminer la somme des $1000$ premiers entiers impairs.

On considère la suite définie par $u_n = \frac{1}{n^2+5}$. Écrire un programme qui affiche tous les termes de la suite $(u_n)$ qui sont strictement supérieurs à $10^{-3}$. Donner de tête un ordre de grandeur de leur nombre.

On considère la suite définie par $u_0=1$ et la relation de récurrence $\forall n\geq 0,\, u_{n+1} = 3 u_n - 3$. Déterminer le premier indice $n$ pour lequel $|u_n|\geq 10^6$.2

Soit $(u_n)$ la suite définie par $u_0 = 1$ et $\forall n\in\N,\,u_{n+1} = 2u_n + 2$. Écrire une fonction nb_un qui prend en argument deux entiers $a,b$ et qui renvoie le nombre de termes de la suite qui vérifient $u_n \in [a,b]$.

On considère la suite définie par $u_0=0$ et $\forall n\geq 0,\,u_{n+1} = |u_n - n|$. Écrire une fonction prenant un entier n en argument et qui renvoie $\max\limits_{0\leq k\leq n} u_k$ : la valeur maximale prise par la suite $(u_n)$ lors des $n+1$ premiers indices. On utilisera la fonction abs pour calculer une valeur absolue.

On note $(F_n)$ la suite de Fibonacci. Écrire une fonction plus_petit_fib_sup qui prend en entier $m$ et renvoie le premier indice $n$ tel que $F_n\geq m$. On proposera une solution avec une complexité linéaire en $n$, c'est-à-dire avec un nombre d'opérations majoré par $Cn$, pour une constante $C$.

  1. Écrire une fonction plus_petit_diviseur_strict qui prend en argument un entier $n\geq 2$ et qui renvoie le plus petit diviseur de n différent de $1$.
  2. Écrire une fonction plus_grand_diviseur_strict qui prend en argument un entier $n\geq 2$ et qui renvoie le plus grand diviseur de n différent de lui-même.

Un entier parfait est un entier égal à la somme de ses diviseurs stricts. Écrire une fonction parfait qui détermine si un entier $n\geq 2$ est parfait, en renvoyant un booléen.

Écrire une fonction somme_chiffres qui calcule la somme des chiffres d'un entier en argument.

On n'utilisera pas la fonction str.

Soit $n\in\N^*$. On dit que $b$ est un inverse de $a$ modulo $n$ si $ab\equiv 1[n]$.

  1. Écrire une fonction naïve inv_naive qui prend en argument a et n et renvoie un inverse de $a$ modulo $n$.
  2. En utilisant l'algorithme Euclide étendu, écrire une version inv_mod rapide.

On considère la fonction de Syracuse $$S\colon \N^*\ra\N^* \quad n\mapsto \begin{cases} \frac{n}{2} \si n\text{ est pair} \\ 3n + 1 \sinon. \end{cases}.$$ Pour $u_0 \in\N^*$, la suite $(u_n)_{n\in\N}$ définie par $\forall n\in\N,\,u_{n+1} = f(u_n)$ et appelée une suite de Syracuse.

  1. Implémenter la fonction S.

    Il est conjecturé que quelle que soit la valeur de $u_0\in\N^*$, il existe un entier $n_0\in\N$ tel que $u_{n_0} = 1$. S'il existe, on note $h(u_0)$ le plus petit tel entier. On suppose dans la suite que cette conjecture est correcte.

  2. Écrire une fonction h prenant un argument un entier naturel non nul u0 et qui renvoie $h(u_0)$.
  3. Écrire une fonction M qui prend en argument l'entier u0 et qui renvoie la valeur maximale des termes de la suite $(u_n)$.

Notes de bas de page:

1

Utiliser une variable intermédiaire, à incrémenter

2

On trouve 14