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 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) : if n == 0 : return 0 f_prev, f = 0, 1 for rang in range(1,n) : f_prev, f = f, f + f_prev 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 : 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
Complexité : \(\O(\symtt{len}(l))\)
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
Complexité : \(\O(\symtt{len}(l))\)
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
2.3. Moyenne
Entrées : une liste d’entiers L
Sortie : la moyenne des valeurs de la liste l
Complexité : \(\O(\symtt{len}(l))\)
def moyenne(l) : somme = 0 for x in l : somme = somme + x return somme / len(l)
2.4. Recherche linéaire - version while
Entrées : Liste l et un élément x
Sortie : L’indice de la première occurence de x dans l, ou -1 si x n’est pas
dans l
Complexité : \(\O(\symtt{len}(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
2.5. Recherche linéaire - version for dans fonction
Entrées : Liste l et un élément x
Sortie : L’indice de la première occurence de x dans l, ou -1 si x n’est pas dans l
Complexité : \(\O(\symtt{len}(l))\)
def recherche_lineaire(l, x) : for i in range(len(l)) : if l[i] == x : return i return -1
2.6. Comptage
Entrées : Liste l et un élément x
Sortie : Le nombre de fois que x apparaît dans l
Complexité : \(\O(\symtt{len}(l))\)
def compter(l, x) : c = 0 for e in l : if e == x : c = c + 1 return c
2.7. Second Minimum
Entrées : Liste d’entiers positifs, disjoints, l de taille supérieure à 2
Sortie : Le second minimum de l
Complexité : \(\O(\symtt{len}(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
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.
Complexité : \(\O(\symtt{len}(s)\,\symtt{len}(w))\)
def rechercher(s,w) : for i in range(0,len(s)-len(w)+1) : j = 0 while j < len(w) and w[j] == s[i+j] : j += 1 if j == len(w) : return i return -1
3.2. Création d’une matrice
Entrées : n, m deux entiers positifs non nuls
Sortie : Une matrice de dimension \(n \times m\) remplie de None
Complexité : \(\O(\symtt{len}(n \times m))\)
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 \(n \times m\)
Sortie : Une copie de M
Complexité : \(\O(n \times 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 ]
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
5. Algorithmes Dichotomiques
5.1. Exponentiation rapide
Entrées : \(a\) un entier et \(b\) un entiers positifs.
Sortie : \(a^b\)
Complexité : \(\O(\log_2(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
5.2. Recherche de 0 d’une fonction
Entrées : \(a < b\) deux flottants, \(e > 0\) un flottant et \(f\) une fonction continue sur \([a,b]\) telle que \(f(a)f(b) < 0\)
Sortie : un flottant \(x\) telle \(\exists x - e \leq c \leq x + e, f(c) = 0\)
Complexité : \(\O(\log_2(\frac {b-a} {e}))\)
def recherche_dichotomique_zero(a,b,e,f) : debut = a fin = b while fin - debut > e : milieu = (debut + fin ) / 2 if f(milieu) == 0 : return milieu elif f(milieu) * f(debut) > 0 : debut = milieu else : fin = milieu return debut
5.3. Recherche dichotomique dans une liste triée
Entrées : Une liste d’entiers triés dans l’ordre croissant et un entier x
.
Sortie : L’indice de x
dans L
, -1
si x
n’est pas dans L
Complexité : \(\O(\log_2(\symtt{len}(L)))\)
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 else : fin = milieu return -1
6. Algorithmes récursifs
6.1. Maximum
Entrées : une liste d’entiers positifs.
Sortie : le maximum de la liste
Complexité : \(\O(\symtt{len}(L))\)
def maximum_rec(L, i) : if i == 0 : return -float('inf') b = maximum(L,i-1) if L[i-1] < b : return b return L[i-1] def maximum(L) : return maximum_rec(L, len(L))
6.2. Somme
Entrées : une liste d’entiers relatifs
Sortie : la somme des entiers de la liste
Complexité : \(\O(\symtt{len}(L))\)
def somme_rec(L, i) : if i == 0 : return 0 return L[i-1] + somme(L, i-1) def somme(L) : return somme_rec(L,len(L))
6.3. Exponentiation rapide
Entrées : \(a\) un entier et \(b\) un entiers positifs.
Sortie : \(a^b\)
Complexité : \(\O(\log_2(b))\)
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. Recherche Dichotomique
Entrées : Une liste d’entiers triés dans l’ordre croissant et un entier x
.
Sortie : L’indice de x
dans L
, -1
si x
n’est pas dans L
Complexité : \(\O(\log_2(\symtt{len}(L)))\)
def recherche_dichotomique_rec(L,x,d,f) : if d == f : return -1 m = (d+f) // 2 if L[m] == x : return m elif L[m] > x : return recherche_dichotomique(L,x,m,f) return recherche_dichotomique(L,x,m+1,f) def recherche_dichotomique(L,x) : return recherche_dichotomique(L,x,0,len(L))
6.5. Tri Fusion
Entrées : Une liste d’entiers de taille \(n\)
Sortie : Une copie de cette liste triée dans l’ordre croissant.
Complexité : \(\O(n\,\log_2(n))\)
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. Complexité : O(len(l1)+len(l2)) """
def tri_fusion_rec(l,d,f) : if f-d < 2 : return l[d:f] m = (d+f)//2 l1 = tri_fusion(l,d,m) l2 = tri_fusion(l,m,f) return fusion(l1,l2) def tri_fusion(l) : return tri_fusion_rec(l,0,len(l))
7. Algorithmique des graphes
7.1. Degrés dans un graphe non orienté
Entrées : la liste d’adjacence d’un graphe à \(n\) sommets et \(m\) arêtes.
Sortie : la liste des degrés de chaque sommets.
Complexité : \(\O(n+m)\)
def degre(g) : deg = [ len(g[s]) for s in g ] for s in g : for v in g[s] : deg[v] += 1 return deg
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 while len(deque) > 0 : s = fifo.popleft() # Traiter s ... for v in g[s] : if not marquage[v] : fifo.append(v) marquage[v] = True 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(deque) > 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 parcours_profondeur(g, source, marquage) : marquage[source] = True # Traitement de source for v in g[source] : if marquage[v] != True : parcours_profondeur(g, v, marquage) # ou ; r = parcours_profondeur(g, v, marquage) return # Quelque chose peut-être
7.4. Connexité
Entrées : la liste d’adjacence d’un graphe à \(n\) sommets et \(m\) arêtes
Sortie : True
si le graphe est connexe, False
sinon.
Complexité : \(\O(n+m)\)
Avec un parcours en profondeur par exemple.
def parcours(g, source, marquage) : marquage[source] = True for v in g[source] : if marquage[v] != True : parcours_profondeur(g, v, marquage) return def connexite(g) : if len(g) == 0 : return True source = g[0] marquage = [False]*len(g) parcours(g,source,marquage) for m in marquage : if not m : return False return True
7.5. 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\log(n)+m)\).
Voici une autre implémentation utilisant un marquage par dictionnaire avec les outils du programme :
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 del file_prio[min_key] return min_key
La complexité finale est alors en \(\O(n^2+m) = \O(n^{2})\) car \(m \leq n^{2}\).
7.6. \(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
8. Dictionnaires
8.1. Nombre d’occurrences
Entrées : Une chaîne de caractère s
.
Sortie : Le nombre d’occurence de chaque caractère dans un dictionnaire.
Complexité : \(\O(\symtt{len(s)})\)
def nb_occ(s) : d = {} for c in s : if c not in d : d[c] = 0 d[c] += 1 return d
9. Programmation Dynamique
9.1. Ordonnancement
Entrées : Une liste L
d’entiers positifs de longueur n
.
Sortie : La différence de charge minimale entre deux listes L1
et L2
se
repartissant les éléments de L
.
Complexité recherche exhaustive : \(\O(n\,2^{n})\).
Complexité programme dynamique : \(\O(n*\symtt{sum(L)})\).
Sous-problèmes : \(\symtt{ordo(L,i,\Delta)}\) correspond à la différence de charge
minimale sachant que les éléments L[i:]
sont déjà repartis avec une différence
de \Delta
. Pour \(0 \leq i \leq \symtt{len(L)}\) et \(0 \leq \Delta \leq \symtt{sum(L)}\).
Formule : \[ \symtt{ordo(L,i,}\Delta) = \begin{cases} \Delta & \text{si } i = 0 \\ \min(\symtt{ordo(L,i-1,}\Delta+\symtt{L[i-1]}), \symtt{ordo(L,i-1,}|\Delta-\symtt{L[i-1]}|) & \text{sinon} \end{cases} \] Le problème \(\symtt{ordo(L)}\) correspond au sous-problème \(\symtt{ordo(L,0,0)}\).
def ordo_memo(L,i,delta,memo) : if i == 0 : memo[(i,delta)] = delta return delta_1 = delta + L[i-1] delta_2 = abs(delta - L[i-1]) if (i-1, delta_1) not in memo : ordo_memo(L,i-1,delta_1,memo) if (i-1, delta_2) not in memo : ordo_memo(L,i-1,delta_2,memo) memo[(i,delta)] = min(memo[(i-1,delta_1)], memo[(i-1,delta_2)]) return def ordo(L) : memo = {} ordo_memo(L,len(L),0,memo) return memo[(len(L),0)]
9.2. Floyd-Warshall
Entrées : Un graphe pondéré orienté g
à n
sommets et m
arrêtes,
représenté par sa matrice d’adjacence.
Sortie : La matrice des distances minimales entre chaque paire de sommets.
Complexité recherche exhaustive : \(\O(n\,n!)\).
Complexité programme dynamique : \(\O(n^{3})\).
Complexité Dijkstra entre chaque paire (s,d) : \(\O(n^{3}\log(n))\).
Sous-problèmes : \(\symtt{fw(g,k) = M^{k}}\) correspond aux distances minimales entre chaque paire de sommets. Sachant que les seuls sommets intermédiaires utilisés sont les sommets \(0 \leq s < k\). Formule : \[ M^{k+1}_{i,j} = \min(M^{k}_{i,j}, M^{k}_{i,k} + M^{k}_{k,j}) \] Le problème \(\symtt{fw(G)}\) correspond au sous-problème \(\symtt{fg(G,n)}\).
On peut utiliser une approche montante ici.
def fw(g) : n = len(g) : m_new = [ [ x for x in line ] for line in g ] for k in range(1,n+1) : m_prev = m_new m_new = [ [ float("inf") for x in line ] for line in g ] for i in range(n) : for j in range(n) : m_new[i][j] = min(m_prev[i][j], m_prev[i][k] + m_prev[k][j]) return m_new
9.3. Levenstein
Entrées : Deux chaînes de caractère, s1
et s2
de longueur \(n_1\) et
\(n_2\).
Sortie : Le nombre minimal d’insertion, suppression, substitution qu’il faut
appliquer à s1
pour obtenir s2
. Appelé distance d’édition.
Complexité recherche exhaustive : \(\O(3^{n_{1} + n_{2}})\).
Complexité programme dynamique : \(\O(n^{2})\).
Sous-problèmes : \(\symtt{lev(s1,s2,i,j)}\) correspond à la distance d’édition
entre s[:i]
et s[:j]
. Pour \(0 \leq i \leq n_{1}\) et \(0 \leq j \leq
n_{2}\).
Formule :
\[ \symtt{lev(s1,s2,i,j)} = \begin{cases} j & \text{si } i = 0 \\
i & \text{sinon si } j = 0 \\
1 + \min(symtt{lev(s1,s2,i-1,j)}, symtt{lev(s1,s2,i,j-1)},
symtt{lev(s1,s2,i-1,j-1)}) & \text{sinon si } \symtt{s1[i-1] != s2[j-1]} \\
1 + symtt{lev(s1,s2,i-1,j-1)} & \text{sinon } \\
\end{cases} \]
Le problème \(\symtt{lev(s1,s2)}\) correspond au sous-problème \(\symtt{lev(s1,s2,}n_{1},n_{2})\).
def lev_memo(s1,s2,i,j,memo) : if i == 0 : memo[(i,j)] = j return elif j == 0 : memo[(i,j)] = i return elif s1[i-1] == s2[j-1] : if (i-1,j) not in memo : lev_memo(s1,s2,i-1,j,memo) if (i,j-1) not in memo : lev_memo(s1,s2,i,j-1,memo) if (i-1,j-1) not in memo : lev_memo(s1,s2,i-1,j-1,memo) memo[(i,j)] = 1 + min(memo[(i-1,j)], memo[(i,j-1)], memo[(i-1,j-1)]) return else : if (i-1,j-1) not in memo : lev_memo(s1,s2,i-1,j-1,memo) memo[(i,j)] = 1 + memo[(i-1,j-1)] return def lev(s1,s2) : memo = {} lev_memo(s1,s2,len(s1),len(s2),memo) return memo[(len(s1), len(s2))]
9.4. Held-Karp
Entrées : Un graphe pondéré orienté g = (S,A)
à n
sommets et m
arrêtes.
Sortie : Le chemin le plus court permettant de passer par chaque sommet
exactement une fois.
Complexité recherche exhaustive : \(\O(n!)\).
Complexité programme dynamique : \(\O(2^{n}\,n^{2})\).
Sous-problèmes : \(\symtt{hk(g,E,i,j)}\) correspond au poids du chemin le plus
cours reliant i
et j
en passant exactement une fois par tous les
sommets de E
, ensemble de sommets de g
tel que i
et j
ne
sont pas dans E.
Formule :
En notant \(d(u,v)\) le poids de l’arête \((u,v)\) (éventuellement \(+\infty\)).
\[ \symtt{hk(g,E,i,j)} = \begin{cases} d(i,j) & \text{si } E = \emptyset \\
\min_{v \in E}(d(v,u) + \symtt{hk(g,E \setminus \{v\},v,j)}) & \text{sinon} \\
\end{cases} \]
Le problème \(\symtt{hk(g,i,j)}\) correspond au sous-problème \(\symtt{hk(g,S
\setminus \{i,j\}, i,j)}\), avec S
l’ensemble des sommets de g
.
S
doit être représenté par son indicatrice sous la forme de tuple pour pouvoir
être mémoïsé. On représente ici g
par sa matrice d’adjacence.
def hk_memo(g,E,i,j,memo) : vide = True for s_in_E in E : if s_in_E : vide = False if vide : memo[(E,i,j)] = g[i][j] return m = float("inf") for s in range(len(E)) : if E[s] : E_sans_s = E[:i] + (False,) + E[i+1:] if (E_sans_s,s,j) not in memo : hk_memo(g,E_sans_s,s,j,memo) val = g[i][s] + memo[(E_sans_s,s,j)] if val < m : m = val memo[(E,i,j)] = m return def hk(g,i,j) : memo = {} S = (True,) * len(g) S = S[:i] + (False,) + S[i+1:] S = S[:j] + (False,) + S[j+1:] # ou S = tuple( [ x != i and x != j for x in range(len(g)) ] ) hk_memo(g,S_sans_ij,i,j,memo) return memo[(len(s1), len(s2))]
10. Apprentissage
10.1. K-voisins
Entrées : Un ensemble de \(n\) données d’entraînements étiquetées, une distance entre les
données, un entier \(k\), une donnée \(x\).
Sortie : L’étiquette majoritaire parmis les \(k\) plus proches voisins de \(x\).
L’ensemble des données d’entraînement est ici un dictionnaire dont la clé est la donnée et la valeur est l’étiquette.
def kvoisins(e,d,k,x) : pp = kplusproches(e,d,k,x) return majo(pp,e,d,k)
Il y a plusieurs algorithmes pour kplusproches
. Parmis elles :
- l’algorithme ci-après en \(\O(kn)\) si on suppose la complexité de \(d\) en \(\O(1)\).
- trier la liste et récupérer les
k
premiers en \(\O(n\log(n))\) - Beaucoup plus compliqué : un variante du quickselect, linéaire en moyenne mais quadratique au pire cas. Qui peut devenir linéaire en le combinant avec une méthode de médiane des médianes.
def kplusproches(e,d,k,x) : L = [] for data in e.keys() : if len(L) < k : L.append(data) elif d(data,x) < d(data,L[k-1]) : L.pop() L.append(data) j = k-1 while j > 0 and L[j] < L[j-1] : L[j], L[j-1] = L[j-1], L[j] j += 1 return L
Pour la fonction majo
, cela dépend de l’heuristique utiliser pour décider de
l’étiquette majoritaire. Dans ça version la plus simple :
def majo(pp,e,d,k) : etiquettes = {} for data in pp : if e[data] not in etiquettes : e[data] = 1 else : e[data] += 1 e_max = None m = -1 for et in etiquettes : if etiquettes[et] > m : e_max = et m = etiquettes[et] return e_max
10.2. K-moyennes
Entrées : Un ensemble de \(n\) données d’entraînements, une distance entre les
données, un entier \(k\), une fonction moyenne
qui calcule la moyenne
d’un ensemble de données.
Sortie : \(k\) catégories.
L’ensemble des données d’entraînement est ici une liste de données.
from random import sample def kmoyenne(e,d,k,moyenne) : centroides = sample(e,k) old_centroides = None while old_centroides != centroides : categories = [ [] for i in range(k) ] for data in e : centroide_plus_proche = 0 min_distance = d(data, centroides[0]) for i in range(1,k) : distance = d(centroide[i], data) if distance < min_distance : centroide_plus_proche = i min_distance = distance categories[centroide_plus_proche].append(data) old_centroides = centroides centroides = [ moyenne(cat[i]) for i in range(k) ] return categories
Si les données sont des vecteurs, la distance et la moyennes peuvent être implémentées par :
def distance(x,y) : d = 0 for i in range(len(x)) : d += (x[i]-y[i])**2 return d ** 0.5 def moyenne(e) : m = [0.0] * len(e[0]) for x in e : for i in range(len(x)) : m[i] += x[i] return [ x / len(m) for x in m ]
11. Théorie des jeux
11.1. Calcul des attracteurs
Entrées : Le graphe d’un jeux sous forme de liste d’adjacence (\(n\) sommets et
\(m\) arêtes), les conditions
de victoires d’un joueur sous la forme d’une liste de sommets du graphe,
le joueur en question.
Sortie : L’ensemble des attracteurs (sommets gagnants) du joueur en question.
Complexité : \(\O(n + m)\)
Dans le graphe d’un jeux chaque sommet est contrôlé par un joueur. Il y a
plusieurs manière de représenté cela. On supposera que chaque sommet est de la
forme (j,s)
avec j
le numéro, 0 ou 1, du joueur contrôlant le sommet.
def transpose(g) : tg = [ [] for s in g ] for s in g : for v in g[s] : tg[v].append(s) return tg def attracteurs(g,v,joueur) : tg = transpose(g) marquage = [ len(tg[s]) for s in tg ] a = [ False for i in range(len(tg)) ] for s in v : parcours(tg,s,a,marquage, joueur) return a def parcours_dfs(g,s,a,marquage, joueur) : if a[s] : return a[s] = True for v in g[s] : if not a[v] : marquage[v] -= 1 j,_ = v if j = joueur or marquave[v] == 0: parcours_dfs(v)
11.2. Algorithme Min-Max
Entrées : Un arbre de jeux à profondeur fixé, donné par sa liste d’adjacence,
r la racine de l’arbre, une heuristique \(h\) permettant
d’estimer le score des feuilles, le joueur (0 pour max, 1 pour min) qui
joue à la racine.
Sortie : Le score du joueur à la racine de l’arbre, et
le coup qui permet de l’attendre.
def minmax(a, r, h, j) : if len(a[r]) == 0 : return h(r), None L = [] for v in a[r] : L.append((minmax(a,v,h,1-j), v)) if i == 0 : return max(L) return min(L)