Logo de kxs.frCours d'informatique pour le lycée et la prépa

Diviser pour régner

Description

Diviser pour régner est une technique algorithmique qui se déroule en trois étapes :

Exemple du tri fusion

Le tri fusion est encore un algorithme de tri. Celui-ci est récursif et utilise, bien sur, la méthode diviser pour régner. Voici un algorithme décrivant le tri fusion :

triFusion(liste):
    si la liste contient un élément ou moins:
		retourner liste
    sinon:
		listegauche = demi-liste gauche de liste
		listedroite = demi-liste droite de liste
		retourner combinaison(triFusion(listegauche), triFusion(listedroite))

combinaison(listegauche, listedroite):
	# listegauche et listedroite sont des listes ordonées
	listeretour = liste vide
	indicelistegauche = 1
	indicelistedroite = 1
	tant que indicelistegauche ≤ taille(listegauche) et indicelistedroite ≤ taille(listedroite):
		si listegauche[indicelistegauche] < listedroite[indicelistedroite]:
			on ajoute listegauche[indicelistegauche] à listeretour
			indicelistegauche = indicelistegauche + 1
		sinon:
			on ajoute listedroite[indicelistedroite] à listeretour
			indicelistedroite = indicelistedroite + 1
	renvoyer listeretour + la fin de la liste non totalement explorée

Voici un schéma montrant le déroulement de cet algorithme sur la liste [4, 3, 8, 2, 7, 1, 5] :

1) Quelle ligne de l'algorithme fait qu'il est récursif ?

c'est la ligne « retourner combinaison(triFusion(listegauche), triFusion(listedroite)) » car la fonction fait appel à elle-même.

2) Expliquer en une phrase ce que fait la fonction combinaison.

Elle assemble deux tableaux triés de façon à avoir un seul tableau, lui aussi, trié.

3) Proposez une implémentation en Python de la fonction combinaison.

def combinaison(l1, l2):
    """
    Permet de combiner deux listes ordonnées pour créer une liste ordonnée
    """
    l = []
    i1 = 0
    i2 = 0
    while i1 < len(l1) and i2 < len(l2):
        if l1[i1] < l2[i2]:
            l.append(l1[i1])
            i1 = i1 + 1
        else:
            l.append(l2[i2])
            i2 = i2 + 1
    return l + l1[i1:] + l2[i2:] # L'une des deux est vide

4) Proposez enfin une implémentation en Python du tri fusion.

def triFusion(l):
    """
    Fonction implémentant l'algorithme de tri-fusion
    """
    if len(l) == 1:
        return l
    else:
        m = len(l) // 2
        return combinaison(triFusion(l[:m]),triFusion(l[m:]))

def combinaison(l1, l2):
    """
    Permet de combiner deux listes ordonnées pour créer une liste ordonnée
    """
    l = []
    i1 = 0
    i2 = 0
    while i1 < len(l1) and i2 < len(l2):
        if l1[i1] < l2[i2]:
            l.append(l1[i1])
            i1 = i1 + 1
        else:
            l.append(l2[i2])
            i2 = i2 + 1
    return l + l1[i1:] + l2[i2:] # L'une des deux est vide

Application : rotation d'image

Il est possible d'appliquer la méthode diviser pour régner à la rotation d'une image carrée. Nous ferons tourner l'image de 90° dans le sens trigonométrique. La méthode est illustrée sur le schéma ci-dessous :

Nous utiliserons le module PIL déjà utilisé l'année dernière. L'image à faire tourner sera la joconde. Elle est carrée avec une taille égale à une puissance de 2. Voici la structure que vous pourrez utiliser :

from PIL import Image

img = Image.open("joconde.png")
# Affiche l'image originale
img.show()

def echangePixels(image,pixel_a,pixel_b):
	"""
	Échange la couleur des deux pixels pixel_a et pixel_b.
	pixel_a : tuple de coordonnées
	pixel_b : tuple de coordonnées

	À compléter !
	"""
	…

def tourne(image):
	"""
	Lance la fonction récursive sur l'image originale.

	Fonction complète.
	"""
    tourneRecursif(image,(0,0),image.size[0])

def echangeQuadrant(image,coinHautGauche_a,coinHautGauche_b,n):
	"""
	Échange les pixels entre les deux quadrants de taille n
	définis par leur coin haut gauche.

	coinHautGauche_a : tuple des coordonnées du coin supérieur gauche du quadrant a
	coinHautGauche_b : tuple des coordonnées du coin supérieur gauche du quadrant b
	n : taille des deux quadrants

	À compléter !
	"""
	…

def tourneRecursif(image,coinHautGauche,taille):
	"""
	Applique une rotation du quadrant de l'image défini par
	son coin supérieur gauche et sa taille.

	coinHautGauche : tuple des coordonnées du coin supérieur gauche du quadrant
	taille : taille du quadrant à faire tourner

	À compléter !
	"""
	…

# Lance le programme
tourne(img)
# Affiche l'image
img.show()

Vous chercherez vous-même sur la documentation de PIL les méthodes à utiliser pour accéder aux pixels de l'image.

from PIL import Image

img = Image.open("joconde.png")
# Affiche l'image originale
img.show()

def echangePixels(image,pixel_a,pixel_b):
	"""
	Échange la couleur des deux pixels pixel_a et pixel_b.
	pixel_a : tuple de coordonnées
	pixel_b : tuple de coordonnées

	À compléter !
	"""
	couleur_a, couleur_b = image.getpixel(pixel_a),image.getpixel(pixel_b)
    image.putpixel(pixel_a,couleur_b)
    image.putpixel(pixel_b,couleur_a)

def tourne(image):
	"""
	Lance la fonction récursive sur l'image originale.

	Fonction complète.
	"""
    tourneRecursif(image,(0,0),image.size[0])

def echangeQuadrant(image,coinHautGauche_a,coinHautGauche_b,n):
	"""
	Échange les pixels entre les deux quadrants de taille n
	définis par leur coin haut gauche.

	coinHautGauche_a : tuple des coordonnées du coin supérieur gauche du quadrant a
	coinHautGauche_b : tuple des coordonnées du coin supérieur gauche du quadrant b
	n : taille des deux quadrants

	À compléter !
	"""
	xa, ya = coinHautGauche_a
    xb, yb = coinHautGauche_b
    for i in range(n):
        for j in range(n):
            echangePixels(image,(xa+i,ya+j),(xb+i,yb+j))

def tourneRecursif(image,coinHautGauche,taille):
	"""
	Applique une rotation du quadrant de l'image défini par
	son coin supérieur gauche et sa taille.

	coinHautGauche : tuple des coordonnées du coin supérieur gauche du quadrant
	taille : taille du quadrant à faire tourner

	À compléter !
	"""

	if taille < 2: return
    x,y = coinHautGauche
    n = taille // 2
    tourneRecursif(image,(x,y),n)
    tourneRecursif(image,(x+n,y),n)
    tourneRecursif(image,(x+n,y+n),n)
    tourneRecursif(image,(x,y+n),n)
    echangeQuadrant(image,(x,y),(x+n,y),n)
    echangeQuadrant(image,(x+n,y),(x+n,y+n),n)
    echangeQuadrant(image,(x+n,y+n),(x,y+n),n)

# Lance le programme
tourne(img)
# Affiche l'image
img.show()

Application : minimum et maximum d'un tableau

L'objectif est d'écrire une fonction récursive min_et_max(T, a, b) renvoyant le couple (minimum, maximum) du tableau T[a:b].

Un algorithme du type « diviser pour régner » permet de déterminer ce couple en exploitant le fait qu’une seule comparaison suffit pour obtenir à la fois le minimum et le maximum d'un tableau de taille 2. Voici cet algorithme :

VARIABLES
T : tableau

DEBUT
si T ne contient qu'un élément:
	retourner (T[0], T[0])
sinon si T contient deux éléments:
	retourner le couple (minimum, maximum)
sinon:
	tableaugauche = demi-tableau gauche de T
	tableaudroit = demi-tableau droit de T
	calculer récursivement le couple (min, max) des deux tableaux
	fusionner les résultats précédents
	retourner le couple (minimum, maximum)
FIN

5) Écrire la fonction min_et_max(T) en Python.

6) La tester sur le tableau T = [1, 6, -5, 8, 14, -2, -7, 21] en lancant la commande min_et_max(T)

7) Combien cette fonction utilise de comparaisons pour un tableau de 4 éléments ?

8) Combien un algorithme classique utilise de comparaisons pour un tableau de 4 éléments ?

9) Répondez aux deux questions précédentes pour des tableaux de 8, 16 et 32 éléments. Que pouvez-vous conclure sur la complexité de cet algorithme ?