C3 — Recherche et trisDans le dialogue d’impression, choisissez « Enregistrer au format PDF » comme destination.
Retour

Licence 1 · Algorithmique 1

Cours 3Recherche et tris

Résoudre deux problèmes classiques et comparer les stratégies possibles.

2 chapitres · 55 min de travail estimé

  1. 1. Recherche25 min
  2. 2. Tris élémentaires30 min

Chapitre 1 · 25 min

Recherche

Comparer deux stratégies de recherche et mesurer leur coût : 1000 contre 10.

« Cette valeur est-elle dans le tableau, et si oui, où ? » C'est le problème le plus banal de l'informatique, et le premier où deux algorithmes corrects se distinguent nettement par leur coût. C'est aussi pour cela qu'on le traite ici : à partir de maintenant, être juste ne suffit plus.

Une convention pour toute la leçon : la recherche renvoie l'indice de la valeur trouvée, ou −1 si elle est absente. On renvoie l'indice et non la valeur, parce que l'appelant sait déjà ce qu'il cherchait — ce qui l'intéresse, c'est .

La recherche séquentielle

La stratégie évidente : regarder les cases une par une jusqu'à trouver.

Fonction RechercheSequentielle(T, n, cible) : entier    Pour i de 0 à n − 1 Faire        Si T[i] = cible Alors            Retourner i        FinSi    FinPour    Retourner −1FinFonction

Animation · 6 étapes

Recherche séquentielle de la valeur 8

  1. Le tableau n'est pas triéAucune information ne permet de sauter des cases : la seule stratégie possible est de les regarder une par une, dans l'ordre.
  2. T[0] = 12 ≠ 8Une comparaison consommée, aucune information gagnée sur la suite.
  3. T[1] = 5 ≠ 8On continue. 5 est plus petit que 8, mais cela n'apprend rien : le tableau est en désordre.
  4. T[2] = 20 ≠ 8Troisième échec.
  5. T[3] = 8 : trouvé !On renvoie 3, l'INDICE, pas la valeur : l'appelant sait déjà ce qu'il cherchait, ce qui l'intéresse c'est où.
  6. Et si la valeur n'y était pas ?Il aurait fallu épuiser les 7 cases avant de pouvoir renvoyer −1. Pire cas : n comparaisons, une par case.

Elle a deux qualités réelles : elle est simple, et elle fonctionne sur n'importe quel tableau, trié ou non. Son coût, en revanche, se lit directement sur l'animation. Dans le meilleur des cas la valeur est en case 0 et une comparaison suffit. Dans le pire, elle est en dernière position ou absente, et il faut les n comparaisons. En moyenne, environ n/2.

Ce qui compte, c'est le pire cas : n comparaisons pour n cases. Doubler la taille du tableau double le travail.

Quiz · 1 question

Dans la recherche séquentielle sur un tableau NON trié, pourquoi ne peut-on pas s'arrêter dès qu'on rencontre une valeur plus grande que la cible ?

  • Parce que la comparaison coûterait trop cher
  • Parce que dans un tableau en désordre, une valeur plus grande ne dit rien sur ce qui suit
  • Parce qu'on ne peut comparer que l'égalité

Réponse : Sur [12, 5, 20, 8], rencontrer 20 alors qu'on cherche 8 n'autorise aucune conclusion : 8 est juste derrière. Chaque comparaison ne renseigne que sur UNE case. C'est exactement ce que le tri va changer.

Pourquoi un tableau trié change tout

Sur un tableau trié, comparer la cible à une seule case renseigne d'un coup sur toutes les autres. Si T[5] vaut 17 et qu'on cherche 23, alors 23 ne peut être ni en T[0], ni en T[1]… ni en T[5] : tout ce qui est à gauche est plus petit que 17, donc plus petit que 23.

Une comparaison, six cases éliminées. C'est cette information gratuite — offerte par le tri, pas par l'algorithme de recherche — que la dichotomie exploite jusqu'au bout.

La recherche dichotomique

L'idée : au lieu de commencer par le début, commencer par le milieu, et recommencer sur la moitié qui reste. Deux bornes délimitent la zone encore candidate.

Fonction Dichotomique(T, n, cible) : entier    g ← 0    d ← n − 1    TantQue g ≤ d Faire        m ← (g + d) ÷ 2        Si T[m] = cible Alors            Retourner m        SinonSi T[m] < cible Alors            g ← m + 1        Sinon            d ← m − 1        FinSi    FinTantQue    Retourner −1FinFonction

Animation · 5 étapes

Recherche dichotomique de la valeur 23 (tableau trié)

  1. Le tableau est trié — tout changeComparer la cible à une seule case renseigne d'un coup sur toutes celles qui sont à sa gauche et à sa droite. C'est cette information gratuite qu'on va exploiter.
  2. m = (0 + 10) / 2 = 5 → T[5] = 1717 < 23 : la cible est forcément à droite. Les six cases de gauche sont éliminées par une seule comparaison.
  3. m = (6 + 10) / 2 = 8 → T[8] = 3131 > 23 : cette fois on garde la gauche. d recule à 7. Il ne reste que deux candidates.
  4. m = (6 + 7) / 2 = 6 → T[6] = 20Division ENTIÈRE : 13 / 2 donne 6, pas 6,5. Un indice ne peut pas être à virgule. 20 < 23, donc g avance à 7.
  5. m = 7 → T[7] = 23 : trouvé4 comparaisons pour 11 cases. La recherche séquentielle en aurait fait 8. L'écart n'est pas encore spectaculaire ici — il le devient à 1000 cases.

Trois détails du code méritent qu'on s'y arrête, parce que chacun est une source de bug.

÷ est la division entière : (6 + 7) ÷ 2 vaut 6, pas 6,5. Un indice ne peut pas être à virgule.

Les + 1 et − 1 dans g ← m + 1 et d ← m − 1 ne sont pas décoratifs. On vient de tester la case m : la garder dans la zone candidate ferait retester indéfiniment la même case, et la boucle ne s'arrêterait jamais.

Enfin, la condition g ≤ d avec un ou égal : quand il ne reste qu'une seule case candidate, g et d sont égaux et cette case doit encore être testée. Avec <, on manquerait toutes les valeurs situées en dernière position de l'intervalle.

Quiz · 1 question

Que se passe-t-il si on écrit g ← m au lieu de g ← m + 1 ?

  • Le résultat est le même, c'est une question de style
  • L'algorithme peut boucler indéfiniment sur la même case
  • L'algorithme renvoie toujours −1

Réponse : Quand il reste deux cases, m vaut g, et g ← m ne fait pas avancer la borne : la zone candidate ne rétrécit plus. La boucle infinie de la leçon 4, dans un algorithme par ailleurs correct.

Compter les comparaisons

Comparons les deux stratégies sur un tableau trié de 1000 valeurs, dans le pire cas.

La recherche séquentielle fait 1000 comparaisons. La dichotomie divise la zone candidate par deux à chaque tour : 1000, puis 500, 250, 125, 63, 32, 16, 8, 4, 2, 1. Onze nombres, donc 10 comparaisons pour arriver à une seule case.

Graphique

Retrouver une valeur parmi 1000 — nombre de comparaisons (pire cas)

  • Séquentielle : 10001000
  • Dichotomique : 1010
La barre de droite se distingue à peine : 10 comparaisons contre 1000. Et le rapport se creuse — pour un million de valeurs, ce serait 1 000 000 contre 20.

Le nombre de divisions par deux nécessaires pour ramener nn à 1, c'est exactement le logarithme en base 2 :

log2(1000)9,97car210=1024\log_2(1000) \approx 9{,}97 \qquad \text{car} \qquad 2^{10} = 1024

Et cet écart ne se contente pas de rester : il explose.

Taille du tableauSéquentielleDichotomique
1001007
1 0001 00010
1 000 0001 000 00020
1 000 000 0001 000 000 00030

Lisez la dernière ligne : multiplier la taille des données par un milliard n'ajoute que trente comparaisons. C'est la différence entre un algorithme qui suit la croissance des données et un algorithme qui s'en moque.

Quiz · 1 question

Un tableau trié passe de 1000 à 2000 valeurs. Combien de comparaisons supplémentaires coûte la recherche dichotomique dans le pire cas ?

  • Environ 1000 de plus
  • Deux fois plus, soit 20
  • Une seule de plus, soit 11

Réponse : Doubler la taille ajoute exactement une division par deux, donc une comparaison. C'est la signature du logarithme : c'est le nombre de DOUBLEMENTS qui compte, pas la taille elle-même.

Le prix du tri

Un point d'honnêteté avant de conclure : la dichotomie exige un tableau trié, et trier a un coût. Pour une recherche unique sur des données en désordre, trier puis chercher revient plus cher que chercher séquentiellement.

Le calcul devient gagnant dès qu'on cherche plusieurs fois dans les mêmes données : le tri est payé une fois, les recherches profitent toutes du gain. C'est exactement ce que fait un index de base de données — et c'est la raison pour laquelle la leçon suivante porte sur le tri.

À vous

Exercice de code

Complétez le resserrement de l'intervalle, puis vérifiez le nombre de comparaisons.

Point de départ

// Complétez la dichotomie. Elle renvoie l'indice de la cible, ou -1,
// et compte ses comparaisons pour qu'on vérifie le coût annoncé en cours.
const T = [3, 5, 8, 9, 12, 17, 20, 23, 31, 42, 55];

function dichotomique(t, cible) {
  let g = 0;
  let d = t.length - 1;
  let comparaisons = 0;

  while (g <= d) {
    const m = Math.floor((g + d) / 2);
    comparaisons++;

    if (t[m] === cible) return { indice: m, comparaisons };
    // à compléter : si t[m] est trop petit, la cible est à DROITE (g bouge) ;
    // sinon elle est à GAUCHE (d bouge).
    break; // ← à supprimer : il n'est là que pour éviter la boucle infinie
  }

  return { indice: -1, comparaisons };
}

console.log(dichotomique(T, 23));  // attendu : indice 7, 4 comparaisons
console.log(dichotomique(T, 3));   // attendu : indice 0
console.log(dichotomique(T, 4));   // attendu : indice -1

Solution

const T = [3, 5, 8, 9, 12, 17, 20, 23, 31, 42, 55];

function dichotomique(t, cible) {
  let g = 0;
  let d = t.length - 1;
  let comparaisons = 0;

  while (g <= d) {
    // Division ENTIÈRE : un indice ne peut pas valoir 6,5.
    const m = Math.floor((g + d) / 2);
    comparaisons++;

    if (t[m] === cible) return { indice: m, comparaisons };
    if (t[m] < cible) g = m + 1;  // le + 1 évite de retester m indéfiniment
    else d = m - 1;
  }

  return { indice: -1, comparaisons };
}

console.log(dichotomique(T, 23));
console.log(dichotomique(T, 3));
console.log(dichotomique(T, 4));

À retenir

Flashcards · 2 cartes

Quelle hypothèse la recherche dichotomique exige-t-elle ?
Que le tableau soit trié. Sans cette hypothèse, comparer à la case du milieu ne permet d'éliminer aucune autre case.
Combien de comparaisons pour chercher parmi un million de valeurs triées ?
Une vingtaine, car 2^20 dépasse un million. Contre un million pour la recherche séquentielle dans le pire cas.

Chapitre 2 · 30 min

Tris élémentaires

Dérouler le tri par sélection et le tri par insertion, et comprendre leur invariant.

La leçon précédente s'est achevée sur une dette : la dichotomie exige un tableau trié. Il est temps de le trier. Les deux algorithmes de cette leçon ne sont pas les plus rapides — ils sont les plus instructifs, parce qu'on peut les dérouler entièrement à la main.

Ce que trier veut dire

Trier un tableau, c'est le réorganiser pour que T[0] ≤ T[1] ≤ … ≤ T[n − 1]. Deux exigences, pas une seule :

  • l'ordre : chaque case est inférieure ou égale à la suivante ;
  • la permutation : le tableau final contient exactement les mêmes valeurs que le tableau initial, ni plus, ni moins.

La seconde est facile à oublier, et c'est pourtant elle qui interdit les fausses solutions. Un algorithme qui remplirait le tableau de zéros produirait une suite parfaitement croissante — et un résultat évidemment faux.

Les deux tris qui suivent travaillent en place : ils réorganisent le tableau existant sans en créer un second. La seule opération dont ils disposent pour cela est l'échange de deux cases — exactement le schéma tmp de la leçon 2.

Le tri par sélection

L'idée tient en une phrase : chercher le plus petit élément restant, et l'amener à sa place définitive.

Pour i de 0 à n − 2 Faire    indiceMin ← i    Pour j de i + 1 à n − 1 Faire        Si T[j] < T[indiceMin] Alors            indiceMin ← j        FinSi    FinPour    tmp ← T[i]    T[i] ← T[indiceMin]    T[indiceMin] ← tmpFinPour

Deux boucles imbriquées : celle de i compte les tours, celle de j cherche le minimum dans ce qui reste. On mémorise l'indice du minimum, pas sa valeur — c'est l'indice qui servira à échanger.

Animation · 9 étapes

Tri par sélection de [5, 2, 9, 1, 6]

  1. Le tableau de départPrincipe : à chaque tour, chercher le plus petit élément de la partie non triée et l'amener à sa place définitive.
  2. Tour 1 — le minimum est 1, en case 3Le trouver a demandé 4 comparaisons : il faut voir toutes les cases restantes pour être sûr d'avoir le plus petit.
  3. Tour 1 — on échange T[0] et T[3]1 est à sa place DÉFINITIVE : plus rien de plus petit ne reste. Le 5 qui occupait la case 0 est parti en case 3.
  4. Tour 2 — le minimum restant est 2, en case 1On ne regarde plus la case 0 : elle est définitive.
  5. Tour 2 — il est déjà à sa placeL'échange T[1] ↔ T[1] a bien lieu dans la plupart des écritures : il ne coûte rien et évite un test supplémentaire.
  6. Tour 3 — le minimum restant est 5, en case 3Deux comparaisons cette fois : la partie non triée rétrécit à chaque tour.
  7. Tour 3 — on échange T[2] et T[3]Le 9 est renvoyé plus loin. Notez qu'il « saute » : le tri par sélection n'est pas stable.
  8. Tour 4 — le minimum restant est 6, en case 4Une seule comparaison suffit : il ne reste que deux cases.
  9. Tour 4 — on échange T[3] et T[4] : c'est finin − 1 tours suffisent : dès que les 4 premières cases sont définitives, la dernière l'est forcément, puisqu'il ne reste qu'une valeur.

L'invariant de ce tri — la propriété vraie à la fin de chaque tour — est le suivant : après le tour i, les cases 0 à i contiennent les i + 1 plus petites valeurs, à leur place définitive. Définitive est le mot fort : ces cases ne bougeront plus jamais.

Remarquez aussi qu'on s'arrête à n − 2 : quand les n − 1 premières cases sont définitives, la dernière l'est forcément, puisqu'il ne reste qu'une valeur pour l'occuper.

Le tri par insertion

Autre idée, celle du joueur de cartes : prendre l'élément suivant et le glisser à sa place parmi ceux déjà rangés.

Pour i de 1 à n − 1 Faire    valeur ← T[i]    j ← i − 1    TantQue j ≥ 0 ET T[j] > valeur Faire        T[j + 1] ← T[j]        j ← j − 1    FinTantQue    T[j + 1] ← valeurFinPour

Animation · 9 étapes

Tri par insertion de [5, 2, 9, 1, 6]

  1. Le tableau de départPrincipe : comme des cartes en main. La case 0 seule est déjà « triée » — un élément unique l'est toujours.
  2. On prend T[1] = 2On le retire mentalement du tableau et on cherche où le glisser à gauche.
  3. 2 se glisse avant 55 est décalé d'une case vers la droite pour libérer la place. Un décalage, une insertion.
  4. On prend T[2] = 9La partie gauche [2, 5] est triée. Où va 9 ?
  5. 9 reste où il estUne seule comparaison (9 > 5) et c'est réglé : aucun décalage. Sur un tableau déjà trié, ce tri ne fait que n − 1 comparaisons.
  6. On prend T[3] = 1Le plus petit élément du tableau, tout au fond. Ça va coûter cher.
  7. 1 remonte jusqu'en tête2, 5 et 9 sont tous décalés d'un cran. Trois décalages pour une insertion : c'est le pire cas de ce tri, un tableau à l'envers.
  8. On prend T[4] = 6Dernier élément à insérer dans [1, 2, 5, 9].
  9. 6 se glisse entre 5 et 9 : c'est finiDifférence essentielle avec la sélection : ici la partie gauche était triée mais PAS définitive — le 9 vient encore de bouger au dernier tour.

La boucle démarre à 1, pas à 0 : une case seule est déjà triée, il n'y a rien à faire au premier élément. La boucle interne ne cherche pas, elle décale — chaque élément trop grand recule d'une case pour ouvrir un trou, et la valeur mise de côté vient s'y loger.

Un détail de la condition j ≥ 0 ET T[j] > valeur mérite l'attention : l'ordre des deux tests n'est pas indifférent. Le premier protège le second. Quand j atteint −1, la machine constate que j ≥ 0 est FAUX et n'évalue même pas T[j], ce qui serait un débordement d'indice. Cette évaluation paresseuse est garantie par tous les langages courants, mais elle vous impose de mettre le garde-fou en premier.

Comparer les deux sur le même tableau

Les deux animations partent du même tableau [5, 2, 9, 1, 6]. Rejouez-les côte à côte : le comportement diffère profondément.

Tri par sélectionTri par insertion
Invariantles i premières cases sont définitivesles i premières cases sont triées entre elles
Opération de baseéchanger deux casesdécaler d'une case
Nombre de comparaisonstoujours le même, quel que soit le tableaudépend du désordre initial
Tableau déjà triémême travail completn − 1 comparaisons, aucun décalage
Nombre d'échangesau plus n − 1autant que de décalages

La différence d'invariant est le point le plus important de la leçon. Dans le tri par sélection, une case placée ne bouge plus jamais : elle est définitive. Dans le tri par insertion, la partie gauche est triée mais pas définitive — regardez le 9 dans l'animation, il est encore déplacé au dernier tour.

Quiz · 1 question

Sur un tableau DÉJÀ trié, lequel des deux fait le moins de travail ?

  • Le tri par sélection, car il fait peu d'échanges
  • Le tri par insertion, car chaque élément est bien placé du premier coup
  • Les deux font exactement le même travail

Réponse : Le tri par insertion compare chaque élément à son voisin de gauche, constate qu'il est plus grand, et n'effectue aucun décalage : n − 1 comparaisons au total. Le tri par sélection, lui, cherche quand même le minimum dans toute la partie restante à chaque tour : il ne profite d'aucun ordre préexistant.

Quiz · 1 question

Dans le tri par sélection, pourquoi mémorise-t-on l'indice du minimum plutôt que sa valeur ?

  • Parce qu'un indice occupe moins de place en mémoire
  • Parce que l'échange a besoin de savoir OÙ se trouve le minimum pour le déplacer
  • Parce que la valeur pourrait changer pendant la boucle

Réponse : Connaître la valeur 1 ne dit pas où elle est. L'échange T[i] ↔ T[indiceMin] a besoin de la position. C'est une constante de l'algorithmique sur tableaux : on manipule des indices, les valeurs suivent.

Quiz · 1 question

Le tri par sélection sur 5 éléments effectue 4 + 3 + 2 + 1 = 10 comparaisons. Combien en fera-t-il sur 10 éléments ?

  • 20deux fois plus
  • 45somme des entiers
  • 100n fois n

Réponse : 9 + 8 + … + 1 = 45, soit n(n − 1)/2 — la formule de la somme des entiers vue à la leçon 4. Doubler la taille a plus que doublé le travail : il a été multiplié par 4,5. C'est ce comportement que la leçon 8 va nommer.

À vous

Exercice de code

Complétez la recherche du minimum et l'échange. L'échange reprend exactement le schéma de la leçon 2.

Point de départ

// Complétez le tri par sélection.
// Rappel de l'invariant : après le tour i, les cases 0 à i sont à leur
// place DÉFINITIVE.
function triSelection(t) {
  const a = [...t]; // on travaille sur une copie, l'original reste intact

  for (let i = 0; i < a.length - 1; i++) {
    let indiceMin = i;

    for (let j = i + 1; j < a.length; j++) {
      // à compléter : retenir l'indice du plus petit élément restant
    }

    // à compléter : échanger a[i] et a[indiceMin]
  }

  return a;
}

console.log(triSelection([5, 2, 9, 1, 6]));  // attendu : [1, 2, 5, 6, 9]
console.log(triSelection([1, 2, 3]));        // attendu : [1, 2, 3]

Solution

function triSelection(t) {
  const a = [...t];

  for (let i = 0; i < a.length - 1; i++) {
    // On mémorise l'INDICE, pas la valeur : c'est lui qui servira à échanger.
    let indiceMin = i;

    for (let j = i + 1; j < a.length; j++) {
      if (a[j] < a[indiceMin]) indiceMin = j;
    }

    const tmp = a[i];
    a[i] = a[indiceMin];
    a[indiceMin] = tmp;
  }

  // n - 1 tours suffisent : la dernière case est forcément à sa place.
  return a;
}

console.log(triSelection([5, 2, 9, 1, 6]));
console.log(triSelection([1, 2, 3]));

À retenir

Flashcards · 2 cartes

Quel est l'invariant du tri par sélection ?
Après le tour i, les cases 0 à i contiennent les plus petites valeurs, à leur place DÉFINITIVE : elles ne bougeront plus.
Quel est l'invariant du tri par insertion ?
Après le tour i, les cases 0 à i sont triées entre elles — mais pas définitives : un élément plus petit peut encore venir s'y insérer.