Algorithmes Classiques
Table of Contents
1. Numériques
1.1. Suite de Fibonacci
La suite de Fibonacci est définie par récurrence double :
\[ F_0 = 0 ,\, F_1 = 1 ,\, F_{n+2} = F_{n+1} + F_{n} \]
Entrées : un entier \(n \geq 0\)
Sortie : \(F_n\)
def fibonacci(n) : f = 0 if n > 0 : # Si n = 0, il n'y a aucun calcul à faire # Rang 1 f = 1 f_prev = 0 rang = 1 # Calcul du rang n en calculant chaque rang i successivement while rang != n : f_suivant = f + f_prev f_prev = f f = f_suivant rang = rang + 1 return f
1.2. Suite de Syracuse
La suite de Syracuse est définie par récurrence :
\[S_{{n+1}}={\begin{cases}{\dfrac {S_{n}}{2}}&{\mbox{si }}S_{n}{\mbox{ est pair,}}\\3S_{n}+1&{\mbox{si }}S_{n}{\mbox{ est impair.}}\end{cases}} \]
Entrées : un entier \(s \geq 1\)
Sortie : Le premier entier \(n \geq 0\) tel que \(S_n = 1\)
def syracuse(s0) : # Rang 0 s = s0 rang = 0 # Calcul du rang suivant dans s, jusqu'à ce que s == 1 while s != 1 : rang = rang + 1 if s % 2 == 0 : s = s // 2 else : s = 3 * s + 1 return rang
2. Séquences
2.1. Somme des éléments d’une liste
Entrées : une liste d’entiers relatifs.
Sortie : la somme des entiers de la liste.
def somme_liste(l) : somme = 0 for x in l : somme = somme + x return somme
Complexité : \(\O(\symtt{len}(l))\).
Invariant : On note \(i\) l’indice de \(x\) dans \(l\).
\[I : \symtt{somme} = \symtt{sum(L[:i])} = \sum_{k = 0}^{i-1} \symtt{L[k]} \]
2.2. Maximum
Entrées : une liste d’entiers L
Sortie : l’indice du maximum de la liste
def imax(l) : if len(l) == 0 : return -1 i_max = 0 maxi = l[0] for i in range(len(l)) : if l[i] > maxi : maxi = l[i] i_max = i return i_max
Complexité : \(\O(\symtt{len}(l))\).
Invariant : \(\symtt{maxi} = \symtt{max(L[:i])}\) et \(\symtt{L[i\_max]} = \symtt{maxi}\)
2.3. Recherche linéaire - version while
Entrées : Liste l et un élément x
Sortie : L’indice de la première occurrence de x dans l, ou -1 si x n’est pas dans l
def recherche_lineaire(l, x) : i_x = -1 i = 0 n = len(l) while i < n and i_x == -1 : if l[i] == x : i_x = i i = i + 1 return i_x
Complexité : \(\O(\symtt{len}(l))\).
Invariant : En posant \(\min(\emptyset) = -1\),
\[\symtt{i\_x} = \min\{ 0 \leq k < i \, | \, \symtt{L[k] = x}\}\]
2.4. Recherche linéaire - version for
Entrées : Liste l et un élément x
Sortie : L’indice de la première occurrence de x dans l, ou -1 si x n’est pas dans l
def recherche_lineaire(l, x) : for i in range(len(l)) : if l[i] == x : return i return -1
Complexité : \(\O(\symtt{len}(l))\).
Invariant : \(\symtt{x} \not\in \symtt{l[:i]}\)
2.5. Second Minimum
Entrées : Liste d’entiers positifs, disjoints, l de taille supérieure à 2
Sortie : Le second minimum de l
def deuxieme_minimum(l) : if l[0] < l[1] : m = l[0] second_m = l[1] else : m = l[1] second_m = l[0] for x in l[2:] : if x < m : second_m = m m = x elif x < second_m : second_m = x return second_m
Complexité : \(\O(\symtt{len}(l))\).
Invariant : On note \(i\) l’indice de \(x\).
\[ \symtt{m} = \symtt{min(L[:i])} \text{ et } \symtt{second_m} =
\symtt{min(L[:i]} \setminus \{ \symtt{m} \} ) \]
3. Double Boucles
3.1. Algorithme de la Fenêtre Glissante
Entrées : s, w deux chaînes de caractères non vides.
Sortie : i >= 0, le plus petit indice de s tel que w est une sous-chaîne de s
à la position i.
def rechercher(s,w) : n = len(s) m = len(w) # Recherche linéaire (for) d'une fenetre qui contient w for i in range(0,n-m+1) : # Recherche linéaire (while) d'un caractère différent # dans la fenêtre. j = 0 i_diff = -1 while j < m : if w[j] != s[i+j] : i_diff = j j += 1 # Si on n'a pas trouvé de caractère dans la fenêtre # c'est la bonne fenêtre. if i_diff == -1 : return i return -1
Complexité : \(\O(\symtt{len}(s)\,\symtt{len}(w))\).
Invariants : Ce sont les invariants des recherches linéaires.
3.2. Création d’une matrice \(n \times m\)
Entrées : n, m deux entiers positifs non nuls
Sortie : Une matrice de dimension \(n \times m\) remplie de None
def matrice(n,m) : M = [] for i in range(n) : L = [ None ] * m M.append(L) return M
ou
def matrice(n,m) : return [ [ None ] * m for i in range(n) ]
Complexité : \(\O(nm)\).
3.3. Copie d’une matrice
Entrées : m une matrice \(p\times q\)
Sortie : Une copie de m
def copie(m) : m_copie = [] for l in m : # pour chaque ligne de m l_copie = [] for x in l : # pour chaque élément de la ligne l_copie.append(x) m_copie.append(l_copie) return m_copie
ou
def copie(M) : return [ [ x for x in L ] for L in M ]
Complexité : \(\O(pq)\).
3.4. Histogramme d’une image
Entrées : m
une matrice \(p \times q\), d’entiers entre 0
et 255
Sortie : h
, l’histogramme de m
. i.e. une liste de taille 256 telle que
h[i]
soit le nombre d’occurrences de i
dans m
.
def histogramme(m) : h = [0]*256 for l in m : # Pour chaque ligne de M for x in l : # Pour chaque élément de la ligne h[x] += 1 return h
Complexité : \(\O(pq)\).
4. Algorithmes Glouton
4.1. Rendu de monnaie
On suppose que le système monétaire choisi permet toujours de rendre la monnaie.
Entrées : Un liste, L
, de billets (entiers) dans l’ordre croissant et un montant, m
, (entier).
Sortie : Une liste de billet de L
dont la somme vaut m
de taille minimale.
def rendu(L,m) : R = [] reste = m i = len(L)-1 # i représente le plus gros billet éventuellement utilisable. while reste > 0 : if L[i] <= reste : # si on peux utiliser le plus gros billet, on l'utilise R.append(L[i]) reste = reste - L[i] else : # sinon on considère le billet d'en dessous. i = i-1 return R
Complexité : \(\O(\symtt{len(L) + reste}\)\).
Invariants : \(\symtt{reste + sum(R) = m}\).
Variant : \(\symtt{reste}+i\)
5. Algorithmes Dichotomiques
5.1. Exponentiation rapide
Entrées : \(a\) un entier et \(b\) un entier positifs.
Sortie : \(a^b\)
def exponentiation_rapide(a,b) : r = 1 u = a v = b while v > 0 : if v % 2 == 1 : r = r * u u = u * u v = v // 2 return r
Complexité : \(\O(\log_{2}(b))\).
Invariants : \(ru^{v} = a^{b}\).
Variant : \(\symtt{v}\)
5.2. Recherche dichotomique dans une liste triée
Entrées : Une liste d’entiers triés dans l’ordre croissant et un entier x
.
Sortie : -1
si x
n’est pas dans L
; l’indice de x
dans L
sinon.
def recherche_dichotomique(L,x) : debut = 0 fin = len(L) while fin - debut > 0 : milieu = (debut + fin) // 2 if L[milieu] == x : return milieu elif L[milieu] < x : debut = milieu + 1 else : fin = milieu return -1
Complexité : \(\O(\log_{2}(\symtt{len}(L)))\).
Invariants : \(x \not\in \symtt{L[:debut]}\) et \(x \not\in \symtt{L[fin:]}\).
Variant : \(\symtt{fin-debut}\)
6. Algorithmes récursifs
6.1. Somme
Entrées : une liste d’entiers.
Sortie : la somme des éléments de la liste.
On défini \(\symtt{somme\_rec(L,i)} = \sum_{\symtt{k}=0}^{\symtt{i}-1} \symtt{L[k]}\) par la formule de récurrence suivante : \[ \symtt{somme\_rec(L,i)} = \begin{cases} 0 & \text{si } \symtt{i} = 0 \\ \symtt{somme\_rec(L,i-1)} + \symtt{L[i-1]} & sinon \end{cases} \]
def somme_rec(L, i) : """ Entrées : L une liste d'entier, $0 \leq i \leq \symtt{len}(L)$. Sorties : \symtt{sum(L[:i]) """ if i == 0 : return 0 s = somme_rec(L,i-1) return s + L[i-1] def somme(L) : return somme_rec(L,len(L))
6.2. Maximum
Entrées : une liste d’entiers relatifs.
Sortie : le maximum de la liste
On défini \(\symtt{maximum\_rec(L,i)} = \symtt{max(L[:i]})\) par la formule de récurrence suivante : \[ \symtt{maximum\_rec(L,i)} = \begin{cases} -\infty & \text{si } \symtt{i} = 0 \\ max(\symtt{maximum\_rec(L,i-1)}, \symtt{L[i-1]}) & sinon \end{cases} \]
def maximum_rec(L, i) : """ Entrées : L une liste d'entier, $0 \leq i \leq \symtt{len}(L)$. Sorties : \symtt{max(L[:i]) """ if i == 0 : return -float('inf') m = maximum_rec(L,i-1) if m > L[i-1] : return m return L[i-1] def maximum(L) : return maximum_rec(L,len(L))
6.3. Exponentiation rapide
Entrées : \(a\) un entier et \(b\) un entiers positifs.
Sortie : \(a^b\)
\[ a^{b} = \begin{cases} 1 & \text{si } b = 0 \\ (a^{2})^{k} & \text{sinon si } b = 2\,k \\ a\,(a^{2})^{k} & \text{sinon, (i.e. } b = 2\,k+1) \end{cases}\]
def exp(a,b) : if b == 0 : return 1 elif b % 2 == 0 : return exp(a * a , b // 2) else : return a * exp( a * a, b // 2)
6.4. Tri Fusion
Entrées : Une liste d’entiers
Sortie : Une copie de cette liste triée dans l’ordre croissant.
On suppose qu’il existe une fonction ayant la spécification suivante :
def fusion(l1 : list[int], l2 : list[int]) -> list[int] : """ Entrées : deux listes d'entiers triés. Sortie: une liste triée contenant les éléments des deux listes en entrée. """
\[ \symtt{tri\_fusion(L)} = \begin{cases} \symtt{[]} & \text{si } \symtt{L = []} \\ \symtt{fusion(tri\_fusion(L[:n//2]),tri\_fusion(L[n//2:]))} & \text{sinon} \end{cases} \]
def tri_fusion(l) : n = len(l) if n < 2 : return l[:] l1 = tri_fusion(l[:n//2]) l2 = tri_fusion(l[n//2:]) return fusion(l1,l2)
Complexité : \(\O(n\,\log_2(n))\)
6.5. Binaire
Entrées : Un entier n
.
Sortie : Une chaîne donnant la représentation binaire de n
.
\[ \symtt{bin(n)} = \begin{cases} \text{""} & \text{si } \symtt{n = 0} \\ \symtt{bin(n//2)} + \text{'0'} & \text{si n est pair} \\ \symtt{bin(n//2)} + \text{'1'} & \text{si n est impair} \end{cases} \]
def bin(n) : if n == 0 : return "" bin_quot = bin(n//2) if n % 2 == 0 : return bin_quot + "0" return bin_quot + "1"
Complexité : \(\O(\log_{2}(\symtt{n}))\).
6.6. Binaire complément à deux sur n
bits
Entrées : Deux entiers n
et k
tel que \(\symtt{n} \in \ints{-2^{\symtt{k}-1}}{2^{\symtt{k}-1}-1}\).
Sortie : Une liste donnant la représentation binaire de n
en complément à 2
sur k
bits.
def bin2(n, k) : if n == 0 : return "0"*k if n < 0 : return bin2(2**k + n, k) bin_quot = bin2(n//2) if n % 2 == 0 : return bin_quot + "0" return bin_quot + "1"
Complexité : \(\O(\log_{2}(\symtt{n}))\).
7. Dictionnaires
7.1. Nombre d’occurrences
Entrées : Une chaîne de caractère s
.
Sortie : Le nombre d’occurrence de chaque caractère dans un dictionnaire.
def nb_occ(s) : d = {} for c in s : if c in d : d[c] += 1 else : d[c] = 1 return d
Complexité : \(\O(\symtt{len(s)})\).
Invariants : On note \(i\) l’indice de \(c\) dans \(s\). d
est toujours le nombre
d’occurrence de chaque caractère de s[:i]
.
8. Algorithmique des graphes
8.1. Degrés dans un graphe non orienté
Entrées : la liste d’adjacence d’un graphe orienté à \(n\) sommets et \(m\) arêtes.
Sortie : la liste des degrés de chaque sommets.
def degre(g) : deg = [ len(g[s]) for s in range(len(g)) ] for s in range(len(g)) : for v in g[s] : deg[v] += 1 return deg
Complexité : \(\O(n+m)\)
8.2. Parcours en largeur
Entrées : la liste d’adjacence d’un graphe à \(n\) sommets et \(m\) arêtes,
source
un sommet de g.
Sortie : ?? dépend du problème
Complexité : \(\O(n+m)\)
from collections import deque # Utilisation d'une file : fifo pour avoir la bonne complexité. def parcours_largeur(g, source) : n = len(g) marquage = [False] * n fifo = deque() fifo.append(source) marquage[source] = True # Traiter source ... while len(fifo) > 0 : s = fifo.popleft() for v in g[s] : if not marquage[v] : fifo.append(v) marquage[v] = True # Traiter v ... return # Quelque chose peut-être
8.3. Parcours en profondeur
Entrées : la liste d’adjacence d’un graphe à \(n\) sommets et \(m\) arêtes,
source
un sommet de g.
Sortie : ?? dépend du problème
Complexité : \(\O(n+m)\)
# Utilisation d'une pile : lifo pour avoir la bonne complexité. def parcours_profondeur(g, source) : n = len(g) marquage = [False] * n lifo = [] lifo.append(source) while len(lifo) > 0 : s = lifo.pop() if not marquage[s] : marquage[s] = True # Traiter s ... for v in g[s] : if not marquage[v] : lifo.append(v) return # Quelque chose peut-être
La version récursive est meilleure.
def visiter(g, s, marquage) : marquage[s] = True # Traitement de source for v in g[s] : if marquage[v] != True : visiter(g, v, marquage) # ou ; r = visiter(g, v, marquage) return # Quelque chose peut-être def parcours_profondeur(g, source) : n = len(g) marquage = [False] * n visiter(g,source,marquage) # ou ; r = visiter(g, source, marquage) return # Quelque chose peut-être