Min, max, somme, moyenne « à la main »
Pour trouver le plus petit ou le plus grand d'une liste sans fonction toute faite, on initialise avec le premier élément, puis on compare chaque suivant. La somme, elle, s'initialise à 0 (accumulateur), et la moyenne = somme / nombre d'éléments.
# min / max / somme / moyenne "a la main"
notes = [12, 8, 15, 9, 17]
mini = notes[0]
maxi = notes[0]
somme = 0
for note in notes:
if note < mini:
mini = note
if note > maxi:
maxi = note
somme = somme + note
moyenne = somme / len(notes)
print("min :", mini)
print("max :", maxi)
print("somme :", somme)
print("moyenne :", moyenne)
Sortiemin : 8
max : 17
somme : 61
moyenne : 12.2
Coût : un seul parcours de la liste, soit n − 1 comparaisons pour le min (idem pour le max).
Piège de l'initialisation. Ne jamais initialiser le min (ou le max) à 0 : sur une liste de températures négatives, mini = 0 resterait 0 alors qu'aucune valeur ne vaut 0. On initialise toujours avec le premier élément de la liste (notes[0]), une valeur qui existe vraiment.
PGCD par l'algorithme d'Euclide
Le plus grand commun diviseur de deux entiers se calcule par l'algorithme d'Euclide : on remplace le couple (a, b) par (b, a % b) tant que b n'est pas nul. Quand b atteint 0, a contient le PGCD.
# PGCD par l'algorithme d'Euclide (modulo)
a = 48
b = 36
while b != 0:
a, b = b, a % b
print("PGCD(48, 36) =", a)
import math
print("verif :", math.gcd(48, 36))
SortiePGCD(48, 36) = 12
verif : 12
Deux versions. La version par modulo ci-dessus (a, b = b, a % b) est la plus rapide. Il existe une version historique par soustractions : tant que a ≠ b, on retire le plus petit au plus grand. Elle donne le même résultat mais fait beaucoup plus de tours. La récursivité (fonction qui s'appelle elle-même) est hors programme U22 : on reste en itératif.
Test de primalité
Un nombre est premier s'il n'est divisible que par 1 et par lui-même (et s'il est ≥ 2). Version naïve : on teste tous les diviseurs de 2 à n − 1. Version améliorée : il suffit de tester jusqu'à la racine carrée, c'est-à-dire tant que i × i ≤ n (au-delà, un diviseur aurait déjà été trouvé en dessous).
# Test de primalite ameliore : i*i <= n
def est_premier(n):
if n < 2:
return False
i = 2
while i * i <= n:
if n % i == 0:
return False
i = i + 1
return True
for n in [1, 2, 7, 9, 13, 15, 29]:
print(n, est_premier(n))
Sortie1 False
2 True
7 True
9 False
13 True
15 False
29 True
Coût : la version naïve fait jusqu'à n − 2 tests de divisibilité, la version améliorée seulement jusqu'à √n (bien moins pour les grands nombres).
Tri à bulles
Le tri à bulles compare des éléments voisins et les échange s'ils sont dans le mauvais ordre. À chaque passe, le plus grand élément « remonte » à sa place finale à droite. On observe l'état de la liste après chaque passe :
# Tri a bulles : etat de la liste apres CHAQUE passe
t = [5, 3, 8, 1, 2]
n = len(t)
for passe in range(n - 1):
for i in range(n - 1 - passe):
if t[i] > t[i + 1]:
t[i], t[i + 1] = t[i + 1], t[i]
print("apres passe", passe + 1, ":", t)
Sortieapres passe 1 : [3, 5, 1, 2, 8]
apres passe 2 : [3, 1, 2, 5, 8]
apres passe 3 : [1, 2, 3, 5, 8]
apres passe 4 : [1, 2, 3, 5, 8]
Coût : de l'ordre de n² comparaisons (deux boucles imbriquées).
Tri par sélection
Le tri par sélection cherche le minimum du reste de la liste et l'échange avec le premier élément non trié. À chaque passe, une case de plus est définitivement placée à gauche :
# Tri par selection : etat de la liste apres CHAQUE passe
t = [5, 3, 8, 1, 2]
n = len(t)
for i in range(n - 1):
imin = i
for j in range(i + 1, n):
if t[j] < t[imin]:
imin = j
t[i], t[imin] = t[imin], t[i]
print("apres passe", i + 1, ":", t)
Sortieapres passe 1 : [1, 3, 8, 5, 2]
apres passe 2 : [1, 2, 8, 5, 3]
apres passe 3 : [1, 2, 3, 5, 8]
apres passe 4 : [1, 2, 3, 5, 8]
Coût : également de l'ordre de n² comparaisons, mais avec beaucoup moins d'échanges que le tri à bulles (un seul par passe).
Recherche séquentielle vs recherche dichotomique
La recherche séquentielle parcourt la liste du début à la fin jusqu'à trouver la cible : simple, mais lente sur de grandes listes (jusqu'à n comparaisons).
# Recherche sequentielle
t = [7, 3, 9, 1, 4, 8]
cible = 4
trouve = -1
for i in range(len(t)):
if t[i] == cible:
trouve = i
break
print("indice :", trouve)
La recherche dichotomique exige un tableau trié. On regarde l'élément du milieu : si c'est la cible, c'est fini ; si la cible est plus grande, on cherche à droite (on avance debut) ; sinon à gauche (on recule fin). À chaque étape, on divise l'intervalle par deux.
# Recherche dichotomique (tableau TRIE) avec trace des bornes
t = [2, 5, 8, 12, 16, 23, 38, 45, 56, 72, 91]
cible = 45
debut = 0
fin = len(t) - 1
trouve = -1
while debut <= fin:
milieu = (debut + fin) // 2
print("debut =", debut, "fin =", fin, "milieu =", milieu, "-> t[milieu] =", t[milieu])
if t[milieu] == cible:
trouve = milieu
break
elif t[milieu] < cible:
debut = milieu + 1
else:
fin = milieu - 1
print("indice trouve :", trouve)
Sortiedebut = 0 fin = 10 milieu = 5 -> t[milieu] = 23
debut = 6 fin = 10 milieu = 8 -> t[milieu] = 56
debut = 6 fin = 7 milieu = 6 -> t[milieu] = 38
debut = 7 fin = 7 milieu = 7 -> t[milieu] = 45
indice trouve : 7
La force de la dichotomie. Sur 11 éléments, elle a trouvé la cible en 4 comparaisons au lieu de 8 pour la recherche séquentielle. Sur une liste de 1000 éléments, elle en fait au plus ≈ 10 (log₂ 1000), contre 1000 pour la séquentielle. Condition obligatoire : le tableau doit être trié.