Nous allons nous intéresser ici au problème suivant : étant donné un ensemble de points du plan, identifier le couple de points les plus proches.
Ce type d’algorithme a son utilité dans les transports aériens ou maritimes par exemple ( ou de manière plus anecdotique, dans la gestion des rebonds
dans le TP que vous avez fait en introduction du cours sur la POO...)
Nous représenterons un point par un tuple de deux entiers correspondant aux coordonnées cartésiennes (x, y)
du point dans un repère orthonormé.
L’ensemble des points considérés sera quant à lui stocké dans un tableau t. Nous supposerons toujours que tous les couples stockés dans t sont distincts.
Considérons deux points P1(x1, y1) et P2(x2, y2); leur distance dite euclidienne est donnée par la relation : dP1P2 = √(x2 − x1 )2 + (y2 − y1)2.
Écrire une fonction distance_euclidienne()
qui calcule la distance euclidienne séparant deux points.
def distance_euclidienne(p1 : tuple, p2: tuple)->float:
pass
return distance
Une méthode « naïve » permettant de déterminer les deux points les plus proches dans un tableau t consiste à considérer successivement tous les couples de points possibles, ce qui peut se faire facilement à l’aide de deux boucles imbriquées.
plus_proches()
qui prend comme paramètre un tableau de points t et renvoie les points (x1, y1), (x2 , y2)
situés à la distance minimale
l’un de l’autre, ainsi que cette distance.
def plus_proches(t: list)->(tuple, tuple, float):
pass
return p1, p2, dmin
plus_proches([(3, 4), (2, -1), (0.5, 2), (3, -4), (-1, -0.5), (-2, 5)]) == (0.5, 2), (-1, -0.5), 2.9154759474226504
plus_proches()
en fonction de la taille n du tableau t; en déduire la complexité de cet algorithme.Nous allons maintenant nous intéresser à la mise au point d’un algorithme permettant de résoudre ce même problème en complexité O(n log n).
On appelle P l'ensemble des n points à considérer.
On partage P verticalement en deux sous-ensembles A et B de même taille ( ligne rouge sur l'image ci-contre ); la plus petite distance entre deux points de P est alors celle, soit entre deux points de A, soit entre deux points de B, soit entre un point de A et un point de B.
On calcule alors récursivement la distance minimale dA entre deux points de A ( en rouge à gauche ) et la distance minimale dB entre deux points de B ( en rouge à droite ); on note δ le minimum entre dA et dB.
Il reste alors à calculer la distance minimale entre un point de A et un point de B, ce qui laisse beaucoup de combinaisons...
Mais on réduit en fait les possibilités en écartant toutes les distances supérieures à δ : pour cela, on ne s’intéresse donc qu'aux points situés dans la bande verticale de largeur 2δ de part et d'autre
du milieu de P ( lignes bleues sur la figure ).
Pas évident l'étape combiner !!!
L'algorithme nécessite de disposer de deux copies du tableau t, l’une triée par abscisses croissantes et l’autre par ordonnées croissantes.
tri_x()
qui prend en paramètre le tableau t, et qui renvoie un nouveau tableau des points triés par abscisses croissantes.tri_y()
qui renvoie un tableau des points triés par ordonnées croissantes.
def tri_x(t: list)->list:
pass
return Px
def tri_y(t: list)->list:
pass
return Py
Le travail à faire ci-dessous vous propose 3 niveaux de difficulté décroissante ( ***, ** et * ) pour implémenter cet algorithme pas du tout évident...
A vous de choisir la difficulté qui vous convient.
Compléter la fonction plus_proches_dpr()
ci-dessous de façon à implémenter l'algorithme décrit précédemment. Attention aux paramètres et aux résultats renvoyés par cette fonction.
Cette fonction fera appel à celles que vous aurez écrites auparavant.
Pour la construction des différents tableaux, on aura bien entendu intérêt à utiliser des méthodes par compréhension.
def plus_proches_dpr(Px: list, Py: list)->(tuple, tuple, float):
''' Fonction implémentant l'algorithme de recherche de deux points les plus proches dans un ensemble de points.
Entrées :
Px = tableau des points triés par abscisses croissantes
Py = tableau des points triés par ordonnées croissantes
Sorties :
p1, p2 = les deux points les plus proches
d = la distance entre eux
'''
n = len(Px) # nombre de points
# Cas de base : renvoi des points les plus proches dans A ( ou B ) et de dA ( ou dB )
pass
# Construction des tableaux des points de A et de B :
pass
# Appel récursif de la fonction sur A et B, et récupération des résultats renvoyés :
pass
# détermination de d ( delta ) et des points correspondant de A ou de B :
pass
# Construction du tableau des points appartenant à la bande verticale à considérer ( pas évident, attention ! ) :
pass
# Calcul des distances entre les points de cette bande et les 7 suivants dans le tableau Q :
pass
# renvoi des deux points les plus proches et de la distance minimale :
pass
Compléter la fonction plus_proches_dpr()
ci-dessous de façon à implémenter l'algorithme décrit précédemment. Attention aux paramètres et aux résultats renvoyés par cette fonction.
Cette fonction fera appel à celles que vous aurez écrites auparavant.
Pour la construction des différents tableaux, on aura bien entendu intérêt à utiliser des méthodes par compréhension.
def plus_proches_dpr(Px: list, Py: list)->(tuple, tuple, float):
''' Fonction implémentant l'algorithme de recherche de deux points les plus proches dans un ensemble de points.
Entrées :
Px = tableau des points triés par abscisses croissantes
Py = tableau des points triés par ordonnées croissantes
Sorties :
p1, p2 = les deux points les plus proches
d = la distance entre eux
'''
n = len(Px) # nombre de points
# Cas de base : renvoi des points les plus proches dans A ( ou B ) et de dA ( ou dB )
if n <= 3:
return .....
# Construction des tableaux des points de A et de B :
m = n//2
A = .................................
B = .................................
# Appel récursif de la fonction sur A et B, et récupération des résultats renvoyés :
A1, A2, dA = ................................
B1, B2, dB = ................................
# détermination de d ( delta ) et des points correspondant de A ou de B :
if dA < dB:
p1 = .....
p2 = .....
d = ......
else:
p1 = .....
p2 = .....
d = ....
# Construction du tableau des points appartenant à la bande verticale à considérer ( pas évident, attention ! ) :
Q = .....................................................
# Calcul des distances entre les points de cette bande et les 7 suivants dans le tableau Q :
for i in range(........): # pour chaque point de Q
for j in range(......): # pour chacun des 7 points suivants ( attention aux bornes de la boucle !! )
dmin = ..................... # distance entre les deux points
if dmin < d: # si cette distance est plus petite que d,
p1 = .... # alors on modifie les informations stockées
p2 = .... # sur les points les plus proches
d = .... # et la distance minimale
return .......... # renvoi des deux points les plus proches et de la distance minimale
En déplaçant les lignes dans le code ci-dessous, reconstruire l'implémentation correcte de l'algorithme présenté précédemment.
Tester le bon fonctionnement de la fonction plus_proches_dpr()
:
Avec le tableau t, il n'y aucune différence dans le temps d’exécution des deux versions de la recherche, voire même l'approche DpR est plus lente à cause de ses multiples appels récursifs que l'on ne trouve pas dans la version naïve.
Mais comme d'habitude, la différence de complexité devient notable pour de grandes valeurs de n.
En utilisant le module timeit, comparer le temps d'exécution des deux versions de la recherche des deux points les plus proches.