Files
TermNSI/Recursivité/Diviser_pour_regner/EXERCICES.md
T

6.3 KiB

Exercices : Diviser pour régner


Les trois premiers exercices se traitent sur papier avant de toucher au clavier. Un algorithme que vous ne savez pas dérouler à la main est un algorithme que vous ne saurez pas déboguer à l'écran. Les derniers sont volontairement plus exigeants.


Exercice 1 : Dérouler le tri fusion

On trie la liste suivante avec le tri fusion vu en cours :

[5, 3, 8, 1, 9, 2, 7]

Questions :

  1. Dessiner l'arbre des divisions, jusqu'à n'obtenir que des listes d'un seul élément.
  2. Écrire, étage par étage, les fusions successives qui reconstituent la liste triée.
  3. Combien d'étages de division compte-t-on pour une liste de 8 éléments ? De 16 éléments ? De 1024 éléments ?
  4. En déduire la relation entre le nombre d'étages et la taille de la liste. Quel est le rapport avec la complexité annoncée en cours ?

Exercice 2 : La fusion vue de près

On reprend la fonction fusionner du cours, celle qui réunit deux listes déjà triées.

Questions :

  1. Dérouler l'appel fusionner([2, 5, 9], [1, 3, 12]) dans un tableau, en notant à chaque tour de boucle les valeurs de i, de j et de resultat.
  2. Combien de comparaisons entre éléments cette fusion a-t-elle demandées ?
  3. Donner deux listes de trois éléments chacune dont la fusion ne demande que trois comparaisons. Expliquer ce qui se produit.
  4. Quel est le nombre maximal de comparaisons pour fusionner une liste de p éléments et une liste de q éléments ? Justifier.
  5. La fonction se termine par return resultat + gauche[i:] + droite[j:]. Pourquoi peut-on ajouter d'un seul coup tout ce qui reste, sans le comparer à quoi que ce soit ?

Exercice 3 : Recherche dichotomique

Chercher une valeur dans une liste triée est le deuxième exemple canonique du diviser pour régner : on compare avec l'élément du milieu, et on élimine la moitié de la liste à chaque étape.

Questions :

  1. Écrire une fonction récursive recherche(tab, x) qui renvoie True si x figure dans la liste triée tab, et False sinon.
  2. Dérouler recherche([1, 4, 6, 9, 12, 15, 20], 12). Quelles sous-listes sont successivement examinées, et combien d'appels sont nécessaires ?
  3. On veut maintenant connaître l'indice de x, et plus seulement sa présence. Expliquer pourquoi la fonction de la question 1, qui découpe la liste en tranches, ne permet pas d'y répondre directement.
  4. Écrire une fonction indice(tab, x, debut, fin) qui renvoie l'indice de x dans tab, ou -1 s'il est absent, sans jamais découper la liste.
  5. Sur une liste d'un million d'éléments, combien d'appels au maximum ? Combien pour une liste deux fois plus longue ?

Exercice 4 : Le maximum, avec et sans diviser pour régner

Questions :

  1. Écrire une fonction récursive maximum(tab) qui applique le schéma du cours : si la liste ne contient qu'un élément, le renvoyer ; sinon la couper en deux, chercher le maximum de chaque moitié, et renvoyer le plus grand des deux résultats.
  2. Combien de comparaisons cette fonction effectue-t-elle sur une liste de 8 éléments ? Et la boucle for que vous écririez spontanément ?
  3. Le découpage apporte-t-il un gain ici ? Justifier.
  4. Un tel découpage ne fait donc pas toujours gagner. À quelle condition sur l'étape « combiner » la méthode devient-elle intéressante ? Comparer avec le tri fusion.

Exercice 5 : Compter les occurrences dans une liste triée

Un serveur de jeu conserve les scores de ses 500 000 joueurs dans une liste triée par ordre croissant. Plusieurs joueurs peuvent avoir exactement le même score, et l'on veut savoir combien.

Questions :

  1. Écrire une fonction compter_naif(tab, x) qui parcourt la liste et compte les occurrences de x. Quelle est sa complexité ?
  2. Écrire une fonction premiere_occurrence(tab, x) qui renvoie, par dichotomie, l'indice de la première apparition de x, ou -1 s'il est absent. On prendra garde à ne pas s'arrêter à la première correspondance trouvée.
  3. Écrire de même derniere_occurrence(tab, x).
  4. En déduire une fonction compter(tab, x) et donner sa complexité.
  5. Sur les 500 000 scores, combien d'opérations séparent les deux méthodes, en ordre de grandeur ?

Exercice 6 : Compter les inversions

Une inversion dans une liste tab est un couple d'indices (i, j) tel que i < j et tab[i] > tab[j], autrement dit deux éléments rangés dans le mauvais ordre l'un par rapport à l'autre. Le nombre d'inversions mesure le désordre d'une liste : il vaut 0 pour une liste triée, et il est maximal pour une liste rangée à l'envers. Les sites de recommandation s'en servent pour estimer à quel point deux classements se ressemblent.

Questions :

  1. Donner les inversions de la liste [2, 4, 1, 3, 5]. Combien y en a-t-il ?
  2. Quelle liste de n éléments en compte le plus ? Combien exactement ?
  3. Écrire une fonction inversions_naif(tab) qui les compte avec deux boucles imbriquées. Quelle est sa complexité ?
  4. On veut faire mieux en s'appuyant sur le tri fusion. Pendant la fusion de gauche et de droite, toutes deux déjà triées, on prend parfois un élément de droite alors qu'il reste encore des éléments dans gauche. Combien d'inversions ce seul choix révèle-t-il d'un coup ? Justifier soigneusement.
  5. Écrire fusionner_et_compter(gauche, droite) qui renvoie le couple formé de la liste fusionnée et du nombre d'inversions découvertes pendant cette fusion.
  6. En déduire tri_fusion_inversions(tab) qui renvoie le couple formé de la liste triée et du nombre total d'inversions. Attention à ne pas oublier les inversions comptées dans chaque moitié.
  7. Quelle est la complexité obtenue ? Sur une liste de 100 000 éléments, quel facteur sépare cette version de celle de la question 3 ?

Auteur : Florian Mathieu

Licence CC BY-SA

Licence Creative Commons
Ce cours est mis à disposition selon les termes de la Licence Creative Commons Attribution - Partage dans les Mêmes Conditions 4.0 International.