Vous êtes sur la page 1sur 25

Algorithmique 

: synthèse
Chapitre 1 : Les tableaux à 2 dimensions
1.1) Définition :
La dimension d’un tableau est le nombre d’indices qu’on utilise pour faire référence à un de ses
éléments.
1.2) Notations :

1.2.1) Déclarer :
Pour déclarer un tableau à 2 dimensions, on écrira :

TypeElément[nbLignes x nbColonnes] nomTableau


Ou en java :

TypeElément[][] nomTableau = new TypeElément[nbLignes][nbColonnes];


1.2.2) Utiliser :

Pour accéder à une case du tableau on donnera les deux indices entre crochets. On considère que la
première colonne portent le numéro ( l’indice ) 0

Exemple : print tab[2,4]


1.2.3) Visualiser :

Notez que la vue sous forme de tableau avec des lignes et des colonnes est une vision humaine. Il n’y
a pas de lignes ni de colonnes en mémoire.

Exemple )
Un tableau déclarer :

Int[4x5] nombres
On peut le visualiser en 4 lignes et 5 colonnes

Ou on peut le visualiser en 5 lignes et 4 colonnes

On pourrait aussi visualiser un tableau à deux dimensions comme un tableau à une dimension dont
chacun des éléments est lui-même un tableau à une dimension. La vision << tableau de tableaux >>
donnerait
Dans cette représentation, le tableau nombres est d’abord décomposé à un premier niveau en quatre
éléments auxquels on accède par le premier indice. Ensuite, chaque élément de premier niveau est
décomposé en cinq éléments de deuxième niveau accessibles par le deuxième indice
1.2.5 ) Complexité :
Un algorithme est idéalement :

Correct : il doit résoudre le problème donné, y compris les cas particuliers


Lisible : quand on lit l’algorithme, on doit pouvoir se convaincre qu’il est correct
Optimisé en temps : il doit éviter les instructions inutiles ( utilisation du processeur )
Optimisé en espace : il doit utiliser le moins de mémoire possible ( allocations mémoire)

Pour comparer des algorithmes en terme d’utilisation de temps et d’espace, un outil incontournable
est le complexité.
Instructions élémentaires :

Une des façons les plus simples pour quantifier la complexité temporelle est de compter le nombre
d’instructions élémentaires utilisées par un algorithme.

Complexité dans le pire des cas :


Nous allons contourner le fait que le nombre d’instructions dépend des paramètres en estimant un
nombre d’instructions « dans le pire des cas 2 ». En d’autre termes, si parfois il y a 1 instruction et
parfois il y en a 1 + n + 2mn, alors nous allons considérer que la complexité est de 1 + n + 2mn : c’est
le pire des cas.
Grand 0 :

Pour s’affranchir d’un calcul précis du nombre d’instructions, nous allons nous intéresser à la
complexité en « grand O ». Dans ce qui suit, n représente un entier qui estime la taille des données
d’un algorithme. Il s’agit souvent de la taille d’un tableau donné en paramètre, ou d’un entier donné
en paramètre. La signification de la notation « grand O » est la suivante :

▷ On dit qu’un algorithme (qui dépend a priori d’un entier n) a une complexité en O(1) si le nombre
d’instructions exécutées ne dépend pas de n. On dit qu’il est en temps constant.

▷ On dit qu’un algorithme (qui dépend a priori d’un entier n) a une complexité en O(n) si le nombre
d’instructions exécutées est toujours inférieur à une quantité proportionnelle à n. (Notons qu’un
algorithme en O(1) est également en O(n).)
▷ Similairement un algorithme aura une complexité en O(n 2 ) si le nombre d’instructions exécutées
est toujours inférieur à une quantité proportionnelle à n 2 .
De manière générale, un algorithme a une complexité en O(f(n)) si le nombre d’instructions
exécutées est toujours inférieur à une quantité proportionnelle à f(n).
L’arithmétique des grands 0 :
Une complexité dans le pire des cas d’un algorithme peut être calculée à partir des complexités des
constituants de cet algorithme. Voici quelques règles naturelles. Considérons des algorithmes
dépendant d’un paramètre n :
▷ A1 en O(f(n)),

▷ A2 en O(g(n)) et
▷ A3 en O(h(n)).

Concaténation :
Lorsque l’on réalise l’algorithme A1 suivi de l’algorithme A2, l’algorithme résultant est en O(f(n) +
g(n)).
Appel de méthode :

Un appel à une méthode compte pour une instruction, dès lors seule la complexité de la méthode
elle-même rentre en ligne de compte 3 .

1.3) Parcours :
Nos algorithmes sont valables quel que soit le type des éléments. Utilisons un type T, pour désigner
un type quelconque
T[n x m] tab

1.3.1) Parcours d’une dimension :


Certains parcours ne visitent qu’une partie du tableau et ne nécessitent qu’une seule boucle
La complexité d’un tel algorithme est 0(m), où m est le nombre de colonnes

La complexité d’un tel algorithme est 0(n), où n est le nombre de lignes

La diagonale descendante. Si le tableau est carré (n = m) on peut aussi envisager le parcours des deux
diagonales
Pour la diagonale descendante, parfois appelé diagonale principale, les éléments à visiter sont en
coordonnées (0,0), (1,1), (2,2), …, (n – 1, n – 1)

Une seule boucle suffit comme le montre l’algorithme suivant :

Void afficherDiagonaleDescendante(T[n x n] tab)


For i from 0 to n-1

Print tab[i,i]
La diagonale montante. Pour la diagonale montante d’un tableau carré (n = m), représentée ci-
dessous, on peut envisager deux solutions :
 Avec deux indices, ou
 Avec un seule, en se basant sur le fait que
Void afficherDiagonaleMontante(T[n x n] tab)

Int col
Lg = n-1

For col from 0 to n-1


Print tab[lg, col]

Lg = lg – 1

Void afficherDiagonaleMontante(T[n x n] tab)


For col from 0 to n-1

Print tab[n-1-col, col]

1.3.2) Parcours des deux dimensions


Les parcours précédents ne concernaient qu’une partie du tableau. Voyons à présent comment visiter
toutes les cases du tableau

Parcours par lignes et par colonnes. Les deux parcours les plus courants sont les parcours << ligne par
ligne >> et << colonne par colonne >>. Les tableaux suivants montrent dans quel ordre chaque case
est visitée dans ces deux parcours

Le plus simple est d’utiliser deux boucles imbriquées


Il suffit d’inverser les deux boucles pour effectuer un parcours par colonne

Mais on peut obtenir le même résultat avec une seule boucle si l’indice sert juste à compter le
nombre de passages (n∗m) et que les indices de lignes et de colonnes sont gérés manuellement.
L’algorithme suivant montre ce que ça donne pour un parcours ligne par ligne. La solution pour un
parcours colonne par colonne est similaire et laissée en exercice.

L’avantage de cette solution apparaitra quand on verra des situations plus difficiles.

1.3.4) Parcours plus compliqués


Le serpent. Envisageons un parcours plus difficile où on inverse le sens du parcours d’une ligne à
l’autre comme illustré par le tableau suivant :
Chapitre 2 : La liste
2.1) L’idée de liste :

Imaginons que l’on désire manipuler par programme une liste de courses. Cette liste va varier ; sa
taille n’est donc pas fixée. Utiliser un tableau à cet effet n’est pas l’idéal : la taille d’un tableau ne peut
plus changer une fois le tableau créé. On peut bien sûr s’en sortir avec un tableau, mais cela
implique :

▷ de tenir à jour sa taille logique ;


▷ de gérer le cas où le tableau n’est pas « assez grand » pour pouvoir mettre tous les éléments de la
liste.
Il serait intéressant de disposer d’une structure nouvelle, qui offre des facilités similaires à celles des
tableaux, tout en ayant un taille dynamique, c’est-à-dire changeante au fil des additions et
suppressions. Par exemple, considérons une liste de courses. On pourrait la représenter ainsi :

1. "fromage"
2. "pain"

3. "salami"
On pourrait ajouter un élément en fin de liste, par exemple de l’eau, pour obtenir la liste :

1. "fromage"
2. "pain"
3. "salami"

4. "eau"
On pourrait aussi supprimer un élément de la liste, par exemple le pain, et obtenir :

1. "fromage"
2. "salami"

3. "eau"
On pourrait aussi insérer un élément dans la liste, par exemple une baguette, ce qui décale, de facto,
la position des suivants :
1. "fromage"

2. "salami"
3. "baguette"

4. "eau"
Et au niveau du code ? Nous aimerions pouvoir écrire des choses comme :

Dans cet exemple nous avons créé une liste de chaînes de caractères (String). De façon générale on
pourra imaginer une liste d’entiers, de booléens, ou d’autres choses encore. Voici par exemple un
morceau de code manipulant une liste d’entiers :

Après sa création, la liste est vide. Ensuite, elle passe par les états suivants :
Enfin, le dernier appel la vide complètement
2.2 ) Comment implémenter une liste en mémoire

Rappelons qu’une classe permet de définir un nouveau type de données, et donc d’encapsuler au
sein d’une seule entité différentes propriétés.

Rappelons aussi que pour les tableaux, le problème est nettement plus simple : il suffit de réserver le
bon nombre de cases en mémoire, et on accède à chaque case via son indice. Comme la taille d’un
tableau ne peut plus changer une fois créé, ceci suffit.
Dans ce chapitre, nous allons voir qu’il est en fait possible d’écrire une telle classe de deux façons
distinctes, à savoir :
1. au moyen d’un tableau géré dynamiquement par la classe. Le tableau est encapsulé par la classe et
remplacé par un tableau plus grand lorsque cela devient nécessaire (une ArrayList),
2. en chaînant les éléments les uns à la suite des autres en mémoire (une LinkedList).

Nous verrons également que ces choisir une de ces implémentations (ArrayList ou LinkedList) impacte
les performances : la complexité des algorithmes implémentés par les méthodes add et get par
exemples varie d’une classe à l’autre.
2.3) Les ArrayList

Nous avons vu dans le cours de DEV1 qu’un tableau a une taille fixe, appelée sa taille physique. On y
accède en Java avec la propriété length (par exemple : tab.length). Lorsqu’on ne connait pas a priori le
nombre d’éléments à mettre dans le tableau, on peut instancier un tableau « suffisamment grand »
pour y mettre les éléments. Le nombre de cases effectivement utilisées est alors appelé la taille
logique. Cette taille doit être ajustée au fur et à mesure qu’on ajoute ou retire des éléments.
Pour rappel voici deux méthodes écrites en DEV1 :

Ces deux méthodes acceptent le tableau registered ainsi que la taille logique nRegistered en
paramètre, modifient le tableau et retournent la nouvelle taille logique.

Le principe d’une ArrayList est alors d’implémenter une liste au moyen d’un tableau encapsulé dans la
classe, la taille logique du tableau étant géré par les méthodes de la classe.
2.5) L’interface d’une liste
Inspirons-nous de ce qui existe en Java.

Remarque : Nous ne précisons pas encore la façon dont ces méthodes sont implémentées, vu que
cela va dépendre de la représentation mémoire choisie pour notre liste (ArrayList ou LinkedList). Nous
donnerons ces implémentations dans les sections qui suivent. Les méthodes fournies ci-dessous ne
constituent donc qu’une interface.

Quelques précisions s’imposent :


▷ get(pos) et set(pos,value) permettent de connaitre ou modifier un élément de la liste. Comme pour
les tableaux, le premier élément de la liste est en position 0.
▷ add(value) ajoute un élément en fin de liste. Elle grandit donc d’une unité.

▷ add(pos,value) insère un élément à une position donnée (entre 0 et taille-1). L’élément qui s’y
trouvait est décalé d’une position ainsi que tous les éléments suivants.

▷ remove(value) enlève un élément de valeur donnée. Elle retourne un booléen indiquant si la


suppression a pu se faire ou pas (si la valeur était présente ou pas). Si la valeur existe en plusieurs
exemplaires, la méthode n’en supprime que la première occurrence.
▷ removePos(pos) supprime un élément d’une position donnée en décalant les éléments suivants.
Nous avons changé le nom de la version Java qui l’appelle remove pour ne pas confondre avec la
précédente dans le cas d’une liste d’entiers.

▷ contains(value) permet de savoir si un élément donné existe dans la liste.


▷ indexOf(value) donne la position d’un élément dans la liste.

▷ si l’élément n’existe pas, elle retourne −1.


▷ si l’élément est présent en plusieurs exemplaires, la méthode donne la position
de la première occurrence.
▷ En pratique, il serait intéressant de chercher un élément à partir d’une partie de
l’information qu’elle contient 1 mais c’est difficile (pas impossible !) à exprimer de
façon générique c’est-à-dire lorsque le type n’est pas connu a priori.

2.6) Implémenter une ArrayList


Dans cette section nous décrivons l’implémentation de la classe ArrayList encapsuler un tableau et sa
taille logique au sein d’un même objet, dans le but de pouvoir gérer ces deux aspects en parallèle. Le
tableau permet de contenir des valeurs, et la taille logique indique quelles sont les cases du tableau
réellement utilisées.

Nous utiliserons des valeurs de type « chaînes de caractères » (String), mais l’implémentation pourra
s’adapter à d’autres types de données .

Les attributs  La classe a donc deux attributs privés : un tableau et sa taille logique.

Ces attributs sont accessibles par le constructeur et toutes les méthodes de la classe.
Constructeur  Le rôle du constructeur est d’initialiser les attributs de la classe.

Nous allons définir deux constructeurs. Dans le premier, nous recevons un entier qui correspond à la
taille physique du tableau. Ceci correspond au nombre maximal de String que notre objet sera
capable de contenir.

La complexité est O(1)


Par facilité, nous définissons un second constructeur avec une taille physique par défaut :

Insertion  La classe offrira notamment deux méthodes pour insérer des valeurs :

Notons que chacune de ces méthodes est responsable de modifier la taille logique. Il n’y a donc plus
d’utilité de retourner cette taille logique. Une implémentation de la première méthode add serait :
La complexité est O(1). Notez que les deux dernières lignes pourraient se réduire à :

Pour la seconde méthode, notons d’abord une conséquence de la simple existence de cette méthode.
En effet, cette méthode permettant d’insérer une valeur à une position précise donnée, ceci implique
que la position des valeurs a de l’importance. Dès lors, insérer une valeur à une position doit décaler
les autres valeurs, afin que l’ordre relatif des valeurs soit préservé. Muni de cette information nous
savons maintenant implémenter la méthode :

Dans la mesure où il faut potentiellement parcourir tout le tableau, cette méthode est en O(n).

Nous aborderons plus tard l’implémentation pour les méthodes de suppression (remove,
removePos).

Taille (size)  Comme c’est la classe qui est responsable de tenir à jour sa taille logique, il doit y avoir
une méthode pour récupérer la taille (logique) courante :

Voici une Implémentation :


La complexité est O(1).
Nous ne définissons pas de méthode qui retourne la taille physique du tableau : ceci est considéré
comme un détail d’implémentation.
Accéder à un élément avec son indice (get, set)  Dans un tableau, la syntaxe tab[2] permet
d’accéder au troisième élément (élément d’indice 2), mais cette syntaxe est réservée aux tableaux.

Trouver un élément  Pour retrouver un élément, nous allons définir deux méthodes.
Supprimer un élément  Pour supprimer un élément, nous définissons deux méthodes :

Vider la liste  Nous définissons également une méthode permettant de vider complètement la liste.
2.7) Liste chainées
Nous avons vu une première classe, ArrayList, qui implémente toutes les méthodes de l’interface List.
Nous allons en voir une autre. Cette fois, l’idée est que chaque valeur de la liste sera contenue dans
un objet (de type Node), qui « connaît » son successeur. Pour pouvoir parcourir une liste, il suffit donc
de connaître le premier Node, et de suivre les successeurs successifs. Ceci est illustré sur le dessin ci-
dessous :

Pour cette raison, "une liste" sera un objet (de type SinglyLinkedList) contenant simplement une
référence vers un Node.

Remarquez qu’il serait aussi possible de chaîner la liste dans les deux sens, comme illustré ci-
dessous :

Ce type de liste chaînée est appelée liste chaînée double. Lorsqu’on mentionne une liste chaînée, il
est donc important de savoir de quoi on parle (une liste simplement chaînée ? une liste double ? 5 ).
L’implémentation que nous allons donner dans les sections qui suivent correspond à une liste chaînée
simple, ce qui explique le nom de la classe : "SinglyLinkedList".

2.8) Implémenter une linked list

Cohérence interne : il faut qu’il n’y ait pas de cycles (c’est-à-dire un noeud qui référence un nœud qui
référence un nœud . . . ultimement référence un noeud précédent). En particulier la chaîne devra
forcément se terminer par null.
Voyons comment implémenter certaines méthodes de notre interface selon ce principe.

Constructeur  Le rôle du constructeur est d’initialiser les attributs de la classe. Pour rappel le
constructeur de nos listes crée une liste vide. Il n’y a donc aucune valeur, et donc aucun Node.
Insertion  Rappelons les méthodes à implémenter :
Accéder à un élément avec son indice (get, set)  avec findElemAtPos nous avons fait l’essentiel du
travail pour implémenter get et set.

La taille  Contrairement au cas de ArrayList, la taille n’est pas un attribut de notre classe . Pour
compter le nombre d’éléments, il faut donc parcourir la liste.

isEmpty  Pour déterminer si une liste est vide, il est naturel de se demander si la taille vaut 0.
Cependant, notre méthode size a une complexité linéaire. Une implémentation en temps constant est
simplement :

2.8.1) Parcourir une SinglyLinkedList


Afficher  Pour rappel, dans les exercices de la section 2.4, nous avions vu l’algorithme suivant pour
afficher une ArrayList :

Ce n’est pas une bonne façon de parcourir une SinglyLinkedList.


En effet, le calcul de liste.get(pos) est en O(n), et il y a bien sûr n tours de boucles, ce qui donne une
complexité en O(n 2 ), plutôt mauvaise pour un simple affichage
La bonne manière d’afficher une liste chaînée est

La complexité est en O(n).


Parcourir avec un Iterator  Cependant la classe Node est privée. Dès lors tout algorithme voulant
parcourir une liste chaînée devrait être implémenté comme une méthode, afin d’accéder à ces objets
Node, ce qui n’a aucun sens. La solution standard à ce problème est de retourner un Iterator : c’est un
objet permettant de parcourir n’importe quelle liste.
2.8.2) Comparaison ArrayList et SinglyLinkedList
On trouvera dans la table 2.1 un récapitulatif des méthodes de l’interface List, avec la complexité
pour chacune des deux implémentations.
Face à un tel tableau, on peut se poser la question de la pertinence d’une structure telle que
SinglyLinkedList.
D’une part la performance réelle dépendra de l’utilisation qui est faite de la structure. À titre
d’exemple, une insertion dans une SinglyLinkedList en connaissant le Node qui précède le point
d’insertion se fait en temps constant. C’est notamment le cas pour une insertion en début de liste,
qui est toujours en temps constant alors que c’est en temps linéaire pour une ArrayList.
D’autre part, le concept de liste chaînée peut être modifié selon le besoin :

▷ ajout d’un attribut size pour garder la taille en mémoire ;


▷ ajout d’un attribut tail pour garder le dernier Node de la liste en mémoire ;

▷ modifier la classe Node pour obtenir une version doublement chaînée, parcourable dans les deux
sens.

Chapitre 3) La pile
3.1) Définition :
Une pile est une collection d’éléments admettant les fonctionnalités suivantes :

▷ on peut toujours ajouter un élément à la collection ;


▷ seul le dernier élément ajouté peut être consulté ou enlevé ;

▷ on peut savoir si la collection est vide.


La pile est donc une collection de données de type dernier entré, premier sorti. L’analogie avec la pile
de dossiers sur un bureau est claire : les dossiers sont déposés et retirés du sommet et on ne peut
jamais ajouter, retirer ou consulter un dossier qui se trouve ailleurs dans la pile.

On ne peut donc pas parcourir une pile, ou consulter directement le n-ième élément. Les opérations
permises avec les piles sont donc peu nombreuses, mais c’est précisément là leur spécificité : elles ne
sont utilisées en informatique que dans des situations particulières où seules ces opérations sont
requises et utilisées. Paradoxalement, on implémentera une pile en restreignant des structures plus
riches aux seules opérations autorisées par les piles. Cette restriction permet de n’utiliser que les
fonctionnalités nécessaires de la pile, simplifiant ainsi son utilisation.

Des exemples d’utilisations sont la gestion de la mémoire par les micro-processeurs, l’évaluation des
expressions mathématiques en notation polonaise inverse, la fonction « ctrl-Z » dans un traitement
de texte qui permet d’annuler les frappes précédentes, la mémorisation des pages web visitées par
un navigateur, etc. Nous les utiliserons aussi plus loin dans ce cours pour parcourir les arbres et les
graphes.
3.2) Implémentation orienté-objet :

3.2.1) Allure générale :


Il existe plusieurs possibilités de classes implémentant cette interface. Nous les détaillerons à titre
d’exercice

3.2.2) Remarques :

▷ Théoriquement, et dans la majorité des utilisations, la pile est infinie, c’est-à-dire qu’on peut y
ajouter un nombre indéterminé d’éléments. Dans certaines situations, on peut cependant imposer
une capacité maximale à la pile. Nous aborderons ce cas particulier dans les exercices.
▷ Lors de l’implémentation de la classe, il faudra songer à envoyer un message d’erreur lorsqu’on
utilise les méthodes sommet et dépiler si la pile est vide. Si la pile possède une taille maximale, alors
c’est empiler qui doit générer une erreur lorsque la pile est pleine.

▷ Nous avons utilisé ici des noms de méthodes neutres indépendants de tout langage de
programmation. Dans la littérature anglaise, on trouvera souvent push, top et pop en lieu et place de
empiler, sommet et dépiler.
3.3) Exemple d’utilisation :

Afin d’illustrer l’utilisation d’une classe implémentant l’interface Pile, nous donnons pour exemple un
algorithme qui lit une suite d’enregistrements d’un fichier fileIn (de type Info) et les reproduit en
ordre inverse dans le fichier fileOut.
Chapitre 4) La file
4.1) Définition :

Une file est une collection d’éléments admettant les fonctionnalités suivantes :
▷ on peut toujours ajouter un élément à la collection

▷ seul le premier élément ajouté peut être consulté ou enlevé


▷ on peut savoir si la collection est vide

La file est donc une collection de données de type premier entré, premier sorti. L’analogie avec une
file de clients à un guichet est évidente : c’est le premier arrivé qui est le premier servi, et il est très
malvenu d’essayer de doubler une personne dans une file ! Noter qu’une fois entré dans une file – au
sens informatique du terme – on ne peut pas en sortir par l’arrière, le seul scénario possible pour en
sortir est d’attendre patiemment son tour et d’arriver en tête de la file. De même que pour la pile, on
ne peut donc pas non plus parcourir une file, ou consulter directement le n-ième élément. Les files
sont très utiles en informatique, citons par exemple la création de mémoire tampon (buffer) dans de
nombreuses applications, les processeurs multitâches qui doivent accorder du temps-machine à
chaque tâche, la file d’attente des impressions pour une imprimante, ...
4.2) Implémentation orienté-objet :

Comme pour la pile, l’interface File ne contient qu’un nombre restreint de méthodes qui
correspondent aux quelques opérations permises avec cette structure : ajouter un élément (« enfiler
»), consulter l’élément de tête, et le retirer (« défiler »).

4.2.2) Remarques :
▷ De même que dans le chapitre précédent, la file est supposée infinie, c’est-à-dire qu’on peut y
ajouter un nombre indéterminé d’éléments..
▷ Dans l’implémentation, il faudra songer à envoyer un message d’erreur lorsqu’on utilise les
méthodes tête et défiler si la file est vide. Si la file possède une taille maximale, alors c’est enfiler qui
doit générer une erreur lorsque la file est pleine.

Chapitre 5) Mises en pratique


5.1) Se poser les bonnes questions :

nous avons commencé le cours d’algorithmique de DEV1 en situant les notions de problème et de
résolution. Nous avons vu qu’un problème bien spécifié s’inscrit dans le schéma :

étant donné [ la situation de départ ] on demande [ L’objectif ]


Une fois le problème correctement posé, on peut partir à la recherche d’une méthode de résolution,
c’est-à-dire d’un algorithme en ce qui concerne les problèmes à résoudre par les moyens
informatiques. Tout au long de l’année, nous avons vu divers modèles et techniques algorithmiques
adaptés à des structures particulières. La plupart des exercices portaient directement sur ces
structures. Ces exercices d’entrainement et de formation quelque peu théoriques constituent en fait
des démarches algorithmiques de base qui trouvent toutes une place dans des problèmes plus
complexes. Mais la plupart des problèmes issus des situations de la vie courante auxquels se
confronte le programmeur s’expriment généralement de manière plus floue : par ex. dresser la
comptabilité des dépenses mensuelles d’une firme, faire un tableau récapitulatif du résultat des
élections par cantons électoraux, faire une version informatique d’un jeu télévisé. . . Les exemples
sont infinis ! C’est dans le cadre de ce genre de problème plus complexe que se pose le problème de
la représentation de données. Une fois le problème bien spécifié apparaissent naturellement les
questions suivantes : quelles données du problème sont réellement utiles à sa résolution ? Y a-t-il des
données plus importantes que d’autres ? Les données doivent-elles être consultées plusieurs fois ?
Quelles données faut-il conserver en mémoire ? Sous quelle forme ? Faut-il utiliser un tableau ? Une
liste ? Faut il créer une nouvelle classe ? Les données doivent-elles être classées suivant un critère
précis ? Ou la présentation brute des données suffit-elle pour solutionner le problème posé ? Les
réponses ne sont pas directes, et les différents outils qui sont à notre disposition peuvent être ou ne
pas être utilisés. Il n’y a pas de règles précises pour répondre à ces questions, c’est le flair et le savoir-
faire développés patiemment par le programmeur au fil de ses expériences et de son apprentissage
qui le guideront vers la solution la plus efficace. Parfois plusieurs solutions peuvent fonctionner sans
pouvoir départager la meilleure d’entre elles. Ce type de questionnement est peut-être l’aspect le
plus délicat et le plus difficile de l’activité de programmation, car d’une réponse appropriée dépendra
toute l’efficacité du code développé. Un mauvais choix de représentation des données peut mener à
un code lourd et maladroit. En vous accompagnant dans la résolution des exercices qui suivent, nous
vous donnerons quelques indices et pistes de réflexion, qui seront consolidées par l’expérience
acquise lors des laboratoires de langages informatiques ainsi que par les techniques de modélisation
vues au cours d’analyse.

5.2) Les structures de données :


Rappelons brièvement les différentes structures étudiées dans ce cours :

▷ les données « simples » (variables isolées : entiers, réels, chaines, caractères, booléens) ;
▷ le tableau, qui contient un nombre déterminé de variables de même type, accessibles via un indice
ou plusieurs pour les tableaux multidimensionnels ;
▷ les objets, combinant une série d’attributs et des méthodes agissant sur ces attributs ;

▷ la liste, qui peut contenir un nombre indéfini d’éléments de même type. Chacune de ces structures
possède ses spécificités propres quant à la façon d’accéder aux valeurs, de les parcourir, de les
modifier, d’ajouter ou de supprimer des éléments à la collection. D’autres structures particulières
s’ajouteront dans le cours d’algorithmique d’ALG3 : les listes chainées, les piles, les files, les arbres, les
associations et les graphes.

Attention : Cette synthèse ne contient ni exemple en java, ni exercice.


Elle ne contient que le contenu purement théorique de chaque chapitre
Heusschen Thibault

Vous aimerez peut-être aussi