Aller au contenu

Exercices

LINFO1121Exercices

Le but de cette page est surtout de pouvoir refaire les bons exercices pendant le blocus. On garde donc une courte remise en route théorique, puis uniquement les implémentations qui valent la peine d’être recodées.

Qu’est-ce qu’un Abstract Data Type (ADT) ? Quelle différence fais-tu entre l’ADT et son implémentation concrète en Java ?

Où placer le top dans une singly linked list pour obtenir des push et pop efficaces ? Quelle complexité obtient-on ?

Pourquoi ce parcours peut-il être mauvais ?

for (int i = 0; i < list.size(); i++) {
process(list.get(i));
}

Quel pattern faut-il utiliser à la place ?

Pourquoi un tableau dynamique peut-il avoir un push en Θ(1)\Theta(1) amorti, alors qu’un redimensionnement coûte ponctuellement Θ(n)\Theta(n) ?

Voir les solutions


Pendant le blocus, refaire les exercices suivants sans regarder la solution au départ.

À refaire entièrement.

Points à maîtriser :

  • invariant entre les deux queues ;
  • transformation FIFO → comportement LIFO ;
  • push, pop, peek ;
  • complexité de chaque opération.

À refaire entièrement.

Points à maîtriser :

  • indices globaux sur plusieurs segments ;
  • parcours d’un segment au suivant ;
  • implémentation de Iterable ;
  • itérateur fail-fast ;
  • gestion correcte des cas limites.

À refaire entièrement.

Points à maîtriser :

  • invariant circulaire ;
  • manipulation des références ;
  • enqueue ;
  • remove(index) ;
  • itérateur fail-fast.

Un ADT définit les données manipulées, les opérations disponibles et leur comportement, sans fixer la représentation interne.

Une implémentation choisit ensuite comment réaliser ce contrat : liste chaînée, tableau dynamique, etc.

Dans une singly linked list, placer le top en tête permet de modifier seulement quelques références.

node.next = first;
first = node;

On obtient ainsi push et pop en Θ(1)\Theta(1).

Placer le top en fin peut imposer un parcours pour retrouver le prédécesseur, donc Θ(n)\Theta(n).

Sur une LinkedList, get(i) peut nécessiter un parcours de la liste. Répéter cet accès pour chaque indice peut donc mener à Θ(n2)\Theta(n^2).

Préférer :

for (E x : list) {
process(x);
}

ou un Iterator, qui parcourt la liste en Θ(n)\Theta(n).

Quand un tableau dynamique double sa capacité, certains push coûtent Θ(n)\Theta(n), mais ces redimensionnements deviennent de plus en plus espacés.

Les copies successives ont un coût total de l’ordre de :

1+2+4+8+⋯<2n1 + 2 + 4 + 8 + \dots < 2n

Sur nn insertions, le coût total reste donc Θ(n)\Theta(n), soit Θ(1)\Theta(1) amorti par insertion.