Types et Représentation en Programmation
Types et Représentation en Programmation
B. Landelle
IV Manipulation de chiers 16
1 Les instructions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2 Lecture dans un chier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
3 Écriture dans un chier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
V Complexité 17
1 Dénitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2 Classes de complexité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
3 Exemples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
VIII Récursivité 25
1 Propagation et cas de base . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
2 Empilement des récursions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
3 Complexité et récursivité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
1
IX Tris 27
1 Tri par insertion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
2 Tri rapide . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
3 Tri fusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
X Graphes 30
1 Représentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
2 Parcours d'un graphe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
3 Plus court chemin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
B. Landelle 2 ISM MP
Les modules scientique et graphique seront importés avec leurs alias habituels :
import numpy as np, [Link] as plt
On notera log la fonction logarithme népérien et log2 le logarithme en base 2 déni par
log x
∀x > 0 log2 (x) =
log 2
• Les booléens
>>> type(True)
<class 'bool'>
>>> not True
False
>>> True or False
True
>>> 1>2
False
B. Landelle 3 ISM MP
Exercice : Soit L une liste de nombres. Écrire une instruction qui détermine la proportion de
ceux inférieurs ou égaux à 1. On autorise l'utilisation de la fonction [Link] qui eectue un
calcul de moyenne.
Corrigé : On saisit :
>>> [Link]([x<=1 for x in L])
• Les ottants
>>> type(1.5)
<class 'float'>
>>> 4e-3
0.004
>>> [Link](2.7)
2.0
>>> [Link](2)
1.4142135623730951
>>> [Link]([Link])
1.2246467991473532e-16
• Les complexes
>>> type(1j)
<class 'complex'>
>>> 1j**2
(-1+0j)
>>> [Link](-1) # dans R
nan
>>> [Link](-1+0j) # dans C
1j
>>> abs(1+1j)
1.4142135623730951
Corrigé :
B. Landelle 4 ISM MP
>>> type(0)
<class 'int'>
>>> type(0.)
<class 'float'>
>>> 0.==0
True
>>> 1.+True
2.0
>>> int(-.5)==[Link](-.5)
False
2 Les variables
Les variables créées sous python sont des étiquettes qui désignent des objets sur lesquels on
peut agir avec des fonctions ou des méthodes. Les méthodes d'un objet désigné par une variable
var s'utilisent selon la syntaxe
[Link](arguments)
Création :
>>> a=1
>>> type(a)
<class 'int'>
Création simultanée :
>>> a,b=1,2
>>> a
1
>>> b
2
>>> a,b
(1, 2)
Échange de variables :
>>> a,b=b,a
>>> a,b
(2, 1)
Opérations/aectations simultanées :
>>> a=10
>>> a+=1
>>> a
B. Landelle 5 ISM MP
11
>>> a//=2
>>> a
5
Corrigé :
>>> a,b
(1.5, 1.4142135623730951)
B. Landelle 6 ISM MP
Du slicing :
>>> a[1:4]
'onj'
>>> a[:4]
'bonj'
>>> a[4:]
'our'
>>> a[::-1]
'ruojnob'
Exercice : Soit mot une chaîne de caractères. Écrire une instruction dont le résultat est True
si mot est un palindrome et False sinon.
Corrigé :
>>> mot==mot[::-1]
• Les listes
>>> a=[1,"bonjour",True]
>>> type(a)
<class 'list'>
>>> id(a)
84754504
>>> a[1]=3.
>>> a
[1, 3.0, True]
>>> id(a)
84754504
>>> [Link](2) # méthode d'ajout en fin de liste
>>> a
[1, 3.0, True, 2]
>>> [Link]() # méthode de suppression en fin de liste
2
>>> a
[1, 3.0, True]
>>> id(a)
84754504
>>> b=[3,2,1,5]
>>> sorted(b)
[1, 2, 3, 5]
B. Landelle 7 ISM MP
>>> b
[3, 2, 1, 5]
>>> [Link]() # la méthode sort modifie b
>>> b
[1, 2, 3, 5]
Pour copier des listes contenant des sous-listes en évitant le problème précédemment rencontré,
il faut une copie en profondeur :
>>> from copy import deepcopy
>>> a=[[1],2]
>>> b=deepcopy(a)
>>> id(a)
90034056
>>> id(b)
90033864
>>> id(a[0])
89938056
>>> id(b[0])
89970056
Corrigé :
B. Landelle 8 ISM MP
>>> L1=list(mot1)
>>> L2=list(mot2)
>>> sorted(L1)==sorted(L2)
• Les dictionnaires
>>> dico={}
>>> type(dico)
<class 'dict'>
>>> dico["chat"]="cat"
>>> dico[0]=False
>>> dico["SPE"]=["MP","PC"]
>>> dico
{'chat': 'cat', 0: False, 'SPE': ['MP', 'PC']}
>>> dico["SPE"].append("PSI")
>>> dico
{'chat': 'cat', 0: False, 'SPE': ['MP', 'PC', 'PSI']}
>>> "SUP" in dico
False
B. Landelle 9 ISM MP
II Représentation des nombres
1 Les entiers
p−1
Un entier n codé sur p bits a pour écriture binaire n = dk 2k avec dk ∈ {0, 1}. On note
P
k=0
n = ⟨dp−1 , . . . , d1 , d0 ⟩2
p−1
Un entier n non nul possède une unique écriture binaire de la forme n = dk 2k avec dk ∈ {0, 1}
P
k=0
pour k < p − 1 et dp−1 = 1. Le bit dp−1 est dit bit de poids fort et d0 bit de poids faible. On a
p−1
2p−1 ⩽ n ⩽ 2k = 2p − 1
P
=⇒ p = ⌊log2 (n)⌋ + 1
k=0
Ce codage est compatible avec l'algorithme usuel d'addition. Un entier négatif sera codé par
⟨1, dp−2 , . . . , d0 ⟩2 .
En python, depuis la version 3, les subtilités des formats d'entiers sont invisibles à l'utilisateur :
les entiers positifs, négatifs, grands ou petits en valeur absolue sont tous codés par le type int :
>>> type(1)
<class 'int'>
>>> type(2**10000)
<class 'int'>
>>> type(-2**10000)
<class 'int'>
• Algorithme de Horner
n−1
Soit x un réel et P = ak Xk ∈ R[X]. Pour calculer ecacement P(x), on peut observer
P
k=0
l'écriture suivante :
n−1
ak xk = (. . . ((an−1 x + an−2 ) x + an−3 ) x + . . .) x + a0
P
P(x) =
k=0
2 Les ottants
Les ottant sont codés selon la norme IEEE 754 et sont de la forme
52 m 10
(−1)s × M × 2E−1023 avec s ∈ {0, 1} , i
ei 2i
P P
M=1+ i
E=
i=1 2 i=0
L'intérêt de cette norme est de pouvoir coder une grande amplitude de nombres. Le bit s sert
au codage du signe, le nombre M codé sur 52 bits est appelé mantisse et l'entier E est appelé
B. Landelle 10 ISM MP
exposant codé sur 11 bits.
Il y a plusieurs limitations au format ottant : pour la plupart, les nombres ne sont pas codés
dèlement, les limites de précision peuvent donner lieu à des phénomènes d'absorption, le format
est borné, etc....
>>> .1+.2
0.30000000000000004
>>> 1+2**(-53)-1==0
True
>>> float(2**1023)
8.98846567431158e+307
>>> float(2**1024)
Traceback (most recent call last):
File "<pyshell#123>", line 1, in <module>
float(2**1024)
OverflowError: int too large to convert to float
! Tester la nullité de x nombre ottant par x==0 n'est pas pertinent : on utilisera un test de
la forme abs(x)<eps avec eps une seuil de précision choisi par l'utilisateur.
Syntaxe standard
def nom_fonction(arguments):
Instructions
return résultat
On notera la présence de l'indentation pour le corps de la fonction. Cette indentation est indis-
pensable. Le retour à l'indentation de def détermine la n du corps de la fonction. L'instruction
return provoque la sortie de la fonction et renvoie le résultat.
Syntaxe courte
nom_fonction=lambda arguments : résultat
B. Landelle 11 ISM MP
sq=lambda x:x*x
On peut casser une boucle for avec un return. Pour tester la présence d'un élément dans une
liste, on peut coder :
def detect(L,elt):
for x in L:
if x==elt:
return True
return False
B. Landelle 12 ISM MP
Le fait de casser la boucle for rend l'étude de complexité plus opaque mais c'est tellement
simple de coder ainsi qu'il serait absurde de s'en priver.
ou
L=list(range(10))
res=[]
for x in L:
[Link](x)
[Link]()
def binom(n,k):
res=1
for i in range(k):
res=(n-i)*res//(i+1)
return res
def binom(n,k):
p=min(k,n-k)
res=1
for i in range(p):
res=(n-i)*res//(i+1)
return res
B. Landelle 13 ISM MP
Pour coder une suite (un )n dénie par la relation de récurrence un+1 = f (un ), on saisit :
def suite(f,u0,n):
u=u0
for k in range(n):
u=f(u)
return u
• Boucle conditionnelle
while Condition:
Instructions
def pgcd(a,b):
u,v=a,b
while v!=0:
u,v=v,u%v
return u
Écrire un programme qui détermine la plus grande puissance de 2 pour laquelle deux entiers
ont toujours même identiant.
Corrigé : On saisit :
def plage_int():
a,b=1,1
while id(a)==id(b):
a*=2
b*=2
return a//2
On obtient :
>>> plage_int()
256
B. Landelle 14 ISM MP
3 Les tests
• Test simple
if Condition:
Instructions
Fonction qui renvoie le nombre d'occurrences d'un élément dans une liste :
def nb_occ(L,elt):
res=0
for x in L:
if x==elt:
res+=1
return res
Exercice : Écrire une fonction qui détermine les indices de toutes les occurrences d'un élément
dans une liste.
Corrigé : On saisit :
def rech_occ(L,elt):
res=[]
for k in range(len(L)):
if L[k]==elt:
[Link](k)
return res
B. Landelle 15 ISM MP
Exercice : Écrire une fonction double(a,b,c) qui renvoie True si aX2 + bX + c admet une
racine double et False sinon.
Corrigé : On saisit :
def double(a,b,c):
eps=1e-10
return abs(b**2-4*a*c)<eps
IV Manipulation de chiers
1 Les instructions
Pour manipuler les chiers, on utilise :
l'instruction open pour l'ouverture ou la création ;
la méthode close pour la fermeture ;
la méthode read pour la lecture intégrale ;
la méthode readline pour lire la ligne courante ;
la méthode write pour l'écriture.
Pour la lecture et l'écriture dans un chier, on recommande de travailler sur des chiers *.csv
avec le caractère ';' comme séparateur. Pour lire dans un chier [Link], on utilise :
data=open('[Link]') # ouverture du fichier dans la variable data
contenu=[Link]() # contenu reçoit l'intégralité de data, format str
ligne=[Link]() # ligne reçoit la ligne courante, format str
[Link]() # fermeture du fichier
B. Landelle 16 ISM MP
0.0 0.0 1.0
0.0317332591272 0.0317279334981
0.0634665182543 0.0634239196566
0.8
0.126933036509 0.126592453574
0.158666295636 0.158001395973 0.4
... ...
0.2
0.0
0.0 0.5 1.0 1.5 2.0 2.5 3.0 3.5
On peut parcourir directement le chier ligne par ligne avec une simple boucle for. Chaque
ligne est lue comme chaîne de caractères.
points=open('[Link]')
tx,ty=[],[]
for coords in points: # lecture ligne par ligne
xy=[Link](";") # coupe la chaine "x;y" en ("x","y")
[Link](float(xy[0]))
[Link](float(xy[1]))
[Link]()
[Link](tx,ty);[Link]()
V Complexité
1 Dénitions
La complexité temporelle d'un algorithme désigne le nombre d'opérations élémentaires réalisées
par l'algorithme. La complexité spatiale d'un algorithme désigne l'espace mémoire occupé lors
de l'exécution de l'algorithme.
La complexité temporelle dans le pire cas est le temps d'exécution maximum. La complexité
temporelle dans le meilleur cas est le temps d'exécution minimum.
B. Landelle 17 ISM MP
La complexité spatiale dans le pire cas est l'espace mémoire occupé maximum. La complexité
spatiale dans le meilleur cas est l'espace mémoire occupé minimum.
On utilise dans le cadre du programme la relation de domination un = O(vn ), la suite (un )n est
dominée par la suite (vn )n , qui signie :
∃n0 ∈ N ∃M > 0 ∀n ⩾ n0 |un | ⩽ M |vn |
Remarque : La bonne notion pour l'étude de complexité n'est pas en réalité la relation de
domination. Eet, si un algorithme a une complexité temporelle en O(n), on peut tout aussi
bien annoncer qu'elle est en O(n2 ). C'est correct même si c'est nettement moins pertinent.
L'usage consiste donc à déterminer la domination la plus ne qui soit ce qui équivaut à utiliser
la relation hors-programme de l'ordre de notée Θ. On note un = Θ(vn ) ce qui signie un = O(vn )
et vn = O(un ).
2 Classes de complexité
L'entier n désigne en général la taille de l'argument. Dans le cas d'un algorithme portant sur
un calcul arithmétique, il peut aussi désigner l'argument lui-même.
! Les temps d'accès à un élément d'une liste en lecture/écriture sont en O(1). Cette caracté-
ristique combinée à une structure dynamique est un des atouts majeurs des listes en python.
B. Landelle 18 ISM MP
Opération Complexité
append O(1)∗
pop O(1)∗
==, != O(n)
in O(n)
remove O(n)
delete O(n)
count O(n)
max, min O(n)
reverse O(n)
sort O(n log n)
(*) : il s'agit de complexité amortie (complexité moyenne en utilisation).
! Les tests d'appartenance et les temps d'accès en lecture/écriture à une couple (clé,valeur)
d'un dictionnaire sont en O(1).
La complexité spatiale de l'instruction range(n) est en O(1) tandis que celle de list(range(n))
est en O(n). Pour s'en convaincre, on exécute :
tn=range(100)
tR=[getsizeof(range(n)) for n in tn]
tL=[getsizeof(list(range(n))) for n in tn]
[Link](tn,tR,tn,tL)
[Link]("Complexité spatiale de range(n) et list(range(n))")
[Link]();[Link]()
800
600
400
200
0
0 20 40 60 80 100
3 Exemples
• Recherche d'un doublon dans une liste
B. Landelle 19 ISM MP
def doublon(L):
n=len(L)
for i in range(n-1):
for j in range(i+1,n):
if L[i]==L[j]:
return True
return False
La complexité est en O(1) dans le meilleur des cas (deux doublons en première et deuxième po-
n−2
P n−1
sition). Dans le pire des cas, s'il n'y a pas de doublons, on eectue 1 = O(n2 ) répétitions.
P
i=0 j=i+1
La complexité est en O(1) dans le meilleur des cas et en O(n) dans le pire des cas. On verra que
le coût d'un tri est en O(n log n) ce qui fait dont un coût total en O(n)+ O(n log n) = O(n log n)
ce qui est meilleur que la complexité précédente.
La complexité en O(1) dans le meilleur des cas (élément présent en première position) et en
O(n) dans le pire des cas (où n est la taille de L).
Remarque : Dans les deux exemples précédents, il s'agit en fait d'une boucle while déguisée
puisque le return True peut casser la boucle for avant la n.
B. Landelle 20 ISM MP
• Recherche d'un élément dans une liste triée
def rech_dicho(elt,L):
deb,fin,trouve=0,len(L)-1,False
while not trouve and deb<=fin:
milieu=(deb+fin)//2
if L[milieu]==elt:
trouve=True
elif L[milieu]>elt:
fin=milieu-1
else:
deb=milieu+1
return L[milieu]==elt,milieu
La complexité est en O(1) dans le meilleur des cas (élément présent au milieu de la liste) et en
O(log n) dans le pire des cas : la longueur de la zone de recherche est divisée par 2 à chaque
étape donc pour 2p−1 ⩽ n < 2p , le programme s'arrête après p itérations.
• Exponentiation rapide
Soit x un réel et n un entier non nul d'écriture binaire n = ⟨dp−1 , . . . , d0 ⟩2 . On a
Pp−1 p−1 p−1 ädi
xn = x( di 2i ) QÄ i
ä QÄ i
i=0 = xdi 2 = x2
i=0 i=0
def expo(x,n):
a,r,k=x,x**0,n
while k>0:
if k%2==1:
r*=a
a*=a
k//=2
return r
Il y a p produits à eectuer d'où une complexité en O(p) = O(log n). On fait l'hypothèse très
simplicatrice que les multiplications qui interviennent ci-dessus sont à coût constant, ce qui
est clairement faux pour de grands nombres.
B. Landelle 21 ISM MP
def test_rech_dicho():
L=list(range(1,20,2))
assert rech_dicho(1,L)==True,0
assert rech_dicho(19,L)==True,9
assert rech_dicho(9,L)==True,4
assert rech_dicho(10,L)[0]==False
assert rech_dicho(18,L)[0]==False
assert rech_dicho(20,L)[0]==False
assert rech_dicho(2,L)[0]==False
assert rech_dicho(0,L)[0]==False
La fonction est enregistrée dans le chier [Link] puis on exécute pytest [Link] depuis
l'invite de commande :
Un invariant de boucle est un prédicat qui ne change pas au cours de la boucle. On démontre
cette propriété par récurrence.
B. Landelle 22 ISM MP
3 Exemples
• Coecient binomial
def binom(n,k):
res=1
for i in range(k):
res=(n-i)*res//(i+1)
return res
La variable k-i est un variant de boucle et une invariant est donné par
P(i) : n
res = i
• Exponentiation rapide
def expo(x,n):
a,r,k=x,x**0,n
while k>0:
if k%2==1:
r*=a
a*=a
k//=2
return r
La variable k est une variant de boucle et notant n = ⟨dp−1 , . . . , d0 ⟩ son écriture binaire, un
invariant est donné par
P(j) :
j
k = ⟨dp−1 , . . . , dj ⟩2 r = x⟨dj−1 ,...,d0 ⟩ a = x2
avec j le nombre de passage dans la boucle while.
empiler depiler
B. Landelle 23 ISM MP
Pour manipuler des piles, on utilise les opération primitives suivantes :
la fonction Pile : crée une pile vide ;
la méthode empiler : ajoute un élément au sommet de la pile ;
la méthode depiler : dépile l'élément au sommet de la pile et le renvoie ;
la méthode vide : indique si la pile est vide.
Exercice : Écrire, uniquement à l'aide des opérations primitives la fonction duplique(P) qui,
pour une pile P non vide, duplique l'élément au sommet de la pile.
Corrigé : On saisit :
def duplique(P):
S=[Link]()
[Link](S)
[Link](S)
2 Files
Une le aussi appelée le d'attente (queue en anglais) est une structure de données basée sur
le principe premier entré, premier sorti (FIFO en anglais, First In First Out ).
Figure 4 File
En python, on dispose du module deque de la bibliothèque collections qui propose une
implémentation de cette structure
from collections import deque
B. Landelle 24 ISM MP
L'implémentation proposée est optimisée : les opérations append, appendleft, pop, popleft
sont de complexité temporelle en O(1) (complexité amortie).
VIII Récursivité
1 Propagation et cas de base
Une fonction récursive est une fonction qui fait appel à elle-même. Cet appel est dénommé
appel récursif ou récursion.
B. Landelle 25 ISM MP
0 1
1 ? 1 1
2 ? 2 ? 2 2
3 ? 3 ? 3 ? 3 6
n fact n fact n fact n fact
3 Complexité et récursivité
• Récursions multiples
Les récursions multiples par propagation sont très coûteuses : avec p récursions à chaque pro-
pagation pour un argument décroissant linéairement et dont les autres opérations sont de com-
plexité temporelle et spatiale en O(1), la complexité temporelle en O(ρn ) avec ρ > 1.
La suite de Fibonacci (un )n est dénie par u0 = u1 = 1 et un+2 = un+1 + un . On peut la coder
naïvement récursivement avec :
def fibo(n):
if n<=1: # cas de base pour n=0 et n=1
return 1
else:
return fibo(n-1)+fibo(n-2) # propagation avec récursion double
√
1+ 5
La complexité temporelle de fibo(n) est en O(φ ) avec φ =
n
.
2
• Exponentiation rapide
Pour (x, n) ∈ R × N, on a
2
si n pair
n
(
x2
xn = Ä n−1 ä2
x× x 2 si n impair
On en déduit l'implémentation récursive simple :
def expo_rec(x,n):
if n==0:
return x**0 # cas de base avec exposant nul
else:
r=expo_rec(x,n//2)**2 # propagation avec le quotient de n par 2
if n%2==0: # distinction selon la parité
return r
else:
return x*r
B. Landelle 26 ISM MP
La complexité temporelle vérie
T(n) = T (n//2) + O(1)
p−1
Ainsi, notant n = di 2i son écriture binaire avec p = ⌊log2 (n)⌋ + 1, il vient
P
i=0
p−1
T n//2k − T n//2k+1 = R(0) + O(p) = O(log n)
P
T(n) = T(0) +
k=0
L'exemple le plus célèbre est sans doute celui de la FFT (Fast Fourier Transform) : l'implé-
mentation naïve de la transformée de Fourier est de complexité temporelle en O(n2 ) tandis que
l'implémentation récursive diviser pour régner est en O(n log n).
IX Tris
Un tri est dit en place s'il modie directement la structure qu'il est en train de trier sans créer
de variable de taille de même ordre que la liste à trier.
Ainsi, pour une liste T, on examine les deux premiers éléments et on les échange éventuellement
pour qu'ils soient ordonnés, puis on considère le troisième élément et on vient, par remontées
successives, l'insérer vis-à-vis des deux premiers déjà triés, etc. . . .
def tri_ins(T):
n=len(T)
for i in range(1,n): # on parcourt la liste
c=T[i] # c est l'élément courant
j=i-1 # j balaie les indices des éléments précédents
while j>=0 and T[j]>c: # tant que c n'est pas bien placé
T[j+1]=T[j] # on décale les éléments précédents
j-=1
T[j+1]=c
B. Landelle 27 ISM MP
C'est un tri en place donc la complexité temporelle est :
dans le meilleur des cas en O(n) ;
dans le pire des cas en O(n2 ).
2 Tri rapide
Le tri rapide repose sur le paradigme diviser pour régner . Il consiste en le procédé suivant :
choix d'un pivot, placement des éléments de la liste vis-à-vis du pivot puis tri récursif à droite
et à gauche du pivot.
⩽ P P ⩾ P
def tri_rapide(T):
if T==[]:
return []
else:
pivot=T[0] # choix du pivot à gauche
T1,T2=[],[]
for x in T[1:]: # placement des éléments vis-à-vis du pivot
if x<pivot:
[Link](x)
else:
[Link](x)
# après placement, tri récursif
return tri_rapide(T1)+[pivot]+tri_rapide(T2)
C'est une version simple à implémenter mais pas en place (défaut mineur en python. . .).
Exercice : Expliquer pourquoi la version proposée du tri rapide n'est pas en place.
Corrigé : On construit au cours des récursions des listes T1 et T2 dont les tailles sont de
l'ordre de n la taille de l'argument T, de l'ordre de n/2 si le pivot est à peu près au milieu après
placement et au pire de l'ordre de n si le pivot est complètement à droite ou à gauche après
placement.
B. Landelle 28 ISM MP
3 Tri fusion
Le tri fusion (merge sort en anglais) est également basé sur le paradigme diviser pour régner .
Il consiste en le procédé suivant : coupure de la liste de taille n en deux listes de tailles ⌊n/2⌋,
⌈n/2⌉, tri récursif de ces listes puis fusion de celles-ci.
def tri_fusion_rec(T,g,d):
if d-g>1:
m=(g+d)//2
tri_fusion_rec(T,g,m) # on trie la moitié gauche
tri_fusion_rec(T,m,d) # on trie la moitié droite
fusion(T,g,m,d) # on fusionne chaque moitié triée
def tri_fusion(T):
tri_fusion_rec(T,0,len(T)) # amorce le tri fusion
Le tri fusion n'est pas en place et sa complexité temporelle est en O(n log n).
B. Landelle 29 ISM MP
L'instruction sorted et la méthode sort sont une implémentation d'un tri hybride construit à
partir du tri par insertion et du tri fusion.
X Graphes
1 Représentation
Pour X un ensemble, on note P2 (X) l'ensemble des parties de X à un ou deux éléments.
Un graphe non orienté G est un couple (S, A) avec S un ensemble ni de sommets ou n÷uds
et A une partie de P2 (S) dont les éléments sont appelés arêtes.
Un graphe orienté G est un couple (S, A) avec S un ensemble ni de sommets ou n÷uds et A
une partie de S2 dont les éléments sont appelés arcs.
x1 x2 x3
0 1 2
y1 y2 y3
4 3
Un graphe valué ou pondéré G est un triplet (S, A, ν) avec (S, A) un graphe orienté ou non
muni d'une application ν : A → R appelée valuation ou pondération du graphe.
3
10
0 1 3
1
9 2
2 6
5
10
2 4 5
2
2
Deux sommets sont dits adjacents s'il existe un arête ou un arc reliant l'un à l'autre.
B. Landelle 30 ISM MP
La représentation d'un graphe G = (S, A) avec S = [s0 , . . . , sn−1 ], orienté ou non orienté par
listes d'adjacences consiste en une liste de listes LA = [LA[i], i ∈ [[ 0 ; n − 1 ]]] telle que pour
i ∈ [[ 0 ; n − 1 ]], la liste LA[i] contient les sommets adjacents à si .
0 1 3
7 2 4
5 8
ou avec un dictionnaire :
S=list(range(9))
A={0:[1,2,6],1:[3],2:[3,4,5,7,8],3:[4],4:[2],5:[2,8],6:[],7:[2],8:[]}
On peut aussi utiliser représenter le graphe par une matrice d'adjacence M = mi,j
0⩽i,j⩽n−1
∈
Mn (R) dénie par
1 si (si , sj ) ∈ A ou {si , sj } ∈ A
∀(i, j) ∈ [[ 0 ; n − 1 ]] mi,j =
0 sinon
0
à í
0 1 0 1 1
0 1 1 0 1
1 4
M= 1 1 0 0 0
0 0 1 0 1
1 0 0 0 0
2 3
B. Landelle 31 ISM MP
2 Parcours d'un graphe
• Parcours en profondeur
On propose une version récursive du parcours en profondeur (DFS en anglais, Depth First
Search). Celui-ci s'applique indiéremment aux graphes orientés ou non orientés. Le principe
est le suivant :
on part d'un sommet donné que l'on considère comme en cours de visite ;
s'il admet des sommets adjacents non visités (des successeurs), on appelle récursive-
ment le parcours en profondeur depuis chacun d'eux ;
s'il n'y a pas ou plus de successeur, le sommet est considéré comme visité .
4 1
3 2
• Parcours en largeur
Le parcours en largeur (BFS en anglais, Breadth First Search) s'applique indiéremment aux
graphes orientés ou non orientés. Son principe est le suivant :
on part d'un sommet qu'on place dans une le et que l'on considère comme en cours
de visite ;
tant que la le n'est pas vide, on sort le sommet premier entré, on fait entrer dans la le
tous ses sommets adjacents non visités puis on considère le sommet d'origine comme
visité .
4 1
3 2
B. Landelle 32 ISM MP
k
P
L(c) = ν(xi−1 , xi )
i=1
On dénit le coût d'un plus court chemin entre les sommets x et y noté δ(x, y) par
inf {L(c), c : chemin de x à y} s'il existe un chemin de x à y
δ(x, y) =
+∞ sinon
3
10
0 1 3
1
9 2
2 6
5
10
2 4 5
2
2
Un chemin est simple s'il ne contient pas deux fois le même arc. Un chemin simple dont les
extrémités coïncident est appelé circuit. Un circuit de coût strictement négatif est dit absorbant.
Étant donnés deux sommets x et y reliés par un chemin, il existe un plus court chemin entre x
et y si et seulement aucun chemin de x à y ne contient de circuit absorbant.
• Algorithme de Dijkstra
Soit G = (S, A, ν) un graphe orienté valué positivement. On note s0 le sommet source et d [u]
la longueur d'un plus court chemin de s0 à un sommet u découvert à un certain stade de l'al-
gorithme.
B. Landelle 33 ISM MP
djvu : pour s un sommet, djvu[s] est un booléen qui dit si le sommet a été déjà vu ou
pas.
Remarque : L'implémentation ci-avant détermine la totalité des plus courts chemins existants
depuis s0. On peut aussi eectuer une recherche plus ciblée d'un plus court chemin entre un
sommet de départ et un sommet d'arrivée (à fournir en arguments).
Exercice : Déterminer la complexité temporelle de dijkstra. On note n = Card S et m =
Card A. Les accès en lecture en écriture dans une liste ou un dictionnaire sont supposés être
en O(1) (complexité amortie pour une liste et complexité moyenne pour un dictionnaire).
Les dictionnaires cout et djvu sont initialisés avec autant de clés que de sommets. Le diction-
naire predec contient les sommets pour lesquels un relâchement a été eectué donc au plus n.
Les autres variables sont en nombre xe et de taille xée. Par conséquent, la complexité spatiale
est en O(n).
• Algorithme A⋆
L'idée mise en ÷uvre dans l'algorithme A⋆ est de privilégier la recherche de chemin d'un som-
met source s à un sommet cible c en s'eorçant d'aller dans la direction de c et donc de réduire,
par exemple, la distance euclidienne à c.
B. Landelle 34 ISM MP
Soit G = (S, A, ν) un graphe orienté valué positivement et c ∈ S. Une heuristique h est une
fonction h : S → R+ . L'heuristique h est dite admissible pour la recherche de plus court chemin
au sommet c (la cible) si
∀s ∈ S h(s) ⩽ δ(s, c)
L'heuristique h est dite consistante si
∀(s, u) ∈ A2 h(s) ⩽ ν(s, u) + h(u)
Avec une heuristique admissible, l'algorithme A⋆ résout le problème de plus court chemin et
qu'avec une heuristique consistante, l'algorithme A⋆ résout le problème de plus court chemin
en complexité linéaire.
def d2(A,B):
xA,yA=A
xB,yB=B
return [Link]((xA-xB)**2+(yA-yB)**2)
def Astar_deb_fin(S,A,deb,fin):
nb=0
def h(s):
return d2(s,fin)
cout,dist,predec,djvu={},{},{},{}
for s in S:
cout[s]=[Link]
dist[s]=[Link]
djvu[s]=False
cout[deb]=h(deb)
dist[deb]=0
while not djvu[fin]:
nb+=1
# recherche elt à cout minimal
smin,cmin=deb,[Link]
for s in S:
if not djvu[s] and cout[s]<cmin:
smin,cmin=s,cout[s]
djvu[smin]=True
# relachement chez les successeurs
for s,nu in A[smin]:
if not djvu[s] and dist[s]>dist[smin]+nu:
dist[s]=dist[smin]+nu
cout[s]=dist[s]+h(s)
predec[s]=smin
return cout,predec,nb
Remarque : Le programme est correct si les sommets deb et fin sont reliés. Dans le cas
contraire, il faut prévoir une sortie de boucle.
B. Landelle 35 ISM MP
0 0
10 10
20 20
30 30
40 40
0 10 20 30 40 0 10 20 30 40
B. Landelle 36 ISM MP