Algorithmes Classiques
Table of Contents
1. Numériques
1.1. Algorithme d’Euclide
Entrées : Deux entiers \(a\) et \(b\)
Sortie : \(\mathtt{pgcd}(a,b)\)
def euclide(a, b) : while a != b : if a > b : a = a - b else : b = b - a return a
1.2. Test de Primalité
Entrées : un entier \(n \geq 2\)
Sortie : True
ssi \(n\) est premier
def premier(n) : p = True d = 2 # Recherche d'un diviseur entre 2 et la racine carré de n while d * d <= n : if n % d == 0 : p = False d = d + 1 return p
1.3. 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 f_suivant = 1 rang = 0 # Calcul du rang n en calculant chaque rang i successivement while rang != n : f_suivant2 = f + f_suivant f = f_suivant f_suivant = f_suivant2 rang = rang + 1 return f
1.4. 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 : if s % 2 == 0 : s = s // 2 else : s = 3 * s + 1 rang = rang + 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
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(1,len(l)) : if l[i] > maxi : maxi = l[i] i_max = i return i_max
2.3. Recherche linéaire - version while
Entrées : Liste l et un objet 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) : n = len(l) i_x = -1 i = 0 while i < n and i_x == -1 : if l[i] == x : i_x = i i = i + 1 return i_x
2.4. Recherche linéaire - version for
Entrées : Liste l et un objet 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
2.5. Comptage
Entrées : Liste l et un objet x
Sortie : Le nombre de fois que x apparaît dans l
def compter(l, x) : c = 0 for e in l : if e == x : c = c + 1 return c
2.6. 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 i in range(2,len(l)) : x = l[i] if x < m : second_m = m m = x elif x < second_m : second_m = x return second_m
3. Boucles imbriquées
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
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) ]
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 ]
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
4. Algorithmes Dichotomiques
4.1. 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
5. Algorithmes récursifs
5.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))
5.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))
5.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)
5.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. Dictionnaires
6.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]
.
7. Algorithmique des graphes
7.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)\)
7.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
7.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
7.4. Dijkstra
Entrées : la liste d’adjacence d’un graphe pondéré orienté à \(n\) sommets et
\(m\) arêtes, source un sommet du graphe.
Sortie : la liste des distances minimales de source à chaque autre sommet du
graphe et des relations de parentés qui permettent d’attendre ces distances.
On suppose qu’on dispose d’une structure de donnée file de priorité qui supporte les opérations suivantes :
creer()
: renvoie une file de priorité vide.est_vide(file)
: renvoie vrai si la file est vide.ajoute(file, x, prio)
: ajoute l’élémentx
dans lafile
avec prioritéprio
s’il n’y est pas déjà. Six
est déjà dans la file on change seulement sa priorité.enleve_min(file)
: enleve de la file et renvoie l’élément de priorité minimale.
def dijkstra(g,source) : n = len(g) # Structures distances = [float(inf)]*n parents = [None]*n file_prio = creer() # Initialisation distances[source] = 0 ajoute(file_prio, source, 0) # Algorithme while not est_vide(file_prio) : # On récupère le sommet non visité à distance minimale s = enleve_min(file_prio) # On relache tous les voisins for voisin, poids in g[s] : distance_par_s = distances[s] + poids if distance_par_s < distances[voisins] : distances[voisins] = distance_par_s parents[voisin] = s ajoute(file_prio, voisin, distances[voisins]) return distances, parents
On peut trouver une implémentation de la structure de file de priorité telle
que les fonctions creer
et est_vide
soient en \(\O(1)\) et les fonctions
enleve_min
et ajoute
soient en \(\O(\log(n))\). La complexité finale est alors
en \(\O((n+m)\log(n))\).
Voici une autre implémentation utilisant un marquage par dictionnaire avec les
outils du programme, et la fonction d.pop(key)
qui permet d’enlever une clé
d’un dictionnaire :
def creer() : # $\O(1)$ return {} def est_vide(file_prio) : # $\O(1)$ return len(file_prio) == 0 def ajoute(file_prio, x, prio) : # $\O(1)$ file_prio[x] = prio def enleve_min(file_prio) : # $\O(\symtt{len(file_prio)})$ min_key, min_prio = None, float('inf') for x, prio in file_prio.items() : if prio < min_prio : min_key, min_prio = x, prio file_prio.pop(min_key) return min_key
La complexité finale est alors en \(\O(n^2+m) = \O(n^{2})\) car \(m \leq n^{2}\).
7.5. \(A^*\)
Entrées :
- la liste d’adjacence d’un graphe pondéré orienté à \(n\) sommets et \(m\) arêtes,
- source un sommet du graphe,
- destination un sommet du graphe,
- heuristique une fonction qui prend en entrée
g
,source
,destination
et un sommets
deg
et qui renvoie une valeur entière.
Sortie : une liste de distances de source à chaque autre sommet du graphe et
des relations de parentés qui permettent d’attendre ces distances.
Si h
ne surestime jamais la distance de s
à destination
, les distances
sont les plus petites. Si h = 0
, c’est l’algorithme de Dijkstra.
Complexité : La même que Dijkstra si h
est en \(\O(1)\). Mais en pratique on
atteindra la destination plus rapidement.
def astar(g, heuristique, source, destination) : n = len(g) # Structures distances = [float(inf)]*n parents = [None]*n file_prio = creer() # Initialisation distances[source] = 0 ajoute(file_prio, source, 0) # Algorithme while not est_vide(file_prio) : # On récupère le sommet non visité à distance minimale s = enleve_min(file_prio) # ***** Une différence est ici ***** if s == destination : break # On relache tous les voisins for voisin, poids in g[s] : distance_par_s = distances[s] + poids if distance_par_s < distances[voisins] : distances[voisins] = distance_par_s parents[voisin] = s ajoute(file_prio, voisin, # ***** Une autre différence est ici ***** distances[voisins]+h(g,source,destination,voisin)) return distances, parents