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

Author: Samy Jaziri

Created: 2024-05-31 ven. 15:42