Vous êtes sur la page 1sur 98

Cours d’Algorithmique et Structures de données

Objectifs du cours
Ce cours d’algorithmique et structures de données permettra à l’étudiant de :
- Comprendre et maitriser les différentes structures et organisations de
données en algorithmique.
- Comprendre les notions de complexité des algorithmes et d’identifier les
meilleurs algorithmes.
- Faire l’analyse expérimentale et l’analyse théorique d’un algorithme.
- Maîtriser les structures de données particulières piles et files d’attente où
les insertions et suppressions d’éléments peuvent se faire en tête ou en
queue de liste.
- Maîtriser la notion de récursivité et développer des procédures récursives.
- Comprendre et maîtriser les structures dynamiques simples (les listes
chainées unidirectionnelles, bidirectionnelles et circulaires) et les structures
avancées (arbres et graphes).
Pré-requis
Pour bien appréhender ce cours, l’étudiant doit avoir suivi et assimilé :
- Le cours de logique de programmation ;
- Quelques notions d’analyse mathématique.
Modes d’évaluation
Les différentes évaluations retenues dans le cadre de ce cours sont :
- Présence aux cours ;
- Travail dirigé ;
- Interrogation et examens.
La moyenne annuelle représente la moitié de points, soit 10 points, et l’examen
également la moitié de points, soit 10 points.

Prof. BATUBENGA MWAMBA Nz. J.D. 1


Cours d’Algorithmique et Structures de données

Plan du cours
Chap. I. Généralités sur les algorithmes et complexité
I.1. Généralités sur les algorithmes
I.2. Notion de complexité d’un algorithme
I.3. Analyse des algorithmes
I.4. Exercices
I.5. Travail dirigé
Chap. II. Les Piles, les Files d’attente et la récursivité
II.1. Généralités
II.2. Les piles
II.3. Les files d’attente
II.4. La récursivité
Chap. III. Les algorithmes de tri
III.1. Définition
III.2. Tri par sélection
III.3. Tri par insertion
III.4. Tri par bulles
III.5. Tri rapide
III.6. Exercices
Chap. IV. Les listes chaînées
IV.1. Notions sur les pointeurs
IV.2. Généralités sur les listes chaînées
IV.3. Définition d’une liste chaînée
IV.4. Représentation en mémoire des listes chaînées
IV.5. Opérations dans une liste chaînée

IV.6. Chaînes Bidirectionnelles ou bilatères

Chapitre V. Les arbres


V.1. Généralités sur les arbres
V.2. Les arbres binaires
V.3. Les opérations sur les arbres binaires
V.4. Les arbres généralisés.
Prof. BATUBENGA MWAMBA Nz. J.D. 2
Cours d’Algorithmique et Structures de données

Chap. I. GENERALITES SUR LES ALGORITHMES ET COMPLEXITE


I.1. Généralités sur les algorithmes
I.1.1. Définitions
L’algorithme (du nom d’Al - Khuwarizmi, mathématicien arabe du IXe siècle) désigne
une suite d’actions dans un langage informel, décrivant la solution d’un problème en
un nombre fini d’étapes.
Un algorithme est aussi défini comme une suite finie d'instructions indiquant de
façon précise à l’automate (ordinateur) l'ordre dans lequel doit être effectué un
ensemble d'opérations pour obtenir la solution d’un problème.
Un programme informatique est un ensemble d’instructions (ou d’opérations)
destinées à être exécutées par un ordinateur. C’est le résultat de la traduction
d’algorithme en langage de programmation.
I.1.2. Qualités d’un algorithme
Les qualités d’un bon algorithme sont :
- lisible : compréhensible même par un non informaticien.
- Haut niveau : pouvoir être traduit dans n’importe quel langage de
programmation.
- Précis : chaque élément de l’algorithme ne doit pas prêter à confusion.
- Concis : ne doit pas être long ; si c’est le cas, il faut décomposer le problème en
sous problèmes.
- Structure : être composé de différentes parties identifiables.
I.1.3. Différentes structures de contrôle
Les algorithmes et leurs programmes informatiques équivalents sont beaucoup plus
compréhensibles s’ils utilisent principalement les modules internes ainsi que les types
de logique ou flux de contrôle ci-après :
a. Séquence ordinaire
A moins que des instructions n’aient été données de manière explicite, les modules
seront exécutés selon la séquence évidente. La séquence pourra être explicitée au
moyen d’étapes numérotées, ou implicitée par l’ordre donné au cours de l’écriture
des modules.

Prof. BATUBENGA MWAMBA Nz. J.D. 3


Cours d’Algorithmique et Structures de données

Module A

Module B

Module C

b. Structure conditionnelle en logique sélective


La logique sélective s’appuie sur un certain nombre de conditions pour permettre la
sélection d’un module entre plusieurs possibles.
Les structures qui permettent l’implémentation de cette logique sont appelées
structures conditionnelles ou structures if (si).
Si alors sinon

Oui Non
Condition

Module A Module B

3. Logique itérative (boucle)


Il s’agit d’un ensemble d’instructions (séquence) qui seront répétées un ensemble
déterminé de fois. Toute itération doit être limitée par une condition qui permettra
d’un sorti. On distingue deux types d’itérations : tant que ….. faire et
répéter…….jusqu’à ce que.

Condition Non

Tant que………..faire Oui


alternative simple.
Module A

Prof. BATUBENGA MWAMBA Nz. J.D. 4


Cours d’Algorithmique et Structures de données

Module A

Répeter jusqu’à ce que


Non
Condition

Oui

On peut également rencontrer l’organigramme suivant :


Selon que faire

Oui

Déterminer la Non
mention (E, TB, B, …)
d’un étudiant en Oui
fonction de sa
moyenne Non

Oui

Non

I.1.4. Exercices
a. Ecrire un algorithme qui lit le nom et l’âge d’une personne, puis affiche « X est
majeur (e) » si l’âge est supérieur ou égal à 18, sinon « X est mineur (e) » dans le cas
contraire (X étant le nom lu).
b. Un service de bureautique facture 50Fc par copie l’impression des vingt premières
copies d’un document, 40Fc par copie les trente suivantes et 30Fc par copie au-delà.
Ecrivez un algorithme qui calcule et affiche le montant à payer correspondant à un
nombre de copies donné. Dressez un ordinogramme y relatif.
Soit l’illustration suivante :

Nombre de copies Montant à payer


12 12 x 50 = 600Fc
28 20 x 50 + 8 x 40 = 1320Fc
65 20 x 50 + 30 x 40 + 15 x 30 = 2650Fc

Prof. BATUBENGA MWAMBA Nz. J.D. 5


Cours d’Algorithmique et Structures de données

c. Proposer un algorithme qui permet de calculer la factorielle d’un nombre donné n


 IN. (Sachant que n !=1x2x…….x (n-1) x n ; 0 !=1 et la factorielle d’un nombre négatif
n’existe pas).
d. Les élections locales, en RDC, obéissent à la règle suivante :
- lorsque l'un des candidats obtient plus de 50% des suffrages, il est élu dès le
premier tour.
- en cas de deuxième tour, ne peuvent participer uniquement que les candidats
ayant obtenu au moins 12,5% des voix au premier tour.
Ecrire un algorithme qui permette la saisie des scores de quatre candidats au premier
tour. Cet algorithme traitera ensuite le candidat numéro 1 (et uniquement lui) : il dira
s'il est élu, battu, s'il se trouve en ballottage favorable (il participe au second tour en
étant arrivé en tête à l'issue du premier tour) ou défavorable (il participe au second
tour sans avoir été en tête au premier tour).
I.2. Notion de complexité d’un algorithme
I.2.1. Définitions
La complexité d’un algorithme est une estimation du nombre d’opérations de base
effectuées par l’algorithme en fonction de la taille des données en entrée de
l’algorithme.
La complexité d’un algorithme est aussi la fonction qui donne le temps d’exécution et
/ ou l’espace nécessaire en fonction de la taille des entrées. Le temps et l’espace
nécessaires à l’exécution d’un algorithme constituent les deux principales unités de
mesure de son efficacité. Des algorithmes différents conçus pour résoudre le même
problème diffèrent correctement entre eux en termes d’efficacité ; et cette dernière
permet ainsi d’identifier les algorithmes les plus efficaces.
Les outils d’analyse d’algorithmes s’appuient généralement sur la mesure du temps
d’exécution des algorithmes et des structures des données. L’exploitation de la
mémoire (de l’espace) par les algorithmes et les structures de données est aussi un
paramètre important de la mesure de la qualité des algorithmes.
Le temps d’exécution est le paramètre matériel le plus utilisé pour la mesure de la
qualité des algorithmes, bien que ce temps dépende aussi de l’environnement dans
lequel les algorithmes sont implémentés et exécutés (Hardware : vitesse du
processeur, son architecture, la vitesse de la mémoire, ... et du software : système
d’exploitation, langage de programmation, compilateur, interpréteur, …).
Le temps est une ressource précieuse et les algorithmes doivent permettre la
résolution des problèmes sur ordinateurs dans les délais les plus courts possibles.
Prof. BATUBENGA MWAMBA Nz. J.D. 6
Cours d’Algorithmique et Structures de données

Par exemple, si un algorithme écrit dans une fonction prend en entrée un tableau de
n éléments, la complexité de l’algorithme sera une estimation du nombre total
d’opérations de base nécessaires pour l’algorithme, en fonction de n. Plus n sera
grand, plus il faudra d’opérations. La nature de l’algorithme sera différente selon que
sa complexité sera plutôt de l’ordre de n, de n2, de n3, ou bien de 2n. Le temps de
calcul pris par l’algorithme ne sera pas le même.
Une opération de base pourra être une affectation, un test, une incrémentation, une
addition, une multiplication, etc.
I.2.2. Les fondements mathématiques de l’algorithmique
L’analyse des algorithmes s’appuie sur les fonctions mathématiques. Les principales
fonctions mathématiques que l’on retrouve en algorithmique sont : la fonction
constante, la fonction logarithme, la fonction linéaire, la fonction quadratique, la
fonction n log n, la fonction cubique, les autres polynômes et la fonction
exponentielle.
a. La fonction constante
f (n) = c (c est constante)
Quelle que soit la valeur de n, f (n) = c. La fonction constante est importante en
algorithmique, car elle permet de caractériser le nombre d’étapes nécessaires pour
effectuer une opération à l’aide d’un ordinateur.
b. La fonction logarithme
Un aspect important dans l’analyse des algorithmes est la présence quasi
permanente de la fonction logarithme.
; pour b > 1.

Cette fonction est définie par :

La base la plus intéressante pour les ordinateurs est la base 2. Dans les calculateurs,
on utilise aussi souvent .

c. La fonction linéaire
f (n) = n
Cette fonction apparait dans les algorithmes chaque fois que nous devons réaliser
une simple opération pour chacun de n éléments d’un ensemble. Par exemple, pour
l’algorithme de recherche linéaire, il est question de comparer une donnée x à

Prof. BATUBENGA MWAMBA Nz. J.D. 7


Cours d’Algorithmique et Structures de données

chaque élément d’un tableau (ou fichier) de n éléments (ou enregistrements), ce qui
exigera n comparaisons.
Le temps nécessaire à l’exécution de cet algorithme est donc proportionnel au
nombre de comparaisons. En supposant que chaque élément du champ de
comparaison possède la même probabilité d’être retenu.
d. La fonction quadratique
f (n) = n2
La raison principale pour laquelle la fonction quadratique apparait dans l’analyse des
algorithmes est due au fait qu’il y a plusieurs algorithmes qui exploitent des boucles
imbriquées. Soit n opérations pour la boucle interne, n x n opérations pour la boucle
externe, ce qui fait n2 opérations en tout.
La fonction quadratique peut également apparaitre dans l’analyse des algorithmes
lorsque dans le contexte de boucles imbriquées, la première itération nécessite une
opération, la seconde nécessite deux opérations, la troisième nécessite trois
opérations et ainsi de suite. Le nombre d’opérations est alors :
1 + 2 + 3 + ... + (n − 2) + (n − 1) + n
C’est le total d’opérations exécutées par la boucle externe lorsque le nombre
d’opérations de la boucle interne croit de 1 à chaque opération de la boucle externe.
e. La fonction

Cette fonction croit un peu plus vite que la fonction linéaire et moins vite que la
fonction quadratique. En diverses occasions, il est possible de ramener le temps
nécessaire pour résoudre un problème d’une fonction quadratique à une fonction
. Ce qui peut représenter un gain non négligeable de temps.

f. La fonction cubique et les autres fonctions


f (n) = n3
Cette fonction apparait moins souvent que les autres mentionnées précédemment.
Toutefois, elle apparait de temps en temps.
Toutes les fonctions que nous avons étudiées précédemment peuvent être
considérées comme faisant partie de la classe des fonctions polynomiales.

Prof. BATUBENGA MWAMBA Nz. J.D. 8


Cours d’Algorithmique et Structures de données

g. La fonction exponentielle
Une autre fonction souvent utilisée dans l’analyse des algorithmes est la fonction
exponentielle
f (n) = bn, avec b > 0
b est la base et n l’exposant. La base qui apparait le plus souvent dans l’analyse des
algorithmes est la base 2. Si par exemple nous avons une boucle qui effectue une
opération, puis double le nombre d’opérations à chaque itération, alors le nombre
d’opérations effectuées à la nieme itération est 2n.
Remarque (quelques sommations)
La sommation suivante apparait de nombreuses fois dans l’analyse des algorithmes :

où a et b sont des entiers avec a ≤ b.

Un polynôme f (n) de degré d dont les coefficients sont a0,a1, …, ad peut être écrit
sous la forme ci-dessous :

Progression géométrique ∀ n ≥ 0 et ∀ réel a > 0 et a ≠ 1.

I.2.3. Notion de grand O ( ou Ordre de grandeur algorithmique)


On appelle opération de base, ou opération élémentaire, toute affectation, test de
comparaison =, >, <, ≤, ..., opération arithmétique +, −, ∗, /, appel de fonctions
comme sqrt, incrémentation, décrémentation. Lorsqu’une fonction ou une procédure
est appelée, le coût de cette fonction ou procédure est le nombre total d’opérations
élémentaires engendrées par l’appel de cette fonction ou procédure. Le temps de

Prof. BATUBENGA MWAMBA Nz. J.D. 9


Cours d’Algorithmique et Structures de données

calcul pris par l’algorithme (sur une machine donnée) est directement lié à ce nombre
d’opérations.
En fait, il est hors de question de calculer exactement le nombre d’opérations
engendrées par l’application d’un algorithme. On cherche seulement un ordre de
grandeur. L’ordre de grandeur asymptotique est l’ordre de grandeur lorsque la taille
des données devient très grande.

Soit un algorithme dépendant d’une donnée d de taille n (par exemple un tableau de


n éléments). Notons NB(d, n) le nombre d’opérations engendrées par l’algorithme.
On dit que l’algorithme est en O(n) si et seulement si on peut trouver un nombre
K tel que (pour n assez grand) : NB(d, n) ≤ K . n
Dans ce cas, on dit aussi que l’algorithme est linéaire. On dit que l’algorithme est en
O(n2) si et seulement si on peut trouver un nombre K tel que (pour n assez grand) :
NB(d, n) ≤ K . n2
Dans ce cas, on dit aussi que l’algorithme est quadratique.
On dit que l’algorithme est en O(2n) si et seulement si on peut trouver un nombre K
tel que (pour n assez grand) :
NB(d, n) ≤ K . 2n
Dans ce cas, on dit aussi que l’algorithme est exponentiel.
I.3. Analyse des algorithmes
Pour être capable d’identifier les bons algorithmes et les bonnes structures des
données, nous devons disposer de bonnes méthodes d’analyse.
Sans tenir compte de l’environnement dans lequel l’algorithme tourne, l’analyse des
algorithmes (complexité) se focalise sur la relation entre le temps d’exécution et la

Prof. BATUBENGA MWAMBA Nz. J.D. 10


Cours d’Algorithmique et Structures de données

taille de l’entrée comme paramètre principal. Dans ce contexte, tout algorithme peut
être étudié ou analysé, soit expérimentalement, soit théoriquement.
I.3.1. Analyse expérimentale
Une fois qu’un algorithme est implémenté, nous pouvons étudier son temps
d’exécution en fonction de la taille de l’entrée en mesurant expérimentalement ce
temps d’exécution. Des telles mesures sont possibles grâce aux fonctions disponibles
dans les langages de programmation et les systèmes d’exploitation.
Les mesures expérimentales ont trois limitations principales :
- Les expériences ne peuvent être faites que sur un nombre limité d’entrées
(d’autres entrées pouvant se révéler importantes sont laissées de côté) ;
- Il est difficile de comparer les temps d’exécution expérimentaux de deux
algorithmes, sauf si les expériences ont été menées sur les mêmes
environnements (Hardware et Software) ;
- On est obligé d’implémenter et d’exécuter un algorithme en vue d’étudier ses
performances. Cette dernière limitation est celle qui requiert le plus de temps
lors d’une étude expérimentale d’un algorithme.
Cette méthodologie associe à chaque algorithme étudié une fonction f(n), qui
caractérise son temps d’exécution en fonction de la taille n de l’entrée. Cette fonction
s’obtient par un ajustement approprié des données expérimentales.
Exemple : Considérons l’algorithme de tri par insertion qui a été implémenté en Java.
Pour mesurer le temps d’exécution, on fait tourner le programme pour des entrées
allant de 1000 à 400000 nombres entiers générés de manière aléatoire. Ce qui donne
lieu au tableau suivant :

Taille entrée Temps exécution (ms)


1000 31
5000 81
10000 243
15000 483
20000 999
25000 1397
30000 2262
50000 5277
60000 7477
70000 10075
90000 18217
100000 20214
150000 48083
Prof. BATUBENGA MWAMBA Nz. J.D. 11
Cours d’Algorithmique et Structures de données

200000 80261
300000 180171
400000 322896
Ce tableau génère la figure suivante qui est une parabole, que l’on peut exprimer par
une équation du type .

Ce qui nous fait dire que le temps d’exécution du tri par insertion est du type (n2),
c’est-à-dire quadratique.
I.3.2. Analyse théorique
L’analyse expérimentale est valable, mais présente des limites. Si nous souhaitons
analyser un algorithme particulier sans procéder à des expériences sur son temps
d’exécution, nous pouvons effectuer l’analyse de son pseudo-code. Il s’agit de
l’analyse théorique également appelée analyse des opérations primitives ou analyse
de la complexité. Ici, on s’appuie sur des opérations de base telles que :
- assigner une valeur à une variable ;
- effectuer une opération arithmétique (exemple : additionner deux nombres) ;
- comparer deux nombres ;
- indexer un tableau ;
- suivre la référence d’un objet ;
En fait, une opération primitive correspond à une instruction de bas-niveau avec un
temps d’exécution constant. Pour déterminer le temps d’exécution d’un algorithme,
il suffit de compter le nombre d’opérations primitives exécutées et pour chaque
opération primitive de multiplier ce nombre par son temps d’exécution constant.

Prof. BATUBENGA MWAMBA Nz. J.D. 12


Cours d’Algorithmique et Structures de données

Il y aura une corrélation entre ce temps d’exécution présumé et le temps d’exécution


sur une machine spécifique. A chaque opération primitive correspond un temps
d’exécution constant et il n’y a qu’un nombre limité d’opérations primitives. Ainsi, le
nombre d’opérations primitives exécutées par un algorithme sera proportionnel au
temps d’exécution de cet algorithme.
En représentant le temps d’exécution d’un algorithme en fonction de la taille des
entrées, on constate que le temps d’exécution se situe entre une borne supérieure
(plus mauvais cas ou plus défavorable) et une borne inferieure (cas le plus favorable).
Une analyse basée sur le cas moyen exige que nous puissions évaluer les temps
d’exécution d’une distribution d’entrées, ce qui implique des calculs probabilistes
compliqués. Bien souvent, on base l’analyse sur l’étude du plus mauvais cas (tous les
autres cas sont meilleurs que celui-là). L’analyse basée sur le plus mauvais cas exige
que l’on puisse identifier l’entrée correspondant au plus mauvais cas et cela est
simple à faire. Cette approche mène le plus souvent au meilleur algorithme.
Exemple : Considérons un algorithme de sommation suivant où le temps d’exécution
sur une entrée particulière est le nombre d'opérations élémentaires (primitives)
exécutées. Nous considérons que chaque ligne de notre pseudo-code demande une
quantité de temps constante. L'exécution de la ligne i prendra donc un temps Ci.

Entier Calcul_Somme (Entier n) Coût fois


1. Entier Var i, j, somme
2. Début
3. Somme ← 0 C1 1
4. Pour i=1 à n C2 n
5. Pour j=1 à i C3

6. Somme ← somme + i + j C4

7. Renvoyer somme C5 1
8. Fin
Le coût de l'exécution de l'algorithme dépend donc de n. On a ainsi :

Prof. BATUBENGA MWAMBA Nz. J.D. 13


Cours d’Algorithmique et Structures de données

Le temps d’exécution du cas le plus défavorable s’exprime sous la forme an2+bn + c


ou a, b, c sont des constantes dépendants des coûts Ci des instructions ; c’est donc
une fonction quadratique de n.
I.3.3. Calcul du nombre d’opérations fondamentales
Puis on compte ces opérations en appliquant des règles simples :
- dans les séquences d’instruction, les opérations s’ajoutent :
coût(I1 … In) = coût(I1) + … +coût(In)
- dans une boucle également, même si l’estimation est délicate pour les boucles
tant…que…faire et répéter…jusqu’à puisqu’on ne connaît pas a priori aisément le
nombre de passages.
coût(pour k allant de a à b faire I(k)) = coût(I(a))+ coût(I(a+1)) … +coût(I(b))
coût(tant que C(p) faire (I(p) ; p++)) = coût(C(p1)) + coût(I(p1))+ …+ coût(C(pn)) +
coût(I(pn)
- dans les branchements conditionnels, on peut calculer deux nombres, suivant que
l’une ou l’autre des instructions s’exécute, ce qui donne deux mesures :
coûtMax(si C alors I1 sinon I2) = coût(C) + Max(coût(I1), coût(I2))
coûtMin (si C alors I1 sinon I2) = coût(C) + Min(coût(I1), coût(I2))
- pour les appels de procédures, s’il n’y a pas de récursivité ni directe ni croisée, on
évalue la complexité avec les règles ci-dessus et l’évaluation préalable de la
complexité des procédures internes ;
- en cas de récursivité, il faut recourir à des procédés mathématiques plus
sophistiqués, du type résolutions d’équations de récurrence.
I.3.4. Complexités en moyenne et au pire
On va s’intéresser à la complexité d’un algorithme pour différents cas de
configurations de données : celles qui maximisent le coût, celles qui le minimisent. On
pourra aussi, si l’on connaît la probabilité d’avoir telle ou telle donnée en entrée,
estimer le comportement moyen de l’algorithme.
 La complexité dans le “pire cas”. On mesure alors le nombre d’opérations avec
les données qui mènent à un nombre maximal d’opérations. Soit donc

Prof. BATUBENGA MWAMBA Nz. J.D. 14


Cours d’Algorithmique et Structures de données

complexité dans le pire cas = max C(d)


d donnée de taille n
 où C(d) est le nombre d’opérations élémentaires pour exécuter l’algorithme
sur la donnée d’entrée d.
 La complexité en moyenne. Cette notion n’a de sens que si l’on dispose d’une
hypothèse sur la distribution des données. Soit donc

complexité en moyenne = ∑ π(d) C(d)


d donnée de taille n
 où π(d) est la probabilité d’avoir en entrée une instance d parmi toutes les
données de taille n.
I.4. Exercices
1. Écrire un algorithme qui initialise un tableau de n entiers, le nombre n étant saisi
au clavier, la valeur d’indice i du tableau valant 1 si 3i2+i−2 est multiple de 5 et 0
sinon. Evaluer la complexité de l’algorithme proposé.
3. Écrire une fonction en LDA qui saisit les éléments d’un tableau dont le nombre
d’éléments n est passé en paramètre. On supposera que le tableau a déjà été alloué
et est passé en paramètre. Evaluer la complexité de l’algorithme proposé.
4. Écrire une fonction en langage algorithmique qui prend en paramètre un entier n
et un tableau à deux dimensions de taille n×n d’entiers. La fonction calcule la somme
des éléments du tableau. Evaluer la complexité de l’algorithme proposé.
5. Écrire une fonction en langage algorithmique qui calcule les valeurs d’un tableau T
de taille n, le nombre n étant saisi au clavier, dont les valeurs T[i] sont telles que
T[0]=1 et pour i ≥1 :

Evaluer la complexité de l’algorithme proposé.


6. Soit la fonction :

a) Écrire une fonction en langage algorithmique qui prend en paramètre un entier x


et calcule f(x). Evaluer la complexité de l’algorithme proposé.

Prof. BATUBENGA MWAMBA Nz. J.D. 15


Cours d’Algorithmique et Structures de données

b) Écrire une fonction en langage algorithmique qui prend en paramètre un entier n


et calcule la somme :
Sn = f(n) + f(n−1) + f(n−2) + f(n−3) + f(n−4) + ···
On arrêtera la somme lorsque la valeur f calculée est nulle. Evaluer la complexité de
l’algorithme proposé.
c) Écrire une fonction en langage algorithmique qui prend en paramètre un entier n
et calcule la somme :
Tn = f(n) + f(n/22) + f(n/32) + f(n/42) + f(n/52) + ···
Dans cette somme, les divisions sont des divisions euclidiennes. On arrêtera la
somme lorsque la valeur f calculée est nulle. Evaluer la complexité de l’algorithme
proposé.
d) Écrire une fonction en langage algorithmique qui prend en paramètre un entier n
et calcule la somme :
Un = f(n) + f(n/2) + f(n/4) + f(n/8) + f(n/16) + ···
Dans cette somme, les divisions sont des divisions euclidiennes. On arrêtera la
somme lorsque la valeur f calculée est nulle. Evaluer la complexité de l’algorithme
proposé.

2. Écrire une fonction en langage algorithmique qui prend en paramètre un entier i et


calcule la valeur u(i) définie par récurrence par :
u0=1; u1=2
uj+1=3 ∗ uj − uj−1 pour j≥1
Evaluer la complexité de l’algorithme proposé.
7. On cherche à savoir si un tableau de n nombres entiers contient un certain nombre
a. On suppose que le tableau est trié dans l’ordre croissant, c’est-à-dire que chaque
élément est inférieur ou égal au suivant dans le tableau.
On se propose d’appliquer une méthode de recherche dichotomique qui nécessite
moins d’opérations, telle que : À chaque étape, on recherche un élément égal à a
entre deux indices imin et imax. Au départ, l’élément peut se trouver n’importe où
(entre imin=0 et imax=n−1).
On compare l’élément d’indice i =(imin + imax)/2 avec a. Trois cas peuvent se
produire :

Prof. BATUBENGA MWAMBA Nz. J.D. 16


Cours d’Algorithmique et Structures de données

• Si l’élément d’indice i est égal à a, l’algorithme se termine : le nombre a se


trouve bien dans le tableau ;
• Si l’élément d’indice i est supérieur à a, un élément égal à a ne peut se trouver
qu’à un indice plus petit que i, puisque le tableau est trié. Le nouvel imax vaut
i−1 ;
• Si l’élément d’indice i est inférieur à a, un élément égal à a ne peut se trouver
qu’à un indice plus grand que i. Le nouvel iminvauti+1.
Á chaque étape, un élément égal à a ne peut se trouver qu’entre imin et imax. On
itère ce procédé jusqu’à ce que imin devienne strictement supérieur à imax. Il ne
reste plus qu’à tester un seul élément.
Comme à chaque étape le nombre d’éléments est divisé par 2, le nombre k d’étapes
est tel que . Donc .

a) Écrire une fonction de recherche dichotomique d’un nombre x dans un tableau de


n nombres. La fonction renvoie 1 si l’élément se trouve dans le tableau et 0 sinon.
b) Quelle est la complexité ?
I.5. Travail dirigé
1. Soit un tableau linéaire DATA qui contient n éléments et soit une information
ITEM donnée. Nous désirons soit déterminer la position LOC de ITEM dans le
tableau DATA, soit transmettre un quelconque message tel que LOC = 0 pour
indiquer que ITEM n’apparaît pas dans DATA.
L’algorithme de recherche linéaire résout ce problème en comparant ITEM à
tous les éléments de DATA pris un à un. C’est à dire que nous comparons
d’abord ITEM à DATA *1+, puis à DATA *2+, et ainsi de suite jusqu’à ce que nous
trouvons ITEM = DATA [LOC].
a) Écrire une fonction de recherche linéaire correspondant à ce cas ?
b) Evaluer la complexité de l’algorithme proposé ?

Prof. BATUBENGA MWAMBA Nz. J.D. 17


Cours d’Algorithmique et Structures de données

Chap II. LES PILES, LES FILES D’ATTENTE ET LA RECURSIVITE


II.1. Généralités
En informatique, il est parfois souhaitable de restreindre les insertions et
suppressions d’éléments en tête ou en queue de liste. Les structures de données qui
sont très utiles dans ces situations sont les piles et les files d’attente.
Une pile est une structure linaire à laquelle il n’est possible d’ajouter ou de retirer des
éléments qu’à une seule extrémité, appelée sommet de la pile. En particulier, les
éléments sont retirés de la pile dans l’ordre inverse dans lequel ils ont été ajoutés à la
pile. Le dernier élément ajouté à la pile est le premier à en être retiré. Les piles sont
appelées listes LIFO (de last-in-fist-out : dernier – entrée – premier – sorti).
De nombreuses applications informatiques utilisent une telle structure de données.
C’est le cas des appels de sous-programmes. Une file d’attente est une liste linéaire à
laquelle des éléments ne peuvent être ajoutés qu’à une extrémité et être retirés à
l’autre. La première personne dans la file d’attente est la première à partir. C’est
pourquoi les files d’attente sont aussi appelées liste FIFO (de first-in, first-out,
premier-arrivé, premier-parti).
En informatique, de nombreuses applications nécessitent la gestion dans l’ordre FIFO
de demandes en attente. Ce sont en particulier tous les problèmes de réservation
(hôtel, avion, …), les demandes d’allocation de ressources dans les systèmes
d’exploitation d’ordinateur (files d’attente des entrées-sorties, des travaux à exécuter
…)
II.2. Les piles
Une terminologie particulière est utilisée pour décrire deux opérations
fondamentales associées aux piles :
a) Push : est le terme utilisé pour décrire l’adjonction d’un élément à la pile.
(empiler)
b) Pop : est le terme utilisé pour décrire la suppression d’un élément d’une pile
(dépiler).
II.2.1. Exemples :
1) Considérons une pile de six éléments AAA, BBB, CCC, DDD, EEE, FFF empilés dans
cet ordre dans une pile vide. Les éléments ne peuvent être défilés que dans
l’ordre inverse de leur empilement. Car les empilements et défilements ne
peuvent se faire qu’au sommet.
L’élément le plus à droite est l’élément du sommet EEE ne peut être défilé avant
que FFF ne l’ait été, DDD ne peut être défilé avant EEE et FFF, et ainsi de suite.
Prof. BATUBENGA MWAMBA Nz. J.D. 18
Cours d’Algorithmique et Structures de données

Top 1 AAA
FFF 2 BBB
EEE 3 CCC
DDD Top 4 DDD
CCC 5 EEE
BBB 6 FFF
AAA 7

N
AAA BBB CCC DDD EEE FFF ….
1 2 3 4 5 6 N

Top
2) Dans une liste AVAIL (pour available, c-à-d disponible), les nœuds libres sont
retirés uniquement de la tête de la liste et que les nœuds libérés étaient insérés
dans cette liste toujours par la tête. La liste AVAIL est implémentée sous forme
d’une pile.
II.2.2. Représentation physique d’une pile
Une pile peut être représentée soit par une liste simplement chaînée, c'est-à-dire une
liste monodirectionnelle, soit par un tableau linéaire.
Considérons une pile sous la forme du tableau linéaire de l’élément au sommet de la
pile et une variable MAX donnera le nombre maximum d’éléments qui peuvent être
accommodés par la pile. TOP = NULL indique que la pile est vide. Dans le cas ci-après
TOP = 3, la pile comporte trois éléments, XXX, YYY, ZZZ et MAX = 8, il reste encore sur
la pile de la place pour 5 éléments.

PILE (STA)
XXX YYY ZZZ
1 2 3 4 5 6 7 8

Top 3 Max 8

Prof. BATUBENGA MWAMBA Nz. J.D. 19


Cours d’Algorithmique et Structures de données

II.2.3. Insertion et suppression


L’opération d’insertion (empilement) d’un item à une pile et l’opération de
suppression (dépilement) d’un item d’une pile peuvent être implémentées par les
procédures PUSH et POP respectivement.
Avant d’appliquer PUSH, il faut vérifier d’abord qu’il y a bien de la place sur la pile
pour le nouvel élément. Sinon, il y a situation de dépassement de capacité.
Avant d’exécuter POP, il faut s’assurer qu’il existe bien un élément à supprimer sur la
pile, si ce n’est pas le cas, nous serons en situation de dépassement de capacité
négatif.
A. Algorithme PUSH (empilement ou insertion)
PUSH (STA, TOP, MAX, ITEM)
1. [ On demande si la pile est déjà pleine ]
Si TOP = MAX alors Ecrire : OVERFLOW et return.
2. Poser TOP = TOP + 1 [ Incrémenter TOP de 1 ]
3. Poser STA [ TOP ] = ITEM [Insérer ITEM dans la nouvelle adresse TOP]
4. Quitter.
B. Algorithme POP (dépilement ou suppression)
1. [ STA possède-t-il un élément à dépiler ? ]
Si TOP = 0 alors Ecrire : UNDERFLOW et EXIT.
2. Poser ITEM = STA [ TOP ] [affecter TOP à ITEM ]
3. Poser TOP = TOP -1 [ décrémenter TOP de 1 ]
4. Quitter
C. Exemples
a. Considérons la pile STA de I.2.2. et empilons www.
1. Puisque TOP = 3  MAX, le contrôle est transféré à 2
2. TOP = 3 + 1 = 4
3. STA [ TOP ] = STA [ 4 ] = www
4. Quitter
Après cette procédure www est désormais l’élément au sommet de la pile.
b. Avec la même pile nous cherchons à dépiler ZZZ
1. Puisque TOP = 3  0 le contrôle est transféré à 2.
2. ITEM = ZZZ [ avec ZZZ = STA [TOP] ]
3. TOP = 3 – 1 = 2
4. Quitter
Après cette procédure STA * TOP + = STA * 2 + = YYY est désormais l’élément au
sommet de la pile.

Prof. BATUBENGA MWAMBA Nz. J.D. 20


Cours d’Algorithmique et Structures de données

II.3. Les files d’attente


Dans la vie courante, le concept de file d’attente nous est très familier ; par exemple
toute personne utilisant un service public (guichets d’administration, caisses de
supermarchés, …) est supposée se plier à la règle " premier arrivé, premier servi ".
En informatique, l’ensemble des programmes de même priorité qui attendent d’être
traités par un système en temps partagé constitue un exemple important de file
d’attente.
II.3.1. Exemple

Soit une file d’attente


A B C D

A est l’élément de tête, D l’élément de queue.


A et D sont aussi le premier est le dernier élément de la liste. Si on supprime un
élément de la file d’attente, il ne peut être que A et nous obtenons :
B C D

B devient l’élément de la tête. Si on ajoute E et F, ça ne peut être qu’à la queue de la


file.
B C D E F

E sera retiré de la file d’attente avant F parce que E était placé avant F, mais il doit
attendre le retrait de C et D.
II.3.2. Représentation des files d’attente
Les files d’attente peuvent être représentées en mémoires de diverses manières,
généralement sous forme de listes monodirectionnelles ou des tableaux linéaires.
Nos files seront représentées par un tableau linéaire Q accompagné de deux
pointeur : F, contenant l’adresse de son premier élément, et R, contenant celle de
son dernier élément. Si F = NULL, la file est vide pour un tableau Q à N éléments.
Chaque fois, qu’un élément est supprimé, la valeur F est incrémentée de 1, ce qui
peut être implémenté par F = F + 1.
De même, chaque fois qu’un élément est ajouté, la valeur de R est incrémentée de 1,
ce qui donne R = R + 1.
C'est-à-dire qu’après N insertions, l’élément de queue occupera Q * N + ou en d’autres
termes, le dernier élément de la file d’attente occupera la fin du tableau.

Prof. BATUBENGA MWAMBA Nz. J.D. 21


Cours d’Algorithmique et Structures de données

II.3.3. Insertion et suppression dans une file


On se propose d’insérer un élément ITEM dans une file d’attente au moment où elle
occupe justement la fin du tableau, c'est-à-dire quand R = N. on peut le faire en
déplaçant simplement toute la file d’attente vers le début du tableau et en modifiant
F et R de façon appropriée, puis en insérant ITEM.
Mais, cette procédure est coûteuse (prend trop de temps). On peut adopter une
autre qui consiste à supposer que le tableau Q est circulaire, c'est-à-dire Q (1) vient
après Q (N). Ensuite, on insère ITEM dans la file d’attente en affectant ITEM à Q (1).
Dans ce conditions, au lieu d’incrémenter R à N + 1, on va initialiser R = 1 et on fait
l’affectation Q [R] = ITEM.
De même, si F = N et que un élément du tableau Q est supprimé, on réinitialise F = 1
au lieu d’incrémenter F à N + 1.
Si notre file d’attente ne comporte qu’un élément, c'est-à-dire F = R  NULL et si cet
élément est supprimé, nous affectons alors F = NULL et R = NULL pour indiquer que
la file d’attente est vide.
A. Algorithme d’insertion
Dans cette procédure qui insère l’information ITEM dans une file d’attente, on
commence par tester s’il n’y a pas dépassement de capacité, c'est-à-dire si la file
d’attente est pleine ou non. (OVERFLOW).
INSERT (Q, N, F, R, ITEM)
1. * File d’attente déjà pleine ?]
Si F = 1 et R = N ou si F = R + 1 alors
Ecrire OVERFLOW et Quitter
2. [ Trouver la nouvelle valeur de R ]
Si F = NULL alors Poser F = 1 et R = 1
* File d’attente initialement vide +
Sinon si R =N alors :
Poser R = 1
Sinon Poser R = R + 1
[ Fin structure Si ]
3. Poser Q * R + = ITEM * Insertion d’un nouvel élément +
4. Quitter

Prof. BATUBENGA MWAMBA Nz. J.D. 22


Cours d’Algorithmique et Structures de données

B. Algorithme de suppression
Dans cette procédure qui supprime le premier élément d’une file d’attente et
l’affecte à la variable ITEM, il faut d’abord commencer par tester s’il n’y a pas
dépassement de capacité négatif (UNDERFLOW), on cherche à savoir si la file
d’attente est vide ou non.
SUPP ( Q, F, R, ITEM)
1. * File d’attente déjà vide ? +
Si F = NULL alors Ecrire UNDERFLOW et Quitter
2. Poser ITEM = Q [ F ]
3. [ Trouver nouvelle valeur de F ]
Si F = R alors * file d’attente à un élément au départ +
Poser F = NULL et R = NULL
Sinon si F = N alors
Poser F = 1
Sinon Poser F = F + 1
[ fin de la structure Si ]
4. Quitter
II.3.4. DEQUE
Une deque est une liste linéaire où les éléments peuvent être ajoutés ou supprimés à
l’une ou l’autre extrémité mais pas au milieu.
Il existe plusieurs manières de représenter une deque en mémoire. On supposera que
notre deque est représentée par un tableau circulaire muni des pointeurs L et H
pointant ses deux extrémités. Les éléments se disposent en partant de l’extrémité
gauche et vont jusqu’à l’extrémité droite. Le terme circulaire signifie que DEQUE [ 1 ]
vient après DEQUE [ N ]
DEQUE
L=4
H=7
A B C D
1 2 3 4 5 6 7 8

Prof. BATUBENGA MWAMBA Nz. J.D. 23


Cours d’Algorithmique et Structures de données

II.4. La récursivité
II.4.1. Généralités
La récursive est un concept qui joue un rôle important en informatique. De nombreux
algorithmes sont en effet décrits de manière plus descriptive en termes de
récursivité.
Considérons une procédure P qui comprend une instruction Call (appel) sur elle-
même, soit une instruction Call sur une autre procédure pouvant d’ailleurs entraîner
une instruction Call de retour sur la procédure initiale P. Dans ces conditions, P
constitue une procédure récursive.
Pour empêcher le programme de tourner indéfiniment, une procédure récursive doit
posséder les deux propriétés suivantes :
(1) Il doit exister certains critères de base pour lesquels la procédure ne s’appelle
pas elle-même.
(2) Chaque fois que la procédure s’appelle elle-même, elle doit être plus proche
de ses critères de base.
Une procédure récursive est dite bien définie si elle possède ces deux critères.
On dit qu’une fonction est définie de manière récursive si sa définition fait référence à
elle-même. Pour que cette fonction ne soit pas circulaire, elle doit posséder les deux
propriétés suivantes :
a. Il doit exister les valeurs de base ou arguments pour lesquelles la fonction ne
se réfère pas à elle-même.
b. Chaque fois que la fonction se réfère à elle-même, son argument doit être plus
proche d’une valeur de base.
II.4.2. Exemple
Calcul de n!
On sait que pour tout entier positif n, on a : n! = n (n-1)!
On peut définir cette fonction factorielle d’une manière récursive.
(1) si n = 0, alors n! = 1
(2) si n  0, alors n! = n (n -1)!
Cette définition est-elle récursive puisqu’elle se réfère à elle-même quand elle
applique (n-1)! ?
(1) – la valeur de n ! est donnée implicitement par n = 0 (par conséquent, 0 est
une valeur de base) ;

Prof. BATUBENGA MWAMBA Nz. J.D. 24


Cours d’Algorithmique et Structures de données

(2) – la valeur de n ! pour n arbitraire est définie en fonction d’une plus petite n
plus voisine de la valeur de base 0. Donc la définition n’est pas circulaire.
C'est-à-dire la procédure est bien définie.
Calculons 3 ! en utilisant la définition récursive.
(1) 3! = 3.2!
(2) 2! = 2.1!
(3) 1! = 1.0!
(4) 0 ! = 1 (valeur de base de la définition récursive)
(5) 1 ! = 1. 1 = 1
(6) 2 ! = 2.1 = 2
(7) 3 ! = 3.2 = 6
Etape (1) : définit 3! en fonction de 2!
Etape (2) : définit 2! en fonction de 1!
Etape (3) : définit 1! en fonction de 0!
Etape (4) : évalue 0! implicité puisque 0 est la valeur de base de la définition
récursive.
Etape 5 à 7 : nous repartons en arrière, en utilisant 0! pour évaluer 1!, qui nous sert à
évaluer 2! et ainsi de suite jusqu’à 3!
II.4.3. Procédures de calcul
A. Première procédure de calcul de la fonction n !
FACTO (FAC, N) calcule n ! et affecte le résultat à FAC
1. Si N = 0 alors Poser FAC = 1 et Return
2. Poser FAC = 1 [initialiser FAC pour la bouche]
3. Répéter pour K = N à 1 (décroit de 1)
Poser FAC = K*FAC
[Fin de la boucle]
4. Fin
B. Deuxième procédure de calcul de la fonction n!
FACTO (FAC, N) calcul n! et affecte le résultat à variable FAC
1. Si N = 0 alors Poser FAC = 1 et Return
2. Call FACTO (FAC, N-1)
3. Poser FAC = N*FAC
4. Fin
Prof. BATUBENGA MWAMBA Nz. J.D. 25
Cours d’Algorithmique et Structures de données

Il convient de préciser que la procédure A évalue n ! au moyen d’un processus de


bouche itérative. Par contre, la seconde procédure est récursive, car elle contient un
appel à elle-même. Certains langages de programmation, en particulier FORTRAN,
n’autorisent pas de sous-programmes récursifs de ce type.
II.4.4. Suite de Fibonacci
On appelle suite de Fibonacci, une suite de la forme 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, …
notée généralement Fo, F1, F2, F3, …
Soit F0 = 0 et F1 = 1 les deux premiers termes, et chaque terme suivant est la somme
des deux qui précèdent :
0 + 1 = 1 ; 1 + 1 = 2 ; 1 + 2 = 3 ; 2 + 3 = 5 ; 3 + 5 = 8 ; 5 + 8 = 13
8 + 13 = 21 ; 13 + 21 = 34 …
La suite de Fibonacci est définie de la manière suivante :
(a) Si n = 0 ou n = 1, alors Fn = 1
(b) Si n  1, alors Fn = Fn-2 + Fn-1
Il s’agit d’une définition récursive, car la définition se réfère à elle-même quand elle
utilise Fn-2 et Fn-1.
(a) les valeurs de base sont 0 et 1
(b) la valeur de Fn est définie en fonction des valeurs plus petites de n qui sont
plus proches des valeurs de base. La fonction est bien définie.
La procédure suivante calcule Fn et affecte sa valeur au premier paramètre FI.
FIBO (FI, N).
1. Si N = 0 ou N = 1 alors Poser FI = N, et retenir
2. Call FIBO (FIA, N-2)
3. Call FIBO (FIB, N-1)
4. Poser FI = FIA + FIB
5. Fin
Il s’agit d’une procédure récursive puisqu’elle contient un appel à elle-même.
La procédure suivante calcule les N premiers nombres de Fibonacci et les affecte à un
tableau F.
1. Poser F [1] = 1 et F [2] = 1
2. Répéter pour K = 3 à N
Poser F [K] = F [K-1] + F [K-2]
[Fin de la boucle]
3. Exit
Prof. BATUBENGA MWAMBA Nz. J.D. 26
Cours d’Algorithmique et Structures de données

Cette procédure itérative est beaucoup plus efficace que la procédure récursive
précédente.
II.4.5. Schémas itératifs - schémas récursifs
Bien que les algorithmes récursifs se présentent souvent sous une forme assez
simple, il n’est pas toujours aisé pour le programmeur de le concevoir car ils
nécessitent un assez grand effort d’abstraction. Un problème se prête
particulièrement bien à une démarche récursive lorsqu’il peut être décomposé en
une suite d’actions dont l’une est un sous-problème semblable au problème d’origine
mais appliqué à un environnement réduit.
Toute la difficulté consiste alors à trouver la solution correspondant à
l’environnement minimum qui ne fera plus appel à la récursivité.
La récursivité est coûteuse, mais donne des solutions qui s’expriment de manière plus
concise que les solutions itératives correspondantes.
Cependant, si la solution itérative est simple à formuler, il est déconseillé d’utiliser
une solution récursive en raison du coût en temps d’exécution de la gestion des
environnements successifs.
Il existe une sorte de problèmes pour lesquels il est difficile de donner une solution
non récursive. Ce sont les problèmes où la recherche de la solution se fait par essais
successifs, comme dans le parcours d’un labyrinthe : il faut essayer un chemin et, s’il
ne conduit pas à la solution, revenir en arrière et essayer un nouveau chemin. Les
applications intéressantes sont en recherche opérationnelle (parcours de graphe) et
en intelligence artificielle.
Il existe de méthodes de traductions d’une procédure récursive en une procédure
non récursive.
II.4.6. Traduction d’une procédure récursive en une procédure non récursive
On suppose P une procédure récursive telle qu’un appel à P ne peut provenir que de
P. P peut être traduite en une procédure non récursive de la manière suivante :
(1) Considérons une pile STY pour chaque paramètre Y ;
(2) Une pile STX pour chaque variable locale.
(3) Une variable locale AD et une pile STAD pour ranger les adresses de retour.
Chaque fois qu’apparaîtra un appel récursif à P, les valeurs actuelles des paramètres
et des variables locales seront empilées sur les piles correspondantes pour
traitements ultérieurs et à chaque retour récursif sur P, les valeurs des paramètres et

Prof. BATUBENGA MWAMBA Nz. J.D. 27


Cours d’Algorithmique et Structures de données

des variables locales relatives à l’exécution actuelle de P seront restaurées après


défilement.
La gestion de l’adresse de retour se fera de la manière suivante :
On suppose que la procédure P contient une instruction récursive Call P à l’étape K. il
existe alors deux adresses de retour associées à cette étape K.
(1) L’adresse actuelle de retour de la procédure P qui sera utilisé quand l’exécution
courante de la procédure P sera achevée.
(2) La nouvelle adresse de retour K + 1, qui est l’adresse de l’étape qui suit Call P et
qui sera utilisée pour revenir au niveau d’exécution actuel de la procédure P.
Remarque :
Certains acteurs empilent la première de ces deux adresses, l’adresse de retour
actuelle, sur la pile d’adresses de retour STAD, d’autres empilent la seconde adresse,
la nouvelle adresse de retour K + 1, sur STAD.
La deuxième méthode a l’avantage de simplifier le processus de traduction de P en
une procédure non récursive. C’est pour cette raison que nous choisirons cette
deuxième méthode qui signifie également qu’une pile STAD vide indiquera un retour
au programme principal qui a initialement appelé la procédure récursive P.
L’algorithme qui traduit la procédure récursive P en une procédure non récursive
comporte trois parties : la préparation, la traduction de chaque appel Call P récursif
en une procédure P et la traduction de chaque instruction de retour Return en une
procédure P.
(1) Préparation
(a) Définir une pile STY pour chaque paramètre, une pile STX pour chaque
variable locale, une variable locale AD et une pile STAD pour gérer les
adresses de retour
(b) Poser TOP = NULL. (initialisation)
(2) Traduction de " l’étape K de Call P "
(a) Empiler les valeurs actuelles des paramètres et des variables locales sur les
piles correspondantes. Empiler la nouvelle adresse de retour K + 1 sur la pile
STAD.
(b) Réinitialiser les paramètres en utilisant les valeurs nouvelles des arguments.
(c) Go to étape 1. [ début de la procédure P]
(3) Traduction de " l’Etape J Return "

Prof. BATUBENGA MWAMBA Nz. J.D. 28


Cours d’Algorithmique et Structures de données

(a) Si STAD est vide, alors : return


[ le contrôle est retransféré au programme principal ]
(b) Restaurer les valeurs de sommet des piles. C'est-à-dire initialiser les variables
locales et les paramètres avec les valeurs de sommet des piles et affecter AD
la valeur de sommet de la pile STAD.
(c) Go to étape AD
Remarque : la traduction de l’étape K, Call P dépend de la valeur de K mais la
traduction de l’étape J " Return " ne dépend pas de la valeur de J. il est nécessaire de
traduire qu’une instruction Return, exemple :
Etape L Return et remplacer
Toute autre instruction Return par :
Go to étape L c'est-à-dire la traduction de l’algorithme.
II.4.7. Applications
1. On donne une fonction f (a, b) avec a  0, b  0, définie récursivement par :

a) Calculer f (2, 3) et f (14, 3)


b) Quelle est l’objet de cette fonction ? éviter (5861, 7).
Solution :
a) f (2, 3) = 0, car 2  3
f (14, 3) = f (11, 3) + 1
= [ f (8, 3) + 1 ] + 1 = f (8, 3) + 2
= [ f (5, 3) + 1] + 2 = f (5, 3) + 3
= [f (2,3) + 1 ] + 3 = 0 + 1 + 3 = 4
Donc f (14, 3) = 4.
b) En faisant la différence a – b, la valeur de f est augmentée de 1. Par
conséquent f (a, b) imprime le quotient de la division entière de a par b.
f (5861, 7) = 837
2. On donne une fonction L (n) définie récursivement par :

Prof. BATUBENGA MWAMBA Nz. J.D. 29


Cours d’Algorithmique et Structures de données

Avec [n/2] le plancher de n/2, c'est-à-dire le plus grand entier qui ne soit pas
supérieur à n/2 .
a) Calculer L (25)
b) Quel est l’objet de cette fonction ?
Solution
a) L (25) = L (12) +1
= [L (6) + 1 ] + 1 = L (6) + 2
= [L (3) + 1] + 2 = L (3) + 3
= [ L (1) + 1 ] + 3 = L (1) + 4
=0+4=4
b) Chaque fois que a est divisé par 2, la valeur de L augmente de 1. Il en résulte
que L est le plus entier tel que .

C'est-à-dire L = [log2n]
3. Soient j et k deux entiers et soit f (j, k) définie récursivement par :

Trouver a) f (2, 7), b) f (5, 3) et c) f (15, 2)

Prof. BATUBENGA MWAMBA Nz. J.D. 30


Cours d’Algorithmique et Structures de données

Chap. III. LES ALGORITHMES DE TRI


III.1. Définition
Soit A, une liste de n éléments. Trier A désigne l’opération de reclassement des
éléments de A de manière qu’il soit dans un ordre croissant, c’est à dire :
A (1) ≤ A (2) ≤ A (3) ≤ … ≤ A (n).
Cette opération a une importance, car l’ordre des valeurs dans une liste peut avoir
une influence sur le choix d’un algorithme de traitement.
Il est fréquent que l’on ait à réarranger les valeurs d’une liste selon l’ordre croissant
en décroissant.
Plus généralement, on peut chercher à trier des objets sur lesquels est définie une
relation d’ordre ≤. On peut ainsi trier des objets suivant un critère (trier des gens
suivant leur taille, trier des mots dans l’ordre alphabétique, etc.).
En pratique, A est souvent un fichier d’enregistrements et son tri désigne un
réarrangement des enregistrements qu’il contient, de manière à ce que les valeurs
d’une clé donnée soient classées selon un certain ordre.
III.2. Tri par sélection
III.2.1. Principe du tri par sélection
Considérons un tableau T de n nombres. Le principe du tri par sélection est le suivant.
On sélectionne le maximum (le plus grand) de tous les éléments, et on le place en
dernière position n-1 par un échange. Il ne reste plus qu’à trier les n-1 premiers
éléments, pour lesquels on itère le procédé.
Exemple : Soit le tableau

6 3 7 2 3 5
Le maximum de ce tableau est 7. Plaçons le 7 en dernière position par un échange.

6 3 5 2 3 7

reste à trier
Le 7 est à sa position définitive ; il ne reste plus qu’à trier les cinq premiers éléments.
Le maximum de ces cinq éléments est 6. Plaçons-le à la fin par un échange :

3 3 5 2 6 7

reste à trier
Prof. BATUBENGA MWAMBA Nz. J.D. 31
Cours d’Algorithmique et Structures de données

Le maximum parmi les nombres restant à trier est 5. Plaçons-le en dernière position
par un échange :

3 3 2 5 6 7

reste à trier
Enfin le maximum parmi les nombres à trier est 3. On le place en dernier et le tableau
est trié.

2 3 3 5 6 7
III.2.2 Algorithme de tri par sélection
L’algorithme de tri par sélection (par exemple sur des nombres entiers) est le
suivant :
PROCEDURE TriSelection(entier *T, entier n)
début
entier k, i, imax, temp;
pour k ←n-1 à 1 pas -1 faire
/* recherche de l’indice du maximum : */
imax←0;
pour i←1 à k pas 1 faire
si T[imax] < T[i] alors imax←i;
fin faire
/* échange : */
temp←T*k+;
T*k+←T*imax+;
T*imax+←temp;
fin faire
fin
III.2.3. Estimation du nombre d’opérations
• Coût des échanges.
Le tri par sélection sur n nombres fait n−1 échanges, ce qui fait 3(n−1) affectations.
• Coût des recherches de maximum :
– On recherche le maximum parmi n éléments : au plus 4(n−1) opérations. (C’est le
nombre d’itérations de la boucle sur i pour k fixé égal à n-1)
– On recherche ensuite le maximum parmi n−1 éléments : au plus 4(n−2) tests.

Prof. BATUBENGA MWAMBA Nz. J.D. 32


Cours d’Algorithmique et Structures de données

(C’est le nombre d’itérations de la boucle sur i pour k fixé égal à n-2)


– On recherche ensuite le maximum parmi n−2 éléments : 4(n−3) tests.
– ...
Le nombre total de tests est de :

C’est la somme des termes d’une suite arithmétique.

Au total, le nombre d’opérations est plus petit que

Ceci est un polynôme de degré 2 en le nombre n d’éléments du tableau. On dit que la


méthode est quadratique, ou encore qu’elle est une méthode en O(n2). La partie de
degré 1 en n est négligeable devant la partie en n2 quand n devient grand.
III.3. Tri par insertion

III.3.1. Principe du tri par insertion


Le principe du tri par insertion est le suivant : on trie d’abord les deux premiers
éléments, puis on insère le 3ème à sa place pour faire une liste triée de 3 éléments,
puis on insère le 4ème élément dans la liste triée. La liste triée grossit jusqu’à contenir
les n éléments.
Exemple
Soit le tableau

6 3 4 2 3 5
En triant les deux premiers éléments on obtient :
3 6 4 2 3 5

trié
En insérant le troisième élément à sa place dans la liste triée :

3 4 6 2 3 5

trié
En insérant le quatrième élément à sa place dans la liste triée :
2 3 4 6 3 5

trié
Prof. BATUBENGA MWAMBA Nz. J.D. 33
Cours d’Algorithmique et Structures de données

En insérant le cinquième élément à sa place dans la liste triée :

2 3 3 4 6 5

trié
En insérant le sixième élément à sa place dans la liste triée :

2 3 3 4 5 6
Pour insérer un élément à sa place, on doit décaler les éléments déjà triés qui sont
plus grands.
III.3.2 Algorithme de tri par insertion
L’algorithme de tri par insertion est le suivant :
PROCEDURE TriInsertion(entier *T, entier n)
Début
entier k, i, v;
pour k ←1 à n-1 pas 1 faire
v←T*k+;
i←k-1;
/* On décale les éléments pour l’insertion */
tant que i≥0 et v < T*i+ faire
T*i+1+←T*i+;
i←i-1;
fin faire
/* On fait l’insertion proprement dite */
T*i+1+←v;
fin faire
fin
III.3.3. Estimation du nombre d’opérations
L’indice k varie de 1 à n−1, soit n−1 valeurs différentes. Pour chaque valeur de k,
l’indice i prend au plus k valeurs différentes. Le total du nombre d’opérations est
donc au plus de :
3(n−1) + 4(1 + 2 + 3 + ··· + (n−2) + (n−1))

Somme pour différentes valeurs de i


Soit un nombre d’opération d’au plus :

Prof. BATUBENGA MWAMBA Nz. J.D. 34


Cours d’Algorithmique et Structures de données

Ce qui est un polynôme du second degré en le nombre n d’éléments. L’algorithme est


donc quadratique (O(n2)).
III.3.4. Stabilité de l’algorithme de tri par insertion
L’algorithme que nous avons décrit ici est stable. En effet, si à l’état initial T*p+ et T*q+
sont tels que : p<q et T*p+ = T*q+. L’insertion de la valeur de T*q+ aura lieu après celle
de la valeur de T[p+ et se fera à un emplacement d’indice supérieur à celui de
l’emplacement d’insertion de la valeur de T[p].
En effet, l’arrêt de décalages s’effectue dès qu’on rencontre une valeur inférieure ou
égale à la valeur d’insertion.
III.4. Tri par bulles
III.4.1 Principe du tri par bulles
Dans le tri par bulle, on échange deux éléments successifs T[i-1] et T[i] s’ils ne sont
pas dans l’ordre. Évidemment, il faut faire cela un grand nombre de fois pour obtenir
un tableau trié. Plus précisément, on fait remonter le maximum à sa place définitive
par échanges successifs avec ses voisins.
Exemple
Voici la succession des échanges d’éléments successifs effectués.

6 3 4 2 3 5

3 6 4 2 3 5

3 4 6 2 3 5

3 4 2 6 3 5

3 4 2 3 6 5

3 4 2 3 5 6
On fait une deuxième passe :

3 2 4 3 5 6

3 2 3 4 5 6

Prof. BATUBENGA MWAMBA Nz. J.D. 35


Cours d’Algorithmique et Structures de données

Puis encore une passe :

2 3 3 4 5 6
III.4.2 Algorithme du tri par bulles
L’algorithme du tri par bulles est le suivant :
PROCEDURE TriBulle(entier *T,entier n)
Début
entier i, k, temp;
/* pour chaque passe */
pour k ←n-1 à 1 pas -1 faire
/* on fait remonter le plus grand */
pour i←1 à k pas 1 faire
si T[i] < T[i-1] alors
/* échange de T[i-1] et T[i] */
temp←T*i+;
T*i+←T*i-1];
T[i-1+←temp;
fin si
fin faire
fin faire
fin
III.4.3. Estimation du nombre d’opérations
La variable k prend n−1 valeurs différentes. Pour chacune de ces valeurs, la variable i
prend k valeurs différentes. Le nombre total d’opérations est plus petit que :
2(n−1) + 5(1 + 2 + 3 + ··· + (n−2) + (n−1))

Somme pour différentes valeurs de i

Soit un nombre d’opération d’au plus :

Ce qui est un polynôme du second degré en le nombre n d’éléments. L’algorithme est


donc quadratique (O(n2)).

Prof. BATUBENGA MWAMBA Nz. J.D. 36


Cours d’Algorithmique et Structures de données

III.5. Le tri rapide

III.5.1. Principe du tri rapide


Le tri rapide fonctionne suivant la méthode « diviser pour régner ». On partage le
tableau en deux, la partie gauche et la partie droite, et on trie chacune des deux
parties séparément. On obtient alors un tableau trié.
III.5.2 Partitionnement
Supposons que l’on souhaite trier une partie d’un tableau T entre les indices imin et
imax. On choisit une valeur pivot, par exemple v = T[imax]. On veut séparer dans le
tableau les éléments inférieurs à v et les éléments supérieurs à v.

Pour faire cela, on introduit un indice i, initialisé à imin, et un indice j, initialisé à imax-
1. On fait ensuite remonter l’indice i jusqu’au premier élément supérieur à v, et on
fait redescendre j jusqu’au premier élément inférieur à v. On échange alors les
éléments d’indices i et j. On itère ce procédé tant que i≤j. À la fin, on place la valeur
pivot en position i.

Le code algorithmique de ce partitionnement est donc :


FONCTION Partitionnement(entier *T, entier imin, entier imax) : entier
Début
entier v, i, j, temp;
v←T*imax+; /* v valeur pivot */
i←imin;
j←imax-1;
tant que i<=j faire
tant que i<imax et T[i] <= v faire
i←i+1;

Prof. BATUBENGA MWAMBA Nz. J.D. 37


Cours d’Algorithmique et Structures de données

fin faire
tant que j>=imin et T[j] >= v faire
j←j-1;
fin faire
si i<j alors /* échange */
temp←T*i+;
T*i+←T*j+;
T*j+←temp;
fin si
fin faire
T*imax+←T*i+; T*i+←v; /* On place la valeur pivot en i */
retourner i; /* renvoie l’endroit de la séparation */
fin

III.5.3. L’algorithme de tri rapide


Après le partitionnement d’un tableau, le pivot v est à sa place définitive dans le
tableau trié. De plus, tous les éléments qui se trouvent à la gauche du pivot dans le
tableau triés sont ceux qui se trouvent à sa gauche après partitionnement. Le tri
rapide consiste à effectuer le partitionnement du tableau en deux parties, puis à trier
chacune des deux parties (gauche et droite) suivant le même principe.
PROCEDURE TriRapide(entier *T, entier imin, entier imax)
Début
entier i;
si imin < imax alors
i←Partitionnement(T, imin, imax);
TriRapide(T, imin, i-1); /* tri de la partie gauche */
TriRapide(T, i+1, imax); /* tri de la partie droite */
fin si
fin

PROCEDURE QuickSort(entier *T,entier n)


Début
TriRapide(T, 0, n-1); /* imin=0 et imax=n-1 */
fin

Prof. BATUBENGA MWAMBA Nz. J.D. 38


Cours d’Algorithmique et Structures de données

Notons que la procédure Tri Rapide s’appelle elle-même. On parle de procédure


récursive. Nous étudierons en détail cette manière de programmer au chapitre III de
ce cours.
III.5.4. Comparaison de temps de calcul
Dans la figure qui suit, nous observons des courbes expérimentales qui montrent le
temps de calcul (temps d’utilisation du CPU) des algorithmes de tri par bulle, par
insertion, par sélection, et du tri rapide, en fonction de la taille n du tableau. Le
nombre d’éléments n du tableau varie de 1 à 2000. On observe que le temps de
calcul donné par le tri rapide est nettement meilleur que le temps de calcul des tris
quadratiques (la courbe est collée près de l’axe des x). La complexité se traduit au
niveau des temps de calcul.

III.6. Exercices
1. On se propose de trier une liste de noms de manière à les ranger par ordre
alphabétique. On utilise la méthode de tri insertion ; écrire un programme réalisant
ce tri.
2. Soit une liste linéaire suivante : 60, 43, 10, 50, 32, 55, 40, 66, 77, 54, 99, 80, 88.
a) Appliquer l’algorithme de tri insertion pour ranger cette liste par ordre croissant.
b) Appliquer l’algorithme de tri sélection moyennant aménagement pour ranger
cette liste par ordre décroissant.

Prof. BATUBENGA MWAMBA Nz. J.D. 39


Cours d’Algorithmique et Structures de données

3. Soit une pile P1 contenant solution une liste non triée de côtes de 10 étudiants.
Ecrire un algorithme qui crée une autre pile P2 contenant la liste triée des éléments
de P1.
4. Soit un tri rapide dont l’idée est : choisir un élément pivot au hasard dans un
tableau, on partitionne celui-ci en deux sous-tableaux tels que les éléments du
premier soient plus petits que le pivot, et les éléments du second plus grands, et on
appelle récursivement la procédure de tri sur ces sous-tableaux. Le cas d’arrêt est
obtenu quand les tableaux sont de taille 0 ou 1, et donc naturellement triés.
Appliquer ce tri rapide à la liste suivante : 32, 51, 27, 85, 66, 77, 23, 13, 46, 57.

Prof. BATUBENGA MWAMBA Nz. J.D. 40


Cours d’Algorithmique et Structures de données

Chap IV. LES LISTES CHAINEES


IV.1. Notions sur le pointeur
IV.1.1. Rappels sur les tableaux
Contrairement aux variables simples, les tableaux permettent de stocker des données
nombreuses de même type en mémoire centrale. A la différence des structures, la
déclaration du tableau en une ligne suffit pour déclarer toutes ses données. Toutes
les données du tableau doivent être de même type, même si le type des éléments
peut être un type complexe comme une structure.
Déclaration d’un tableau
 On déclare un tableau (statique) linéaire par :
Var nomTableau[NOMBRE_ELEMENTS] : typeElements
ou
typeElements nomTableau[NOMBRE_ELEMENTS];
où typeElements est le type des éléments du tableau, nomTableau est le nom du
tableau, et NOMBRE_ELEMENTS est une constante indiquant le nombre d’éléments
du tableau.
Par exemple, pour déclarer un tableau de 100 entiers appelé tab :
entier tab[100];
En C : int tab[100];
Pour déclarer un tableau de 150 caractères appelé chaine;
caractère chaine[150];
 On déclare un tableau (statique) binaire par :
Var nomTableau[NBRE_LIGNE, NBRE_COLONNE] : typeElements
ou
typeElements nomTableau[NBRE_LIGNE, NBRE_COLONNE];
où typeElements est le type des éléments du tableau, nomTableau est le nom du
tableau, et NBRE_LIGNE et NBRE_COLONNE sont des constantes indiquant
respectivement le nombre de lignes et le nombre de colonnes du tableau.
Par exemple, pour déclarer un tableau d’entiers appelé tabs constitué de 50 lignes et
40 colonnes :
entier tabs[50, 40];
Prof. BATUBENGA MWAMBA Nz. J.D. 41
Cours d’Algorithmique et Structures de données

En C : int tabs[50, 40];


Dans des telles déclarations, le nombre d’éléments, le nombre de colonnes et le
nombre de lignes du tableau sont obligatoirement des constantes.
Accès aux éléments d’un tableau

Les éléments d’un tableau sont comme des cases rangées successivement dans la
mémoire centrale. Ils sont numérotés par des indices.
Par exemple, les éléments du tableau linéaire tab déclaré ci-dessus, qui comporte 100
éléments, ont des indices 0, 1, 2, ..., 99 ; soit tab*0+, tab|1+, tab*2+, …, tab*99+.
Et les éléments du tableau tabs qui comporte 50 lignes et 40 colonnes, ont des
indices *0, 0+, *0, 1+, …, *1, 0+, *1, 1+, … *49, 39+
Les indices des éléments d’un tableau commencent à 0 et non pas à 1. Par
conséquent, les éléments d’un tableau linéaire à N éléments ont leurs indices allant
de 0 à N−1. L’accès dans ce cas à un élément d’indice supérieur ou égal à N
provoquera systématiquement un résultat faux, et généralement une erreur
mémoire (erreur de segmentation). Il est de même pour un tableau binaire à M lignes
et N colonnes, dont les éléments ont leurs indices allant de (0, 0) à (M-1, N-1). L’accès
dans ce cas à un élément d’indice de ligne supérieur ou égal à M et/ou d’indice de
colonne supérieur ou égal à N provoquera systématiquement un résultat faux (erreur
mémoire). On parle de dépassement de tableaux.
Plus généralement, l’élément d’indice i dans le tableau linéaire tab est noté tab[i],
tandis que l’élément de la ligne i et colonne j dans le tableau tabs est noté tabs[i, j].
Les indices i et j doivent impérativement être des nombres entiers positifs ou nuls. Ils
peuvent aussi être le résultat de toute une expression comme dans tab[3*m+2], où m
est un entier.
Pour parcourir un tableau linéaire, on le balaye avec une boucle Pour (car le nombre
de cases est connu à l’avance) ; et pour un tableau binaire, on le balaye avec deux
boucles Pour imbriquées.
Exercice : Ecrire un algorithme qui lit les effectifs de personnes souffrant d’une de 5
maladies données, dans chacune de 24 communes de Kinshasa. Ensuite, il détermine
l’effectif total par maladie pour l’ensemble de la ville de Kinshasa.
IV.1.2. Structures
Une structure est un type qui permet de stocker plusieurs données, de même type ou
de types différents, dans une même variable de type structure. Une structure est
composée de plusieurs champs, chaque champ correspondant à une donnée.

Prof. BATUBENGA MWAMBA Nz. J.D. 42


Cours d’Algorithmique et Structures de données

Exemple : déclaration d’une structure Point qui contient trois champs x, y et z de réel.
struct point
{ /* déclaration de la structure */
Réel x, y, z; /* trois champs x, y, z se déclarent comme des variables */
/* mais on ne peut pas initialiser les valeurs */
};
La déclaration d’une variable de type struct point se fait ensuite comme pour une
autre variable :
struct point P; /* Déclaration d’une variable P */
Utilisation d’une structure
Une fois la variable déclarée, on accède aux données x, y, z du point P par un point.
Ces données sont désignées dans le programme par P.x, P.y, P.z. Ces données P.x, P.y,
P.z, ici de type réel, sont traitées comme n’importe quelle autre donnée de type réel
dans le programme.
Notons que l’on pourrait rajouter d’autres données des types que l’on souhaite à la
suite des données x, y, z dans la structure. Voici un exemple de programme avec une
structure point.
Début
struct point
{ /* déclaration de la structure */
Réel x, y, z;
};/* ne pas oublier le point-virgule */
struct point P;
Afficher("Veuillez entrer les coordonnées d’un point 3D :");
Lire(P.x, P.y, P.z);
Afficher("L’homothétie de centre O et de rapport 3");
Afficher("appliquée à ce point donne :’’);
Afficher(3*P.x, 3*P.y, 3*P.z);
Fin

Prof. BATUBENGA MWAMBA Nz. J.D. 43


Cours d’Algorithmique et Structures de données

IV.1.3. Pointeur
La mémoire centrale d’un ordinateur est composée d’un très grand nombre d’octets.
Chaque octet est repéré par un numéro appelé adresse de l’octet. Chaque variable
dans la mémoire occupe des octets contigus, c’est-à-dire des octets qui se suivent.
Par exemple, un float occupe 4 octets qui se suivent. L’adresse de la variable est
l’adresse de son premier octet.
L’adresse d’une variable peut elle-même être mémorisée dans une autre variable. Les
variables dont les valeurs sont des adresses des autres vraibles s’appellent des
pointeurs. Nous notons ici une variable pointeur par PTR.
À partir d'un type t quelconque, on peut définir un typepointeur_sur_t. Les variables
du type pointeur_sur_t contiendront, sous forme d'une adresse mémoire, un mode
d'accès au contenu de la variable de type t, appelée variable pointée. Celle-ci
n'aura pas d'identificateur, mais sera accessible uniquement par l'intermédiaire de
son pointeur. Nous notons ici une variable pointée par le pointeur PTR avec
l’identificateur *PTR+ ou PTR^.

Quelques syntaxes :
 Déclarer un pointeur : PTR : pointeur_sur_t ou ^t.
 Créer une variable dynamique de type t : allouer(PTR)
 Libérer la place occupée par la variable dynamique créée : desallouer(PTR)
 Affecter une valeur à la variable pointée : PTR^ ← <expression de type t>
 Lire une valeur de type t et la mettre dans la variable pointée : lire(ptr^)
 Afficher la valeur présente dans la zone pointée : écrire(ptr^)
Exemple :
L'algorithme suivant n'a comme seul but que de faire comprendre la
manipulation des pointeurs. À droite les schémas montrent l'état de la mémoire en
plus de ce qui s'affiche à l'écran.
Variables
ptc : ^chaîne de caractères

Prof. BATUBENGA MWAMBA Nz. J.D. 44


Cours d’Algorithmique et Structures de données

ptx1, ptx2 : ^entier

Début
allouer(ptc)
ptc^ ←'chat'

écrire(ptc^)
allouer(ptx1)

lire(ptx1^)
ptx2 ←ptx1

écrire(ptx2^)
ptx2^ ←ptx1^ + ptx2^

écrire(ptx1^, ptx2^)
ptx1 ←Nil

écrire(ptx2^)
désallouer(ptx2^)

ptx2 ←Nil

ptc ←Nil

Fin

Remarque : Erreur commise, on a coupé l'accès à la zone de mémoire contenant


« Chat » sans la désallouer. Elle devient donc irrécupérable.
On peut déclarer un pointeur qui pointe vers une variable de type structuré. Dans ce
cas, la variable pointée sera également définie par le nom du pointeur suivi de ^, et
on accèdera à ses différents champs de la manière que pour les variables simples de
type structuré.

Prof. BATUBENGA MWAMBA Nz. J.D. 45


Cours d’Algorithmique et Structures de données

Exemple :
Algorithme Pointeur2;
Structure fiche {
Nom : Chaine de caractères
Note : entier
}
pfiche : ^fiche
var p, q, Meilleur : pfiche
Début
Allouer(p)
Allouer(q)
p^.nom ← “Pascal”
p^.note ← 18
q^.nom ← “Blaise”
q^.note ← 16
Si p^.note > q^.note alors Meilleur ← p sinon Meilleur ← q
Ecrire(“Le meilleur est : “, Meilleur^.nom)
Fin
On peut aussi définir un type structuré avec un ou plusieurs champs qui soient du
type pointeur qui pointe vers le même type structure. Ceci permet d’implémenter
des structures comme des listes chaînées, arbres et graphes.
Exemple :
Structure Maillon {
valeur : Entier
suivant : ^Maillon
}
IV.2. Généralités sur les listes chaînées
Une liste est une suite d’un nombre variable d’objets de même type, appelés
éléments de la liste. Il s’agit d’une collection linéaire d’éléments d’information. Elle
est enregistrée sur un support adressable et il existe une action simple permettant de
passer d’un élément à l’élément suivant s’il existe.
Deux points sont importants dans ce qui précède :

Prof. BATUBENGA MWAMBA Nz. J.D. 46


Cours d’Algorithmique et Structures de données

1. Le fait que la liste soit constituée d’un nombre variable d’éléments implique que
ceux-ci puissent être crées ou supprimés en fonction des besoins du traitement :
ce sont des objets dynamiques.
2. La liste est enregistrée sur un support adressable : pour désigner un élément, on
utilisera en général un pointeur. Le support pourra être la mémoire centrale ou
un périphérique à accès sélectif.
Les tableaux constituent un moyen de rangement de données avant leurs
traitements. Ils présentent certains inconvénients. Par exemple, il est relativement
cher d’insérer et de supprimer des éléments dans un tableau. De plus comme un
tableau occupe généralement un bloc d’espace mémoire, il n’est pas possible d’en
doubler ou d’en tripler simplement la taille quand cela est nécessaire. Pour cette
raison, on dit que les tableaux sont des listes définies comme des structures des
données statistiques. Comment représenter une liste dans une mémoire adressable
où il faut pouvoir y effectuer différents traitements tels que :
- accéder au premier élément (tête de liste) ;
- accéder aux divers autres éléments ;
- supprimer ou ajouter des éléments.
Une première manière de ranger une liste en mémoire est d’intégrer à chaque
élément de la liste un champ appelé lien ou pointeur qui contient l’adresse de
l’élément suivant de la liste. Dans ces conditions, les éléments successifs de la liste
n’ont plus besoin d’occuper en mémoire des positions adjacentes. Cela rend plus
facile l’adjonction et la suppression.
Il en résulte que si le problème posé est de rechercher des éléments dans la liste pour
les insérer ou les supprimer, comme dans le cas du traitement de texte, il vaut mieux
ranger les données dans une liste utilisant des pointeurs que dans un tableau. Ce type
de structure est appelé liste chaînée. Les listes circulaires et bidirectionnelles (ou
doublement chaînées) sont les généralités naturelles des listes chaînées.
Une deuxième manière de ranger une liste en mémoire est la représentation
contiguë. Une liste contiguë est une liste dont les éléments sont adjacents sur le
support adressable. Nous n’insisterons pas sur cette représentation, car elle est peu
utilisée à cause de sa structure et des limitations de traitement qui en découle.

Prof. BATUBENGA MWAMBA Nz. J.D. 47


Cours d’Algorithmique et Structures de données

IV.2. Définition d’une liste chaînée

Une liste chaînée (monodirectionnelle) est un ensemble d’éléments d’informations


appelés nœuds, où l’ordre linéaire est assuré par des pointeurs.
Chaque nœud est divisé en deux parties, la première contenant l’information dont
l’élément est porteur, et la deuxième, appelée champ de lien ou champ du pointeur,
contient l’adresse du nœud suivant dans la liste.
Exemple d’une liste chaînée à 6 nœuds

NAME

ou 

START
    X

(2) (1)

(1) champ du pointeur suivant du troisième nœud.


(2) Champ information du troisième nœud
On note que chaque nœud comporte deux parties, celle de gauche représente la
partie information qui peut contenir un enregistrement complet d’items de données
(NOM, ADRESSE, …) et celle de droite représente le champ du pointeur du nœud.
Le pointeur du dernier nœud contient une valeur particulière, appelée pointeur Null
qui est une adresse invalide. Dans la pratique, on utilise le 0 ou un nombre négatif
pour le pointeur vide (). Il signale la fin de la liste.
La liste chaînée comporte un pointeur de liste (START) qui contient l’adresse du
premier nœud de la liste. Une liste sans nœuds est dite nulle ou vide.
IV.3. Représentation en mémoire des listes chaînées
Considérons une liste chaînée LISTE. Pour sa représentation en mémoire, LISTE
nécessite deux tableaux linéaires, soient INFO et LINK (lien) tels que INFO  K et LINK
K contiennent respectivement, la partie information et le champ du pointeur d’un
nœud de LISTE.
LISTE nécessite également un nombre de variable, START qui contient la position du
début de la liste et une indication de pointeur suivant notée NULL qui indique la fin
de la liste.

Prof. BATUBENGA MWAMBA Nz. J.D. 48


Cours d’Algorithmique et Structures de données

Exemple : Soit la représentation en mémoire de la chaîne de caractère ci-après :

INFO LINK
1
2
3 O 6
4 T 0
START 9
5
6  11
7 X 10
8
9 N 3
10 I 4
11 E 7
12

La chaîne s’obtient de la manière suivante :


START = 9 et INFO 9 = N est le premier caractère
LINK  9 = 3 et INFO 3 = 0 est le second caractère
LINK 3 = 6 et INFO 6 =  (blanc) est le troisième caractère
LINK 6 = 11 et INFO 11 = E est le quatrième caractère
LINK 11 = 7 et INFO 7 = X est le cinquième caractère
LINK 7 = 10 et INFO 10 = I est le sixième caractère
LINK 10 = 4 et INFO 4 = T est le septième caractère
LINK 4 = 0, la valeur NULL, la liste est terminée.
La chaîne représentée en mémoire est ‘ NO EXIT ‘
IV.4. Opérations dans une liste chaînée
On étudiera dans la suite les opérations suivantes dans une liste chaînée :
Défilement, recherche, insertion et suppression.
Nota : Nous adoptons la notation suivante : Pour une variable pointeur PTR qui
pointe sur le nœud (variable pointée) actuellement en cours de traitement, le
premier champ de celui-ci sera noté INFO [PTR] et le second LINK[PTR] en lieu et
place de PTR^.INFO et PTR^.LINK.

Prof. BATUBENGA MWAMBA Nz. J.D. 49


Cours d’Algorithmique et Structures de données

IV.4.1. Défilement d’une liste chaînée


Soit LISTE, une liste chaînée en mémoire, rangée dans les tableaux linéaires INFO et
LINK, avec START pointeur sur le premier élément et NULL indiquant la fin de LISTE.
Supposons que nous souhaitons faire défiler LISTE dans le but de traiter chaque
nœud, exactement une fois.
Nous proposons ici un algorithme de défilement qui utilise une variable pointeur PTR
qui pointe sur le nœud actuellement en cours de traitement. Par conséquent, LINK
PTR pointe sur le nœud suivant. L’instruction PTR= LINK PTR déplace le pointeur
au nœud suivant de la liste.
Dans cet algorithme, nous commençons par initialiser PTR à START, puis traitement
de INFOPTR, information contenue dans le premier nœud. Mise à jour de PTR par
l’affectation PTR = LINK PTR, de manière à ce que PTR pointe sur le second nœud.
Ensuite, traitement de INFOPTR, information contenue dans le second nœud, de
nouveau mise à jour de PTR = LINK PTR et traitement de INFOPTR, information du
troisième nœud. Et ainsi de suite jusqu’à ce que PTR = NULL qui indique la fin de la
liste.
Algorithme
1. Poser PTR = START  initialisation du pointeur
2. Repeat les étapes 3 et 4 tant que PTR  NULL
3. Write INFO PTR
4. Poser PTR = LINK  PTR mire à jour du pointeur
 Fin de la boucle étape 2
5. EXIT.
Cet algorithme imprime l’information contenue dans chaque nœud d’une liste
chaînée.
IV.4.2. Recherche dans une liste chaînée
Soit LISTE, une liste chaînée rangée en mémoire. Considérons un ITEM d’une
information donnée. Nous nous propose d’étudier ici la recherche de la position LOC
du nœud où ITEM apparaît pour la première fois dans LISTE.
A. LISTE n’est pas ordonnée
Supposons que les données dans LISTE ne sont pas obligatoirement ordonnées.
Alors, la recherche de ITEM dans LISTE se fait par défilement de la liste en utilisant
une variable pointeur PTR et en comparant ITEM au contenu INFO PTR de chaque
nœud de LISTE, pris un à un. Avant de mettre à jour le pointeur PTR par PTR=

Prof. BATUBENGA MWAMBA Nz. J.D. 50


Cours d’Algorithmique et Structures de données

LINKPTR, nous devons effectuer deux tests. D’abord, il faut voir si nous n’avons pas
atteint la fin de la liste, c'est-à-dire contrôler si PTR= NULL, sinon, nous cherchons si
INFOPTR = ITEM.
Algorithme
1. Poser PTR = START
2. Répéter étape 3 tant que PTR  NULL
3. Si ITEM = INFO PTR alors :
Poser LOC = PTR, et Exit
Si non
Poser PTR= LINKPTR  PTR pointe sur le nœud suivant
Fin de la structure if
Fin de boucle de l’étape2
4. Poser LOC= NULL La recherche est un échec
5. Exit
Remarque : La fonction de complexité de cet algorithme est la même que celle de
l’algorithme de recherche linéaire dans les tableaux linéaires. Dans le cas le plus
favorable, f (n) = 0 (n), dans le cas courant f (n) = 0 (n/2)
B.LISTE est ordonnée.
On cherche toujours ITEM en faisant défiler LISTE, en utilisant un pointeur PTR et en
comparant ITEM au contenu de INFO PTR de chaque nœud de LISTE, pris un à un.
On arrête le processus dès que ITEM excède à INFOPTR.
L’algorithme suivant trouve la position LOC du nœud où ITEM apparaît pour la
première fois dans LIST :
Algorithme :
1. Poser PTR = START
2. Repeat étape 3 tant que PTR  NULL :
3. Si ITEM  INFO PTR, alors :
Poser PTR = LINKPTR PTR pointe sur le nœud suivant 
sinon si ITEM = INFOPTR, alors :
Poser LOC = PTR, et Exit Succès
Sinon :
Poser LOC = NULL, et Exit ITEM est supérieur à INFOPTR
Fin de structure if
*Fin de boucle de l’étape 2+
4. Poser Loc = NULL
5. Exit.

Prof. BATUBENGA MWAMBA Nz. J.D. 51


Cours d’Algorithmique et Structures de données

Remarque : La complexité est toujours la même que celle dans autres algorithmes
de recherche linéaire. C'est-à-dire le temps d’exécution dans le cas le plus
défavorable et proportionnel à n : f (n) = 0(n), n nombre d’élément de LISTE, dans le
cas le plus courant f (n) = 0 (n/2).
IV.4.3. Insertion dans une liste chaînée
Soit LISTE, une liste chaînée de nœuds successifs A et B. on désire insérer un nœud N
entre A et B.
START


Nœud A Nœud B

     X
xXX
(Avant insertion)
START

Nœud A Nœud B

     X


Nœud N (Après insertion)
On note que le nœud A pointe à présent sur le nœud N et le nœud N pointe sur le
nœud B. notons que les cases mémoires inutilisées dans les tableaux sont aussi liées,
de manière à former une liste chaînée utilisant AVAIL (pour available : disponible)
comme variable pointeur de liste. La liste de mémoire disponible sera aussi appelée
la liste AVAIL. Une telle structure de données sera souvent désignée en écrivant
LIST (INFO, LINK, START, AVAIL). La figure de l’insertion se présente de la manière
suivante.
START

Nœud A Nœud B
     X

AVAIL

Nœud N
     X

Liste des emplacements libres


Prof. BATUBENGA MWAMBA Nz. J.D. 52
Cours d’Algorithmique et Structures de données

Les trois champs de pointeur ont été modifiés comme suit :


(1) le champ de pointeur du nœud A pointe maintenant sur le nœud N sur
lequel pointait précédemment AVAIL
(2) AVAIL pointe, maintenant, sur le second nœud de la liste d’espace
disponible, sur lequel pointait précédemment le nœud N.
(3) Le champ de pointeur du nœud N pointe maintenant sur le nœud B, sur
lequel pointait précédemment le nœud A.
IV.4.4. Algorithme d’insertion
Il existe plusieurs algorithmes qui insèrent des nœuds dans les listes chaînées. Nous
étudierons trois algorithmes différents suivant les cas : le premier insère un nœud en
début de liste, le second, après un nœud de position donnée, et le troisième un
nœud dans une liste ordonnée. On considère que les algorithmes agissent sur une
liste chaînée rangée en mémoire sous la forme LIST (INFO, LINK, START, AVAIL) et
l’information nouvelle sera contenue dans la variable ITEM. Les différents
algorithmes comprendront les étapes ci-après :
a) Test de la liste AVAIL pour contrôler si elle comporte les espaces disponible.
Dans le cas contraire, c'est-à-dire si AVAIL = NULL, l’algorithme imprimera le
message OVER FLOW (dépassement de capacité).
b) Extraction du premier nœud de la liste AVAIL. La variable NEW (nouvelle) sera
utilisée pour garder la trace de la position de ce nouveau nœud, cette étape
pouvant être implémentée par la paire suivante d’affectation, dans cet ordre :
NEW = AVAIL
AVAIL = LINK AVAIL
c) Copie de l’information nouvelle dans le nouveau nœud. C'est-à-dire
INFO NEW = ITEM

START
 Liste des emplacements libres

  

AVAIL

Prof. BATUBENGA MWAMBA Nz. J.D. 53


Cours d’Algorithmique et Structures de données

A) Algorithme 1
On suppose que notre liste chaînée ne soit pas obligatoirement ordonnée et qu’il n’y
ait aucune raison que le nœud nouveau soit inséré en une position particulière
donnée dans la liste. Il s’agit d’une insertion en tête de liste.
Cet algorithme insère ITEM en tête de la liste dont il devient alors le premier nœud.
ALGO 1 (INFO, LINK, START, AVAIL, ITEM)
1. Si AVAIL= NULL, alors WRITE OVER FLOW, et Exit
2. Poser NEW= AVAIL et AVAIL= LINK [AVAIL]
*Extraction du premier nœud dans la liste AVAIL+
3. Poser INFO [NEW] = ITEM [copie des données nouvelles dans le
nœud nouveau+
4. Poser LINK [NEW] = START [le nouveau nœud pointe maintenant
sur le nœud précédemment en première position+.
5. Poser START = NEW [modification de START qui pointe maintenant
sur le nœud nouveau+
6. Exit.

START

  X

NEW
Insertion de ITEM au début d’une liste

ITEM 

B. Algorithme 2
Il s’agit d’une insertion après un nœud donnée. Supposons que nous ayons la valeur
LOC, où LOC est la position d’un nœud A dans une liste chaînée LIST ou LOC= NULL.
L’algorithme qui suit insère ITEM dans LIST de manière à ce que ITEM suive le nœud A
ou, lorsque LOC = NULL, ITEM soit le premier nœud. Soit le nœud nouveau NEW (N).
Si LOC = NULL, N est inséré comme premier nœud (c’est-à-dire en tête de liste) dans
LIST, c’est le cas de l’algorithme 1. Si non, le Nœud N pointera sur le nœud B (qui à
l’origine suivait le nœud A) en vertu de l’affectation LINK *NEW+ = LINK [LOC] et le
nœud A pointera sur le nœud nouveau N par suite de l’affectation LINK *LOC+ = NEW.
Cet algorithme insère ITEM comme premier nœud de position LOC ou insère ITEM
comme premier nœud de la liste si LOC= NULL.

Prof. BATUBENGA MWAMBA Nz. J.D. 54


Cours d’Algorithmique et Structures de données

ALGO 2 (INFO, LINK, START, AVAIL, LOC, ITEM)


1. Si AVAIL= NULL, alors write OVER FLOW, et EXIT
2. Poser NEW= AVAIL et AVAIL = LINK [AVAIL]
*Extraction du premier nœud dans la liste AVAIL+
3. Poser INFO [NEW] = ITEM [copie des données nouvelles dans le
nouveau nœud+
4. Si LOC=NULL alors [insérer comme premier nœud+
Poser LINK [NEW] =START et START= NEW.
Si non:[insérer après le noeud de position LOC]
Poser LINK [NEW] =LINK [LOC] et
LINK [LOC] = NEW
[Fin de la structure if]
5. Exit.
C. Algorithme 3
Il s’agit de l’insertion dans une liste chaînée ordonnée. Soit ITEM à insérer dans une
liste chaînée ordonnée LIST. Dans ce cas, ITEM doit être inséré entre les nœuds A et B
de manière que INFO (A) ITEM  INFO (B).
L’algorithme qui suit trouve la position LOC du nœud A, c'est-à-dire détermine la
position LOC du dernier nœud de LIST dont la valeur est inférieur à celle de ITEM.
Il s’agit du défilement de la liste en utilisant la variable pointeur PTR, comparaison de
ITEM à INFO *PTR+ à chaque nœud. Pendant ce défilement, trace est gardée du nœud
précédent au moyen de la variable pointeur SAVE. SAVE et PTR sont mires à jour par
les affectations :
SAVE= PTR et PTR= LINK [PTR]
Le défilement continue jusqu’à ce que INFO *PTR+  ITEM, ou en d’autres termes, il
s’arrête dès que ITEM  INFO [PTR]. Alors PTR pointe sur le nœud B de manière à ce
que SAVE contienne la position du nœud A.
Les cas où la liste est vide ou que ITEM  INFO [START], et LOC= NULL, sont traités
séparément, dans la mesure où ils ne font pas appel à la variable Save.
Cet algorithme détermine la position LOC du dernier nœud d’une liste ordonnée, tel
que INFO [LOC]  ITEM ou Poser LOC= NULL
ALGO 3 (INFO, LINK, START, ITEM, LOC)
1. Si START= NULL, alors Poser LOC=NULL et Return. [liste vide]
2. Si ITEM  INFO [START], alors Poser LOC=NULL et Return [cas particulier ?]

Prof. BATUBENGA MWAMBA Nz. J.D. 55


Cours d’Algorithmique et Structures de données

3. Poser SAVE= START et PTR=LINK(START]


[Initialisation du pointeur]
4. Répéter les étapes 5 et 6 tant que PTR  NULL
5. Si ITEM  INFO (PTR], alors :
Poser LOC= SAVE, et Return.
[Fin de structure Si]
6. Poser SAVE = PTR et PTR= LINK [PTR]
[Mise à jour des pointeurs]
*Fin de la boucle de l’étape 4+
7. Poser LOC = Save
8. Return

SAVE PTR
START
 

     X

On obtient aussi un algorithme qui insère ITEM dans une liste chaînée ordonnée.
ALGO (INFO, LINK, START, AVAIL, ITEM)
1. *Application de l’algorithme 3 pour déterminer la position du nœud
précédent ITEM]
CALL ALGO 3 (INFO, LINK, START, ITEM, LOC)
2. *Application de l’algorithme 2 pour insérer ITEM après le nœud en
position LOC]
CALL ALGO 2 (INFO, LINK, START, AVAIL, LOC, ITEM).
3. Exit.
D. Exemple : Considérons l’exemple d’un hôpital qui comporte 12 lits dont 9 sont
occupés. On suppose que la liste est rangée dans le tableau linéaire BED et LINK,
c'est-à-dire le patient dont le lit est K est affecté à BED [K].

Prof. BATUBENGA MWAMBA Nz. J.D. 56


Cours d’Algorithmique et Structures de données

On note que les lits suivants 10, 2 et 6 sont libres.

START BED LINK


5 1 KIBOKO 7
2 6
3 DIANA 11
4 MUTOMBO 12
5 AKELA 3
AVAIL 6
7 LUBUYA 4
5
8 GABY 1
9 SULUA 0
10 2
11 FATAKI 8
12 NDEMA 9

On a une liste alphabétique des patients avec la liste AVAIL. Supposons que JEAN doit
y être ajouté. Nous allons appliquer ALGO ou plus précisément ALGO 3 d’abord et
l’algorithme ALGO ensuite. Il convient de noter que ITEM= JEAN et INFO=BED.
a. ALGO 3 (BED, LINK, START, ITEM, LOC)
1. Puisque START  NULL, le contrôle est transféré à 2
2. Puisque BED [5] = AKELA JEAN, le contrôle est transféré à 3
3. SAVE = 5 et PTR = LINK [5] = 3
4. On exécute étapes ci-après :
 BED (3) = DIANA JEAN, SAVE = 3 et PTR = LINK [3]=11
 BED (11)= FATAKI JEAN, SAVE = 11 et PTR = LINK [11]=8
 BED (8) = GABY JEAN, SAVE = 8 et PTR = LINK[8]=1
 Puisque BED (1) = KIBOKO JEAN, nous avons LOC = SAVE = 8 et Return.
b. ALGO 2 ( BED, LINK, START, AVAIL, LOC, ITEM) [Ici LOC = 8]
1. Puisque AVAIL  NULL, le contrôle est transféré à 2
2. NEW=10 et AVAIL= LINK [10]=2
3. BED [10]= JEAN
4. Puisque LOC  NULL nous avons
LINK [10] = LINK [8] = 1 et LINK [8] = NEW=10
5. Exit

Prof. BATUBENGA MWAMBA Nz. J.D. 57


Cours d’Algorithmique et Structures de données

Après insertion de JEAN, sur la liste des patients, les trois pointeurs AVAIL, LINK [10]
et LINK [8] ont été modifié.

START 5 BED LINK


1 KIBOKO 7
2 6
3 DIAMA 11
4 MUTOMBO 12
5 AKELA 3
6
7 LUBOYA 4
8 GABY 1
9 SULUA 0
AVAIL 2
10 JEAN 2
11 FATAKI 8
12 NDEMA 9

IV.4. 5. Suppression dans une liste chaînée.

Soit LIST, une liste en mémoire sous la forme LIST (INFO, LINK, START, AVAIL) et N un
nœud compris entre les nœuds A et B. on se propose de supprimer le nœud N dans la
liste chaînée. La suppression est exécutée dès que le champ de pointeur de nœud
suivant le nœud A est modifié de manière à ce qu’il pointe sur le nœud B.
Par conséquent, lorsqu’on supprime un nœud, il est nécessaire de garder la trace de
l’adresse du nœud qui précède immédiatement le nœud qui va être supprimé.

START


Nœud A Nœud N Nœud B
     X
A

Avant suppression

START


Nœud A Nœud N Nœud B
  
A A   X

Après suppression
Il convient de souligner que dès qu’un nœud est supprimé dans une liste, il sera
incorporé en tête de la liste AVAIL. On a les opérations suivantes :

Prof. BATUBENGA MWAMBA Nz. J.D. 58


Cours d’Algorithmique et Structures de données

(1) Le champ de pointeur de nœud suivant le nœud A pointe sur B, sur lequel pointait
précédemment le nœud N.
(2) Le champ de pointeur de nœud suivant le nœud N pointe maintenant sur le nœud
originellement premier de la liste d’espace libre, sur lequel pointait
précédemment AVAIL.
(3) AVAIL pointe sur le nœud supprimé.
N.B : Cas particuliers :
a) Si le nœud N supprimé est le premier nœud de la liste, START pointeur sur le nœud
B.
b) Si le nœud N supprimé est le dernier nœud de la liste, le nœud A contiendra le
pointeur Null.

START
 Nœud Nœud
Nœud N
 A   B    X

AVAIL Liste des données


   X

Liste des emplacements libres

Exemple :
Considérons la liste des malades d’un hôpital (IV. 3), supposons que GABY soit sort de
l’hôpital. Il en résulte que BED (8) est vide. Par conséquent, pour que la liste chaînée
existe toujours, il faut exécuter les trois modifications suivantes des champs de
pointeurs :
LINK [11] = 10 LINK [8] = 2 AVAIL = 8
La première signifie que FATAKI (II) pointe sur JEAN.
La deuxième et la troisième modification ajoutent le lit vide à liste AVAIL. Avant
d’effectuer la suppression, il fait trouver le nœud BED *8+ supprimé.
IV.4.6. Algorithmes de suppression
Il existe une diversité d’algorithme de suppression dans les listes chaînées qui
répondent à des besoins nombreux. Nous nous proposons d’étudier ici deux
algorithmes :

Prof. BATUBENGA MWAMBA Nz. J.D. 59


Cours d’Algorithmique et Structures de données

- le premier supprime le nœud suivant un nœud donné.


- le second, supprime un nœud comportant une information donnée ITEM.
Comme déjà signalé les algorithmes de suppression retourneront l’espace mémoire
relatif au nœud N supprimé à la tête de la liste AVAIL, d’où les affectations LINK *LOC+
= AVAIL et AVAIL = LOC, LOC représente la position du nœud N supprimé.
Un algorithme qui exécute une opération de suppression doit d’abord contrôler s’il
existe un nœud dans la liste. Sinon, c'est-à-dire START = NULL, il imprimera le
message UNDERFLOW (dépassement de capacité négatif).
L’algorithme suivant (DEL1) supprime le nœud N de position LOC. soit LP la position
du nœud qui précède N, quand N est le premier nœud, LP = NULL.
DEL1 (INFO, LINK, START, AVRIL, LOC,)
1. Si LP = NULL, alors N est le premier noeud.
Poser START = LINK [START] [supprimer le premier nœud+
Sinon
Poser LINK [LP] = LINK [LOC] [supprimer le noeud]
[fin de la structure if]
2. *Renvoi du nœud supprimé à la liste AVAIL+
Poser LINK [LOC] = AVAIL et AVAIL = LOC
3. Exit.

START


Nœud 1 Nœud 2 Nœud 3
  

START = LINK [START]


Suppression du premier nœud de la liste

Le second algorithme de suppression consiste à supprimer dans LIST le premier nœud


qui contient ITEM. Il faut savoir qu’avant de supprimer N, il est indispensable de
connaître la position du nœud qui le précède. C’est pourquoi nous introduisons
d’abord une procédure qui aura pour objet de déterminer les positions du nœud N
contenant ITEM et du nœud précédent le nœud N, respectivement LOC et LP. Si N est
le premier nœud de la liste, nous poserons LP = NULL et si ITEM n’apparaît pas dans
LIST, LOC = NULL (comme ALGO 3).
Ensuite, on fait le défilement de la liste en utilisant la variation pointeur PTR et en
comparant ITEM à INFO *PTR+ en chaque nœud. Pendant le défilement, on garde la

Prof. BATUBENGA MWAMBA Nz. J.D. 60


Cours d’Algorithmique et Structures de données

trace de la position du nœud précédent en utilisant une variation SAVE comme dans
les autres cas déjà étudiés.
SAVE = PTR et PTR = LINK [PTR]
Le défilement va continuer tant que INFO [PTR]
INFO [PTR]  ITEM, en d’autres termes si INFO *PTR+ = ITEM, le défilement est
terminé. PTR contient alors la position LOC du nœud N et SAVE, la position LP du
nœud précédent N. notons que l’instruction INFO *START+ = ITEM signifie que N est le
premier nœud. La procédure PRO détermine la position LOC du premier nœud N qui
contient ITEM est la position LP du nœud précédent N. si ITEM n’appartient pas dans
la liste, la procédure pose LOC = NULL, si ITEM apparaît dans le premier nœud, elle
pose LP = NULL.
PRO ( INFO, LINK, START, ITEM, LOC, LP).
1. [liste vide ?] si START = NULL alors
Poser LOC = NULL et LP = NULL et Return
[Fin de la structure Si]
2. *ITEM dans le premier nœud ?] si INFO [START] = ITEM alors
Poser LOC = START et LP = NULL et Return.
[Fin de la structure Si]
3. Poser SAVE = START et PTR = LINK [START]
[Initialisation du pointeur]
4. Répéter les étapes 5 et 6 tant que PTR  NULL
5. Si INFO [PTR] = ITEM alors
LOC = PTR et LP = SAVE, et Return
[Tin de la direction if]
6. Poser SAVE = PTR et PTR = LINK [PTR]
[Mise à jour des pointeurs]
[Fin de la boucle de l’étape 4+
7. Poser LOC = NULL [Echec de la recherche]
8. Return
Avec cette procédure, nous allons présenter un algorithme dont l’objet est de
supprimer dans une liste chaînée le premier nœud N qui contient une information
donnée ITEM. L’algorithme DEL 2 exécute cette opération.
DEL2 (INFO, LINK, START, AVAIL, ITEM).
1. [Utiliser PRO pour trouver les positions de N et du nœud qui le précède+
CALL PRO (INFO, LINK, START, ITEM, LOC, LP)

Prof. BATUBENGA MWAMBA Nz. J.D. 61


Cours d’Algorithmique et Structures de données

2. si LOC = NULL alors Ecrire : ITEM n’est pas dans la liste, et exit.
3. [Supprimer le nœud+
Si LP = NULL, alors :
Poser START = LINK *START+ *supprimer le premier nœud+
Sinon :
Poser LINK [LP] = LINK [LOC] [supprime le Nœud N en général+
[Fin de la structure Si]
4. *Renvoyer le nœud supprimé à la liste AVAIL]
Poser LINK [LOC] = AVAIL et AVAIL = LOC
5. Exit
Exemple :

Considérons la liste de malade déjà étudiée dans les paragraphes précédents.


Supposons que GABY ait quitté l’hôpital. On utilise la procédure PRO pour trouver la
position LOC de BABY et celle du patient qui le précède, LP.
Ensuite nous utiliserons l’algorithme DEL2 pour supprimer GABY dans la liste. On a
donc ITEM = GABY, INFO = BED et START = 5.

BED LINK
1 KIBOKO 7
START 5 2 6
3 DIAMA 11
4 MUTOMBO 12
5 AKELA 3
AVAIL 6 0
8
7 LUBOYA 4
8 2
9 SULUA 0
10 JEAN 1
11 FATAKI 10
12 NDEMA 9

(a) PRO (BED, LINK, START, ITEM, LOC, LP)


1. Puisque START  NULL, passer à 2
2. Puisque BED [5] = AKELA  GABY, passer à 3
3. SAVE = 5 et PTR = LINK [5] = 3
4. Les étapes 5 et 6 de cette procédure :
 BED (3) = DIANA  GABY, et SAVE = 3 et

Prof. BATUBENGA MWAMBA Nz. J.D. 62


Cours d’Algorithmique et Structures de données

PTR = LINK [3] = 11


 BED (11) = PATAKI  GABY, et SAVE = 11 et
PTR = LINK [11] = 8
 BED (8) = GABY, et nous avons :
LOC = PTR = 8 et LP = SAVE = 11 et Return.
(b) 1. Call PRO (BED, LINK, START, ITEM, LOC, LP)
*D’où LOC = 8 et LP = 11+
2. Comme LOC  NULL, passer 3
3. Comme LP  NULL, nous avons
LINK [11] = LINK [8] = 10
4. LINK [8] = 2 et AVAIL = 8
5. Exit.
IV.4.7. Listes chaînées à en-tête
A. Définition
On appelle une liste chaîné à en-tête, une chaînée qui contient toujours un nœud
particulier, le nœud d’en-tête, situé, comme son nom l’indique, en tête de la liste. On
utilise souvent deux types de listes à nœuds d’en-tête :
1. Une liste à nœud d’en-tête mise à la tête est une liste à en-tête dont le dernier
nœud contient un pointeur vide.
START

 Nœud d’en tête

   X

2. Une liste circulaire à en-tête est une liste à en-tête dont le dernier nœud pointe
sur le nœud d’en-tête.
START

 Nœud d’en tête

   

B. Propriétés
1. Le nœud dans une liste à en-tête représente un nœud ordinaire, pas le nœud
d’en-tête. Par conséquent, le premier nœud dans une liste à en-tête est le

Prof. BATUBENGA MWAMBA Nz. J.D. 63


Cours d’Algorithmique et Structures de données

nœud qui suit le nœud d’en-tête et sa position est LINK [START] et non START
comme dans une liste ordinaire.
2. Dans l’algorithme qui utilise une variable pointeur pour faire défiler une liste
circulaire à en-tête, on commence par PTR = LINK [START] et non PTR = START
et on termine par PTR = START et non PTR = NULL.
3. Les listes circulaires à en-tête sont plus fréquemment utilisées que les listes
chaînées ordinaires parce qu’elles rendent de nombreuses opérations plus
faciles à formuler et à implémenter. Les raisons sont :
a. le pointeur vide n’est pas utilisé et, par conséquent, tous les pointeurs
contiennent des adresses valides. (on supprime le vide)
b. chaque nœud ordinaire possède un antécédent ou prédécesseur et il en
résulte que le premier nœud ne constitue plus un cas particulier.
c. Recherche dans une liste circulaire à en-tête
c1. L’algorithme ci-après détermine la position LOC du premier nœud LIST qui
contient ITEM quand LIST est une liste chaînée circulaire à en-tête.
CHA1 [INFO, LINK, START, LOC]
1. Poser PTR = LINK [START]
2. Répéter tant que INFO [PTR]  ITEM et PTR  NULL
Poser PTR = LINK [PTR]
[Le nœud PTR pointe sur le nœud suivant+
3. Si INFO [PTR] = ITEM, alors :
Poser LOC = PTR
Sinon
Poser LOC = NULL
[Fin de structure Si]
4. Exit.
Remarque : Dans la boucle de recherche (étape 2) les deux testes qui contrôlent la
boucle n’étaient pas exécutés au même moment dans l’algorithme relatif aux listes
ordinaires, car dans une liste ordinaire INFO *PTR+ n’est pas défini quand PTR = NULL.
(PTR = START)
c2. L’algorithme ci-après trouve les positions LOC du premier nœud N qui
contient ITEM et LP du nœud précédent N qui contient ITEM et LP du
nœud précédent N quant LIST est une liste circulaire à en-tête.
CHA2 [INFO, LINK, START, ITEM, LP)
1. Poser SAVE = START et PTR = LINK [START]

Prof. BATUBENGA MWAMBA Nz. J.D. 64


Cours d’Algorithmique et Structures de données

[Initialisation du pointeur]
2. Répéter tant que INFO [PTR]  ITEM et PTR  START
Poser SAVE = PTR et PTR = LINK [PTR]
[Mise à jour du pointeur]
[Fin de la boucle]
3. Si INFO [PTR] = ITEM, alors
Poser LOC = PTR et LP = SAVE
Sinon : Poser LOC = NULL et LP = SAVE
[Fin de la structure Si]
4. Exit
c3. L’algorithme suivant supprime le premier nœud N qui contient ITEM quand
LIST est une liste circulaire à en-tête.
1. *utiliser la procédure CHA2 pour trouver les positions de N et du nœud
qui le précède]
2. Si LOC = NULL, alors WITE : ITEM n’est pas dans la liste et Exit.
3. LINK *LP+ = LINK *LOC+ *supprimer le nœud+
4. *Renvoyer le nœud supprimé à la liste AVAIL+
Poser LINK [LOC] = AVAIL et AIVAIL = LOC
5. Exit
IV.4.8. Liste circulaire
Une liste chaînée dont le dernier nœud pointe sur le premier nœud au lieu de
contenir le pointeur vide, est appelée une liste circulaire.
En effet, une liste simplement chaînée ne permet pas, à partir d’un élément,
d’accéder directement à ceux qui le précèdent. Pour remédier à cet inconvénient, on
peut faire pointer le dernier élément sur le premier et obtenir ainsi une liste
circulaire qu’on appelle aussi liste en anneau.

START

     

IV.4.9. Rangement en mémoire des polynômes


Les listes chaînées à en-tête sont souvent utilisées pour le rangement en mémoire
des polynômes. Le nœud d’en-tête joue un rôle important dans cette représentation,
puisqu’il représente le polynôme zéro.

Prof. BATUBENGA MWAMBA Nz. J.D. 65


Cours d’Algorithmique et Structures de données

Polynôme d’une variable NOW


P (x) = 2x8 – 5x7 – 3x2 + 4
La partie information de chaque nœud est divisée en deux champs qui représentent,
respectivement, le coefficient et l’exposant relatifs au terme considéré et les nœuds
sont chaînés selon l’ordre décroissant des puissances.

START

 Coefficient Exposant

0 -1  2 8  -5 7  -3 2  4 0 

COEF EXP LINK


1 0 -1 3
POLY 1 2 5
3 2 8 4
4 -5 7 6
5 8
AVAIL 2 6 -3 2 7
7 4 0 1
8 9
9
10

IV.5. Chaînes Bidirectionnelles ou bilatères


A. Généralités
Dans les listes vues précédemment, le parcours ne peut se faire que dans un seul
sens : de la tête vers la queue. Il s’agit des listes monodirectionnelles. Une liste
bidirectionnelle, peut être parcourue dans deux directions :
Comme habituellement dans le sens direct de la tête à la queue, ou dans le sens
rétrograde, c'est-à-dire de la queue à la tête.
Dans ces conditions, étant donné la position LOC d’un nœud N, nous avons
immédiatement accès au nœud qui le suit et à celui qui le précède.

Prof. BATUBENGA MWAMBA Nz. J.D. 66


Cours d’Algorithmique et Structures de données

Cela entraîne qu’il est possible de supprimer N dans la liste sans avoir à n’en
parcourir aucune partie.
Une liste bidirectionnelle est un ensemble d’éléments d’informations appelés nœuds
où chacun d’entre eux est divisé en trois parties :
(1) Un champ d’information INFO qui contient les données de N
(2) Un champ de pointeur FORW qui contient la position du nœud suivant
dans la liste.
(3) Un champ de pointeur BACK qui contient la position du nœud précédent
dans la liste.
Ce type de liste nécessite également deux pointeurs de liste : FIRST, qui pointe sur le
premier nœud de la liste, et LAST, qui pointe sur le dernier.
Le pointeur vide apparaît dans le champ FORW du dernier nœud de la liste ainsi que
le champ BACK du premier nœud de la liste.

Champ INFO du Nœud N


FIRST Champ du pointeur BACK du nœud N
LAST
 Champ du pointeur FORW du Nœud N

X         X
4

Nœud N
En utilisant la variable FIRST et le champ de pointeur FORW, nous pouvons, comme
auparavant, parcourir une liste bidirectionnelle dans le sens direct habituel.
De plus, en utilisant la variable LAST et le champ de pointeur BACK, nous pouvons
maintenant la parcourir également dans le sens rétrograde.
Soit LOC A et LOC B les positions de deux nœuds A et B.
FORW [LOC A] = LOC B si et seulement si
BACK [LOC B] = LOC A c'est-à-dire le nœud B suit le nœud A si seulement si le nœud A
précède le nœud B.
B. Représentation en mémoire
Les listes bidirectionnelles peuvent être rangées en mémoire sous forme de tableaux
linéaires de la même manière que les listes monodirectionnelles, à cela qu’elles
nécessitent deux tableaux de pointeurs, FORW et BACK au lieu d’un seul, LINK, ainsi
que deux pointeurs de liste, FIRT et LAST, au lieu d’un START.

Prof. BATUBENGA MWAMBA Nz. J.D. 67


Cours d’Algorithmique et Structures de données

La liste AVAIL de l’espace mémoire disponible dans le tableau sera toujours rangée
sous la forme d’une liste monodirectionnelle faisant appel à FORW comme champ de
pointeur, puisque nous ne supprimons et nous n’insérons de nœud qu’en début de la
liste AVAIL.
C. Exemple
On reprend l’exemple de 9 patients qui sont réparties entre 12 lits dans une salle
d’hôpital. La liste alphabétique peut être organisée en une liste bidirectionnelle. Les
valeurs de FIRST et du pointeur FORW sont respectivement, les même que celles de
START et du tableau LINK et par conséquent, la liste peut être parcourue selon l’ordre
alphabétique direct. En outre, l’utilisation de LAST et du tableau de pointeur BACK
permet de parcourir cette liste selon l’ordre alphabétique inverse c'est-à-dire que
LAST pointe sur SULUA dont le champ de pointeur BACK pointe sur NDEMA, dont le
champ de pointeur pointe sur MUITOMBO et ainsi de suite.

LIT BED FORW BACK


1 KIBOKO 7 8
5
2 6
3 DIANA 11 5
4 MUTOMBO 12 7
LAST 5 AKELA 3 0
9 6 0
7 LUBOYA 4 1
AVAIL 8 GABY 2 11
10 9 SULUA 0 12
10 1
11 FATAKI 10 3
12 NDEMA 9 4

D. Listes bidirectionnelles à en-tête


Les avantages de la liste bidirectionnelle et ceux de la liste circulaire à en-tête
peuvent être combinés en une liste bidirectionnelle circulaire à en-tête.
Cette liste est circulaire perce que les nœuds situés aux deux extrémités pointent sur
le nœud d’en-tête.
Une telle liste bidirectionnelle ne nécessite qu’une seule variable pointeur START qui
pointe sur le nœud d’en-tête, car les deux pointeurs du nœud d’en-tête pointent.
Sur les nœuds situés aux deux extrémités de la liste.

Prof. BATUBENGA MWAMBA Nz. J.D. 68


Cours d’Algorithmique et Structures de données
START

Nœud d’en tête

 

       

E. Opérations sur les listes bidirectionnelles


E1. Défilement
On se propose de faire défiler LIST de manière à traiter une fois exactement chacun
des nœuds qui la contient. Si LIST est une liste bidirectionnelle ordinaire, nous
pouvons alors appliquer l’algorithme vu au IV.4.1 sur le défilement d’une liste
chaînée.
E.2. Recherche
On se propose de déterminer la position LOC de ITEM dans LIST. Nous pouvons
appliquer l’algorithme vu au IV.4.2 si LIST est une liste bidirectionnelle ordinaire et
l’algorithme C1 vu au IV.4.6 si LIST comporte un nœud d’en-tête. Dans ce dernier cas,
l’avantage est grand de pouvoir rechercher ITEM dans le sens rétrograde si nous
avons des raisons de suspecter qu’ITEM apparaît vers la queue de liste.
E3. Suppression
Supposons que LOC représente la position donnée d’un nœud N dans LIST et
supposons que nous souhaitions supprimer N dans LIST. On fait l’hypothèse que LIST
est une liste bidirectionnelle circulaire à en-tête. BACK [LOC] et FORW [LOC]
représente respectivement, la position du nœud qui précède et celle du nœud qui
suit N. il en résulte que, N est supprimé en modifiant la paire suivante de pointeurs :
FORW [BACK [LOC] = FORW [LOC] et BACK [FORW [LOC] = BACK [LOC]
De plus, le nœud supprimé est renvoyé à la liste AVAIL par l’instruction
FORW [LOC] = AVAIL et AVAIL = LOC
L’algorithme ci-après, valable pour les listes bidirectionnelles présente un réel
avantage car, pour supprimer N on n’a pas besoin de faire défiler la liste pour trouver
la position du nœud précèdent N.

Prof. BATUBENGA MWAMBA Nz. J.D. 69


Cours d’Algorithmique et Structures de données

ALGO BI1 [INFO, FORW, BACK, START, AVAIL, LOC]


1. [Supprimer le nœud]
Poser FORW [BACK [LOC] = FORM [LOC] et BACK [FORM [LOC]] = BACK [ LOC ]
2. *Renvoyer le nœud à liste AVAIL+
Poser FORW [LOC] = AVAIL et AVAIL = LOC
3. Exit.
E.4. Insertion
Supposons que nous souhaitions insérer un ITEM d’information donné entre les
nœuds A et B. comme dans le cas d’une liste monodirectionnelle il nous faut tout
d’abord extraire le premier nœud N de la liste AVAIL en utilisant la variable NEW pour
garder la trace de sa position, puis copier les données ITEM dans ce nœud. Il faut
donc initialiser :
NEW = AVAIL, AVAIL = FORW [AVAIL], INFO [NEW] = ITEM on insère N de contenu
ITEM dans la liste en modifiant les pointeurs :
FORW [LOC A] = NEW FORW [NEW] = LOC B
BACK [LOC B] = NEW BACK [NEW] = LOC A
ALGOBI 2 [INFO, FORW, BACK, START, AVAIL, LOCA, LOCB, ITEM]
2. Si AVAIL = NULL alors Ecrire OVERFLOW, et EXIT
3. *Extraire un nœud de la liste AVAIL et copier l’information nouvelle dans ce
nœud+
Poser NEW = AVAIL, AVAIL = FORW [AVAIL],
INFO [NEW] = ITEM
4. *Insérer le nœud dans la liste+
Poser FORW [LOC A] = NEW, FORW [NEW] = LOC B
BACK [LOC B] = NEW, BACK [NEW] = LOC A
5. Exit
E.5. Remarques
- L’algorithme ALGOBI2 suppose que LIST contient un nœud d’en-tête. Par
conséquent, LOCA ou LOC B peuvent pointer sur ce nœud d’en-tête, auquel
cas N sera aussi bien inséré comme premier ou dernier nœud de la liste.
- En général, le rangement des données sous forme d’une liste bidirectionnelle,
ce qui nécessite l’utilisation d’espace supplémentaire pour les pointeurs
rétrogrades et du temps supplémentaires pour modifier ces pointeurs, plutôt
que sous la forme d’une liste monodirectionnelle ne vaut pas la peine d’être

Prof. BATUBENGA MWAMBA Nz. J.D. 70


Cours d’Algorithmique et Structures de données

adopté que s’il est nécessaire de chercher fréquemment la position du nœud


qui précède un nœud donné N.
1. Si LINK [START] = NULL alors
Ecrire : UNDER FLOW et Exit [ liste vide ]
2. Poser PTR = LINK [ START ] et SAVE = START
[ Initialiser les pointeur ]
3. Répéter tant que LINK [ PTR ]  START
[ Défilement pour rechercher le dernier nœud +
Poser SAVE = PTR et PTR = LINK [ PTR ]
[ Mise à jour de SAVE et PTR ]
[ Fin de la boucle ]
4. Poser LINK [ SAVE ] = LINK [ PTR ]
[ Supprimer le dernier nœud +
5. Poser LINK [ PTR ] = AVAIL et AVAIL = PTR
[ Renvoyer le nœud à la tête AVAIL +
6. Exit.

Prof. BATUBENGA MWAMBA Nz. J.D. 71


Cours d’Algorithmique et Structures de données

Chap V. LES ARBRES


V.1. Généralités
Les tableaux et les listes chaînées ne permettent pas de représenter les informations
sur lesquelles, il existe une hiérarchie. Or des nombreuses applications en
informatique utilisent une organisation sous forme d'arbre. Il s'agit d'une structure
des données non linéaire. Le schéma ci-après montre la représentation sous la forme
d'arbre d'une variable composée.
Agent

Identité Date de naissance Qualification

Nom Postnom Jour Mois Année Fonction Diplôme

Date Lieu

V.2. Les arbres binaires


V.2.1. Définition et exemple
Un arbre binaire T est un ensemble muni d'éléments appelés nœuds, tels que :
a) T est vide (arbre nul ou arbre vide, ie aucun nœud) ou ;
b) T comporte un nœud particulier R, appelé racine de T et les autres nœuds de T
forment une paire ordonnée T1 et T2 d'arbres binaires disjoints.
 Si T comporte une racine R, les deux arbres T1 et T2 sont appelés
respectivement sous arbres de gauche et de droite de R.
 Si T1 est non vide, sa racine est le successeur de gauche de R ; de même si T2
est non vide, sa racine est le successeur de droite de R. (T1 et T2 sont binaires
et disjoints).

Prof. BATUBENGA MWAMBA Nz. J.D. 72


Cours d’Algorithmique et Structures de données

Exemple : L’arbre T ci-après comprend 11 nœuds.


A

B C

D E G H

F J K

1) La racine de T est le nœud A.


2) Le nœud B est le successeur de gauche et le nœud C est le successeur de
droite de la racine.
3) Le sous arbre de gauche de la racine A comprend les nœuds B, E, D et F, et
celui de droite les nœuds C, G, H, J, K et L.
4) Les nœuds A, B, C et H possède chacun deux successeurs. Les nœuds E et J un
seul et les nœuds D, F, G, K et L aucun. Les nœuds sans successeurs sont
appelés nœuds terminaux ou feuilles.
Remarque
Dans la mesure où l'arbre binaire est défini en fonction de sous arbres binaires T1 et
T2, on dit qu’il y a récursivité. C'est-à-dire, chaque nœud N de T comporte un sous
arbre de gauche et un sous arbre de droite. De plus, si N est un nœud terminal, les
deux sous arbres gauches et droites sont vides.
Deux arbres binaires T et T' sont similaires s'ils ont la même structure ou d'autres
termes s'ils ont la même forme.
V.2.2. Terminologie
Soit N un nœud d'un arbre T de successeur de gauche S1, et de successeur de droite
S2, N est appelé parent de S1 et de S2. De plus, S1 est appelé enfant de gauche de N,
S2 de droite de N ; S1 et S2 sont dits frères.
Pour chaque nœud N d'un arbre binaire T, il possède un parent unique appelé
prédécesseur de N. Un nœud quelconque L est appelé descendant d'un nœud N s'il
existe une succession d'enfants de N à L.
N étant alors appelé ancêtre de L, et L est appelé descendant de gauche ou de droite
de N selon que L appartient aux sous arbres de gauche ou de droite.

Prof. BATUBENGA MWAMBA Nz. J.D. 73


Cours d’Algorithmique et Structures de données

A chaque nœud d'un arbre binaire T est affecté un numéro de niveau. La hauteur
d'un arbre binaire est le nombre de nœuds qui constituent le plus long chemin de la
racine à une feuille.
5

4 4

1 1
3

La hauteur de cet arbre est 5 et on fait figurer dans chaque nœud la hauteur associée.
Un arbre binaire complet est un arbre binaire de hauteur k possédant 2 k - 1 nœuds.
Puisqu'il n'a pas de trous, un tel arbre peut être représenté par un tableau de 2 k - 1
éléments, tout simplement en rangeant les valeurs des nœuds niveau par niveau.
Autrement dit, un arbre binaire est considéré comme complet si toutes ses feuilles
sont au même niveau et que chacun de ses nœuds internes à deux enfants.
L'exemple ci-après représente l'arbre complet T15 (arbre à 15 nœuds) de hauteur 4.

Théorème : Un arbre binaire complet de hauteur h comprend l = 2h-1 feuilles et m =


2h-1 -1 nœuds internes.
Corollaire 1 : L’arbre binaire complet de hauteur h comprend un total de n = 2 h-1
nœuds.
Corollaire 2 : Dans tout arbre binaire de hauteur h.
h ≤ n ≤ 2h et
Avec n correspondant au nombre de ses nœuds.
Avec ce type d'étiquetage, il est facile de déterminer les enfants et le parent de tout
nœud K de l’arbre complet Tn. Pour le nœud K, les enfants de gauche sont 2*K et ceux

Prof. BATUBENGA MWAMBA Nz. J.D. 74


Cours d’Algorithmique et Structures de données

de droite 2*K+1. Le parent du nœud K est la partie entière de K/2.


Par exemple, les enfants du nœud 11 sont les nœuds 22 et 23, et le parent de 11 =
11/2 = Int[5,5] = 5.
La hauteur ou profondeur Dn de l'arbre complet est donné par :
.

Un arbre binaire presque complet est un arbre binaire dont tous les niveaux sont
pleins sauf le dernier, qui ne comporte que les p nœuds de gauche (un tel arbre n'est
pas complet, mais au moins il n'a pas de trous). Dans une représentation par tableau
définie comme pour les arbres binaires complets, il ne remplit donc que le début du
tableau.

Un arbre binaire T est appelé arbre pair ou encore arbre binaire étendu si chacun de
ses nœuds N possède soit 0, soit 2 enfants.
Dans ces conditions, les nœuds qui possèdent 2 enfants sont appelés, nœuds internes
et ceux qui en possèdent 0 nœuds, sont externes.
V.2.3. Représentation des arbres binaires en mémoire
On peut représenter un arbre binaire de 2 manières :
 La première et la plus courante s'appelle « représentation chaînée », elle est
analogue à la représentation en mémoire des listes chaînées.
 La seconde qui utilise un tableau unique est appelée « représentation
séquentielle de T ».
Dans n'importe quelle représentation d'un nœud, l'utilisation doit avoir un accès
direct à la racine R de T et un nœud N quelconque de T.
A. Représentation séquentielle d'un arbre
Soit T, un arbre complet. Il existe une manière très efficace de définir la
représentation séquentielle. Cette représentation utilise un tableau linéaire T en
mémoire :
a) La racine R de T est rangée en TREE [1]
b) Si un nœud occupe TREE *K+, son enfant de gauche est rangé en TREE *2*K+ et

Prof. BATUBENGA MWAMBA Nz. J.D. 75


Cours d’Algorithmique et Structures de données

son enfant de droite en TREE (2*K+1)


Si TREE (1) = Nul alors l'arbre est vide.

K /
K=1
45 1 45
2 22
K=2 K=3 3 77
22 77 4 11
K=4 K=5 K=6
5 30
11 30 90
6 90
9 15
15 88
25 10 25
12 88

On suit la manière dont les nœuds sont répartis.


Remarque :
T comprend 9 nœuds, il est nécessaire d'introduire 14 adresses dans le tableau TREE.
En fait, si on intègre les entités nulles correspondant aux nœuds terminaux, nous
aurions besoin de TREE [29] pour le successeur de droite TREE [14]. D'une manière
générale, la représentation séquentielle d'un arbre de profondeur nécessite un
tableau d'éléments dont le nombre est approximativement 2 d-1.
Il en résulte que la représentation de l'arbre T est généralement efficace.
B. Représentation chaînée
Un arbre binaire T sera rangé en mémoire selon une représentation chaînée qui
utilise 3 champs, INFO, LEFT, RIGHT et un pointeur ROOT de la manière suivante :
chaque nœud N de T correspondra à une adresse K tel que :
1. INFO *K+ contient l'information du nœud N ;
2. LEFT *K+ contient l'adresse de l'enfant de gauche du nœud N ;
3. RIGHT *K+ contient l'adresse de l'enfant de droite du nœud N ;
4. ROOT contient l'adresse de la racine R de T.
Si un sous arbre quelconque est vide, le pointeur correspondant contiendra la valeur
NULL. Si l'arbre lui-même est vide, la racine ROOT contiendra la valeur NULL.

Prof. BATUBENGA MWAMBA Nz. J.D. 76


Cours d’Algorithmique et Structures de données

B C

D E G H

F J K

Prof. BATUBENGA MWAMBA Nz. J.D. 77


Cours d’Algorithmique et Structures de données

Remarque :
Dans la pratique, il n'y aura qu'un élément d'information à chaque nœud N de l'arbre
T. Un enregistrement entier pourrait être rangé en un nœud N.
En d'autres termes, INFO peut être en réalité un tableau linéaire d'enregistrements
ou un ensemble des tables parallèles.
Puisque dans un arbre binaire les nœuds peuvent être supprimés ou insérés, nous
supposerons implicitement que les adresses vides dans le tableau INFO, LEFT, RIGHT
forment une liste chaînée avec un pointeur AVAIL.
Généralement, on range le pointeur de AVAIL dans le tab.LEFT ; Toute adresse
invalide peut être choisi comme adresse NULL.
V.3. Opérations sur les arbres binaires
V.3.1. Défilement des arbres binaires
Il existe trois manières de faire défiler un arbre T de racine R.
a) Pré-ordre, c'est-à-dire commencer par le nœud (N), le sous-arbre de gauche
(G) et enfin le sous arbre de droite (D), d'où NGD ou NLR.
1) Traiter la racine R.
2) Faire défiler le sous-arbre de gauche de R
3) Faire défiler le sous-arbre de droite de R
b) In-ordre, c'est-à-dire commencer par le sous-arbre de gauche (G), le nœud (N),
et enfin le sous arbre de droite (D), d'où GND ou LNR.
1) Faire défiler le sous-arbre de gauche de R
2) Traiter la racine R.
3) Faire défiler le sous-arbre de droite de R
c) Post-ordre, c'est-à-dire commencer par le sous arbre de gauche, le sous arbre
de droite, enfin le nœud N, d'où GDN ou LRN.
1) Faire défiler le sous-arbre de gauche de R
2) Faire défiler le sous-arbre de droite de R
3) Traiter la racine R.
Remarquons que dans tous les algorithmes précédents sont définis récursivement
dans la mesure où ils entraînent le défilement dans un ordre donné de sous arbres.
La différence entre les trois algorithmes résident dans le choix du moment où la
racine R est traitée.

Prof. BATUBENGA MWAMBA Nz. J.D. 78


Cours d’Algorithmique et Structures de données

Exemple
Considérons l'arbre binaire T ci-après :

On a la racine A, le sous-arbre de gauche LT et le sous-arbre de droite RT.


a. Le défilement pré-ordre de T traite d'abord A, puis défile LT et enfin défile
RT. Le défilement de LT pré-ordre traite d'abord B puis D et E. De même, le
défilement de RT pré-ordre traite C puis F. En définitive, le défilement de
l'arbre T donne le résultat ci-après A, B, D, E, C et F.
b. Le défilement in-ordre de T défile en premier lieu LT, traite A puis défile RT,
Le défilement in-ordre de LT traite d'abord les points D, B puis E. Celui de R T
traite F ensuite C. Finalement, le résultat sera donc D, B, E, A, F, C.
c. Le défilement post-ordre de T défile LT ensuite RT, et traite A. Dans la suite,
le défilement post-ordre de LT traite D, E, B et celui de RT traite F, C.
Finalement, le défilement post-ordre de T donne le résultat suivant D, E, B,
F, C, A.
V.3.2. Algorithmes de défilement
On examinera séparément les trois défilements au moyen de procédure non
récursive utilisant les notions en pile.
A. Défilement pré- ordre
Cet algorithme utilise une variable pointeur (PTR) qui contiendra l'adresse du nœud N
actuellement scruté.
Soit L(N), l'enfant de gauche du nœud N et R(N), l'enfant de droite du nœud N. On
utilisera également un tableau STACK où sont rangées les adresses des nœuds pour
des traitements ultérieurs.
Démarche à suivre
Empiler initialement NULL sur la pile STACK, puis initialiser PTR = ROOT, répéter
ensuite les étapes ci-après tant que PTR ≠ NULL.
(1) Descendre le chemin le plus à gauche dont la racine est en PTR en traitant chaque
nœud N rencontré et en empilant chaque enfant de droite R(N) s'il en existe sur
la pile STACK. Le défilement est terminé après qu'un nœud N sans enfant de
Prof. BATUBENGA MWAMBA Nz. J.D. 79
Cours d’Algorithmique et Structures de données

gauche L(N) ait été traité.


Dans ces conditions, on met PTR à jour en utilisant l'instruction PTR = LEFT
[PTR]. Le défilement s'arrête lorsque LEFT [PTR] = NULL.
(2) La recherche ascendante consiste à dépiler et affecter à PTR l'élément au
sommet de STACK. Si PTR ≠ NULL, retourner au point (1) sinon EXIT. L’élément
initial NULL sur STACK sert d’indicateur.
L'exemple suivant permettra de mieux comprendre les différentes étapes de
l'algorithme avant sa présentation.
Exemple : soit l'arbre binaire T.

(1) On empile NULL sur STACK : STACK = 0.


Ensuite PTR = A, racine de T
(2) Descendre le chemin le plus à gauche de la racine avec PTR = A
(i) Traiter A et empiler son enfant de droite C, on a :
STACK = 0, C
(ii) Traiter B
(iii) Traiter D et empiler son enfant de droite H sur STACK :
STACK = 0, C, H
(iv) Traiter G (Pas d’enfant de droite)
Aucun n’autre nœud n’est traité, car G n’a pas d’enfant de gauche ;
(3) [Recherche ascendante] dépiler l’élément au sommet de STACK, H et poser
PTR = H, on a STACK = 0, C
(4) Descendre le chemin le plus à gauche de la racine, H
(i) Traiter H et empiler son enfant de droite K sur STACK :
STACK = 0, C, K
Aucun n’autre nœud n’est traité, car H n’a pas d’enfant de gauche ;
(5) Recherche ascendante, dépiler l’élément K de STACK et poser PTR = K ⇒ on a
STACK = 0, C
Puisque PTR ≠ NULL, on retourne à l’étape (1) de l’algorithme.
(6) Descendre le chemin le plus à gauche de la racine avec PTR = K
(i) Traiter K, (aucun nœud n'est traité car K n'a pas d'enfant)

Prof. BATUBENGA MWAMBA Nz. J.D. 80


Cours d’Algorithmique et Structures de données

Aucun n’autre nœud n'est traité, car K n'a pas d'enfant de gauche
(7) Recherche ascendante, dépiler l’élément C de STACK, et poser PTR = C ⇒ on a
STACK = 0
Puisque PTR ≠ NULL, on retourne à l’étape (1) de l’algorithme.
(8) Descendre le chemin le plus à gauche de la racine avec PTR = C
(i) Traiter C et empiler son enfant de droite F sur STACK, on a STACK = 0, F
(ii) Traiter E (pas d’enfant de droite)
Aucun n’autre nœud n'est traité, car K n'a pas d'enfant de gauche
(9) Recherche ascendante, dépiler l’élément F de STACK, et poser PTR = C ⇒ on a
STACK = 0
Puisque PTR ≠ NULL, on retourne à l’étape (1) de l’algorithme.
(10) Descendre le chemin le plus à gauche de la racine avec PTR = F
(i) Traiter F (Pas d’enfant de droite)
Aucun n’autre nœud n'est traité, car F n'a pas d'enfant de gauche
(11) Recherche ascendante, dépiler l’élément NULL au sommet de STACK, et poser
PTR = NULL. Comme PTR = NULL, donc fin de l'algorithme.
Résultat : En regardant les points 2, 4, 6, 8 et 10, on a les résultats suivants A, B, D, G,
H, K, C, E, F.
Algorithme (de défilement suivant l'option pré-ordonné)
On part d'un tableau T rangé en mémoire. L'algorithme ci-après opère un défilement
pré-ordonné de T. On utilisera un tableau STACK pour ranger temporairement les
adresses de nœuds.
PRE (INFO, LEFT, RIGHT, ROOT)
1. Poser TOP = 1, STACK = NULL, PTR = ROOT
2. Répéter les étapes 3 à 5 tant que PTR ≠ NULL
3. Appliquer PROCESS à INFO [PTR]
(Process Operation qui consiste à traiter chaque élément du tableau)
4. Si RIGHT *PTR+ ≠ NULL, Empiler sur STACK
Poser TOP = TOP + 1, STACK [TOP] = RIGHT [PTR]
(Fin de si)
5. Si LEFT *PTR+ ≠ NULL, alors Poser PTR = LEFT *PTR+
Sinon, dépiler STACK
PTR = STACK [TOP] ; TOP = TOP - 1
(Fin de si)

Prof. BATUBENGA MWAMBA Nz. J.D. 81


Cours d’Algorithmique et Structures de données

6. EXIT
Remarque
L'opération PROCESS dans l'algorithme de défilement peut faire appel à certaines
variables qui doivent être initialisé avant qu'elle ne soit appliquée sur tout élément
du tableau.
B. Défilement in- ordre
On utilisera également un pointeur PTR qui contiendra (initialement) l'adresse du
nœud N. Ainsi, qu'un tableau STACK où seront rangées les adresses de nœud en vue
de leur traitement ultérieur.
Avec cet algorithme, un nœud n'est traité que lorsqu'il est dépilé de STACK.
Démarche à suivre :
 Empiler initialement NULL sur STACK comme indicateur puis initialiser PTR =
racine ROOT (c-à-d PTR = ROOT). Répéter ensuite les étapes suivantes jusqu'à
ce que NULL soit dépilé de STACK.
a) Descendre le chemin le plus à gauche ayant PTR comme racine en
empilant chaque nœud N sur STACK et en stoppant (chaque fois qu'un
nœud N sans enfant de gauche est empilé sur STACK)
b) Recherche ascendante Dépiler et traiter les nœuds empilés sur STACK, Si
NULL est dépilé, alors fin de l'algorithme.
Si un nœud N possédant un enfant de droite R(N) est traité, alors Poser
PTR = R(N). En affectant PTR = RIGHT [PTR] et retourner à l'étape a.
On retiendra ici qu'un nœud N n'est traité que quand il est dépilé de
STACK. L'exemple ci-après permettra de mieux comprendre l'algorithme
qui va suivre.

1. STACK = 0, PTR = A
2. Descendre le chemin le plus à gauche de A en empilant le nœud ABDGK (on
aura) STACK = 0, A, B, D, G, K.

Prof. BATUBENGA MWAMBA Nz. J.D. 82


Cours d’Algorithmique et Structures de données

3. Recherche ascendante ;
Les nœuds K, G et D sont dépilés et traiter, ce qui laisse STACK = 0, A, B.
Ensuite, arrêter les traitements à D car D est un enfant de gauche.
Puis initialiser PTR = H enfant de droite de D.
4. Descendre le chemin le plus à gauche de la racine PTR = H, en empilant les
nœuds H et L sur STACK. Alors STACK = 0, A, B, H, L ; (L n'a pas d'enfants de
gauche).
5. Les nœuds L et H sont dépilés et traités, ce qui laisse STACK, 0, A, B. Arrêter le
traitement à H, car H a un enfant de gauche L.
Puis initialiser PTR = M enfant de droite de H.
6. Descendre le chemin le plus à gauche M, en empilant les nœuds M : STACK = 0,
A, B, M. Aucun autre nœud n'est empiler, car M n'a pas d'enfants de gauche.
7. *Recherche ascendante+, Les nœuds M, B et A sont dépilés et traités, ce qui
laisse STACK = 0.
Aucun entre élément de STACK n'est dépiler, car A possède un enfant de
gauche.
Initialiser PTR = C enfant de droite de A.
8. Descendre le chemin le plus à gauche de la racine C, en empilant les nœuds C
et F sur STACK. Alors STACK = 0, C, F.
9. *Recherche ascendante+. Le nœud E est dépilé et traité. Comme E n'a pas
d'enfants de droite, C est dépilé et traité et comme C n'a pas d'enfants de
droite, (l'élément suivant), NULL est dépilé. D'où, la fin de l'algorithme.
Résultat du défilement d'après les étapes 3, 5, 7, 0 les nœuds sont traités dans l'ordre
ci-après K, G, D, L, H, M, B, A, E, C.
Algorithme
ln (lNFO, LEFT, RIGHT, ROOT)
1. Poser TOP = 1, STACK [1] = NULL, PTR = ROOT
2. Répéter tant que PTR ≠ NULL
a) Poser TOP = TOP + 1 et STACK [TOP] = PTR
b) Poser PTR = LEFT [PTR]
(Fin de la bouche)
3. Poser PTR = STACK (TOP) et TOP = TOP - 1
4. Répéter les étapes 5 à 7 tant que PTR ≠ NULL
5. Appliquer PROCESS à INFO [PTR]

Prof. BATUBENGA MWAMBA Nz. J.D. 83


Cours d’Algorithmique et Structures de données

6. (Traitement de l'enfant de droite) si RIGHT *PTR+ ≠ NULL alors


a) Poser PTR = RIGHT [PTR]
b) GO TO étape 2
(Fin de Si)
7. Poser PTR = STACK [TOP] et Top = Top - 1
(Fin de la boucle 4)
8. Fin
c) Défilement post-ordonné
Dans ce cas, il est possible que nous ayons à sauvegarder un nœud N dans deux
situations différentes. On distingue entre les deux cas en empilant N ou son opposé -
N sur STACK.
Démarche à suivre
Empiler NULL sur STACK, PTR = ROOT.
Répéter ensuite les étapes ci-après jusqu'à ce que NULL soit dépouillé de STACK.
a) Descendre nœud N du sommet empilé N sur STACK et si N possède un enfant
de droite R(N) empiler - R(N) sur STACK et si N possède un enfant de
b) *Rechercher ascendante+ dépiler et traiter le nœud positif de STACK
Si NULL est dépilé alors fin de l'algorithme.
Si un nœud négatif est dépilé, c'est-à-dire PTR = - N, initialiser PTR = N en
affectant PTR = - PTR et retourner l'étape a.
N.B : Notons qu'un nœud N n'est traité (plutôt) que lorsqu'il est défilé de STACK et
qu'il est positif.
L'exemple ci-après ça nous aide à comprendre l'algorithme dans cette démarche.
Exemple : considérons l'arbre T ci-après :

1. PTR = A, STACK = 0, c'est-à-dire


Empiler NULL sur STACK
2. Descendre le chemin le plus à gauche de la racine PTR = A. En empilant les

Prof. BATUBENGA MWAMBA Nz. J.D. 84


Cours d’Algorithmique et Structures de données

nœuds B, D, G et K sur STACK.


Comme A possède un enfant de droite C, empilé - C sur STACK après A mais
avant B. De même comme D a un enfant de droite H, empiler - H après D mais
avant G ; d'où, en ce moment là STACK = 0, A, - C, B, D, - H, G, K.
3. Dépiler et traiter K, dépiler et traiter G, comme - H est une valeur négative,
dépiler seulement - H, ce qui laisse STACK = 0, A, - C, B, D.
A cette étape, on a PTR = - H. On réinitialise en posant PTR = H.
4. Descendre le chemin le plus à gauche de la racine PTR = H. Empiler H sur
STACK. Comme H a un enfant de droite M, empiler - M après H. empiler
ensuite L (un enfant de gauche). Donc STACK = 0, A, - C, B, D, H, - M, L.
5. Dépiler et traiter L, dépiler - M, ce qui donne STACK = 0 A, - C, B, H.
On a PTR = - M. Poser PTR = M.
6. Descendre le chemin le plus à gauche de la racine PTR = M. seul M est empilé.
Ce qui donne STACK = 0, A, - C, B, D, H, M.
7. Dépiler et traiter M, H, D, B. Dépiler - C ce qui donne STACK = 0, A, A ce niveau
PTR = - C, réinitialiser PTR = C et
8. Descendre le chemin le plus à gauche de PTR = C. C est empilé et écrire STACK
= 0, A, C, E.
9. Dépiler et traiter E, C et A.
Lorsque O est dépilé, le tableau STACK est vide et l'algorithme est terminé.
Résultat : K, G, L, M, H, D, B, E, C, A
Algorithme
Post (INFO, LEFT, RIGHT, ROOT)
Soit un arbre T range en mémoire. L'algorithme suivant effectue un défilement
POST ORDONNE de l'arbre T.
1. Poser TOP = 1, STACK [1] = NULL, PTR = ROOT
2. Répéter les étapes 3 à 5 tant que PTR ≠ NULL
3. TOP = TOP + 1 et STACK POP) = PTR
4. Si (l'enfant de droite) RIGHT (PTR) ≠ NULL, alors Poser TOP = TOP + 1 et
STACK [TOP] = - RIGHT [PTR]
[Fin de Si]
5. Poser PTR = LEFT [PTR]
[Fin de la boucle 2]

Prof. BATUBENGA MWAMBA Nz. J.D. 85


Cours d’Algorithmique et Structures de données

6. Poser PTR = STACK [TOP] avec TOP = TOP - 1


7. Répéter tant que PTR > 0
a) Appliquer PROCESS à INFO [PTR]
b) Poser PTR = STACK [TOP] et TOP = TOP - 1
(Fin de la boucle)
8. Si PTR < 0
a) PTR = - PTR
b) GO TO à l'étage 2
(Fin de Si)
9. SORTIE
Remarque :
Nous venons de voir les différents types de parcourts d'un arbre binaire, Ceci permet
de traiter les différents nœuds constituant l'arbre. Le défilement permet le
traitement des informations contenues dans un nœud.
V.3.3. Arbre de recherche binaire (Arbre binaire ordonné)
L'arbre de recherche binaire est une des structures de données les plus importantes
en informatique. Elle permet de rechercher et de trouver un élément avec un temps
d'exécution moyen de f(n) = 0 ( ), fonction de complexité meilleure possible.

Elle permet également d'insérer et d'extraire facilement des éléments.


A. Définition
Soit T un arbre binaire. T est appelé arbre de recherche binaire ou arbre binaire
ordonné si chacun de ses nœuds N possède la propriété suivante : la valeur en N est
supérieur à toute valeur contenue dans le sous arbre de gauche de N et inférieur à
toute valeur contenue dans le sous arbre de droite de N.
Par définition, tout défilement in - ordonné de T donnera une liste ordonnée des
éléments de T.
B. Exemple
38

14 56

8 23
45 82
18 70

Prof. BATUBENGA MWAMBA Nz. J.D. 86


Cours d’Algorithmique et Structures de données

V.3.4. Recherche et insertion dans les arbres de recherche binaire


A. Principe
Soit un ITEM d'information donnée. On se propose de trouver la position de ITEM sur
l'arbre de recherche binaire T ou insérer ITEM sous forme d'un nouveau nœud à la
place appropriée dans l'arbre T.
Il y a deux étapes à suivre :
a) Comparer ITEM au nœud N de l'arbre T ;
i) Si ITEM < N, aller à l'enfant de gauche de N
ii) Si ITEM > N, aller à l'enfant de droite de N
b) Répéter l'étape (a) jusqu'à occurrence de l'un des événements :
i) Rencontre d'un nœud tel que ITEM = N, cas de succès.
ii) Rencontre d'un sous arbre vide, c'est-à-dire la recherche est un échec et
ITEM est inséré à la place du sous arbre vide.
L'opération consiste à partir de la racine R et à descendre l'arbre T jusqu'à
trouver ITEM ou à l’insérer comme nœud terminal de T.
B. Exemple
Soit un arbre de recherche binaire T représenté en figure ci-dessus. On se propose
d'insérer ITEM = 20 dans l'arbre.
1. Comparer ITEM = 20 à la racine 38 de l'arbre T. Puisque 20 < 38 aller à l'enfant
de gauche de 38 soit 14.
2. Comparer ITEM = 20 à 14, comme 20 > 14, passer à l'enfant de droite de 14,
soit 23.
3. Comparer ITEM = 20 à 23, puisque 20 < 23, passer à l'enfant de gauche de 23,
soit 18.
4. Comparer ITEM = 20 à 18, puisque 20 > 18 et que 18 n'a pas d'enfant de droite,
insérer 20 comme enfant de droite de 18.
38

14 56

8 23
45 82
18 70

20

L'algorithme de recherche et d'insertion s'appuiera sur la procédure suivante qui


détermine les adresses d'un ITEM donné et de son parent.

Prof. BATUBENGA MWAMBA Nz. J.D. 87


Cours d’Algorithmique et Structures de données

La procédure ci-après (FIND) détermine l'adresse L du nœud ITEM dans l'arbre T ainsi
que l'adresse PAR u parent de ITEM.
Il convient de faire remarquer que si :
1. L = NULL et PAR = NULL, c'est-à-dire l'arbre est vide.
2. L ≠ NULL et PAR = NULL, ITEM est la racine de l'arbre.
3. L = NULL et PAR ≠ NULL, ITEM n'est pas dans l'arbre et il peut être inséré à
l'arbre T en tant qu'un enfant du nœud N d'adresse PAR.
L'algo de recherche et d'insertion s'appuiera sur la procédure suivante qui détermine
les adresses d'un ITEM et de son parent.
FIND [INFO, LEFT, RIGHT, ROOT, ITEM, PAR, L]
1. [Arbre vide ?]
Si ROOT = NULL, alors poser L = NULL et PAR = NUL et Return
2. [ITEM en racine ?]
Si ITEM = INFO (ROOT), alors poser L = ROOT et PAR = NUL ; Return
3. [Initialiser les pointeurs PTR et SAVE]
Si ITEM < INFO (ROOT) alors
Poser PTR = LEFT (ROOT) et SAVE = ROOT
Sinon
Poser PTR = RIGHT [ROOT] et SAVE = ROOT
[Fin de si]
4. Répéter les étapes 5 et 6 tant que PTR ≠ NUL
5. [ITEM trouvé ?]
Si ITEM = INFO (PTR), alors L = PTR et PAR = SAVE ; Return
6. Si ITEM < INFO (PTR), alors
Poser SAVE = PTR et PTR = LEFT (PTR)
Sinon
Poser SAVE = PTR et PTR = RIGHT [PTR]
[Fin de Si]
[Fin de la boucle 4]
7. [Echec] Poser L = NULL et PAR = SAVE
8. EXIT.

Prof. BATUBENGA MWAMBA Nz. J.D. 88


Cours d’Algorithmique et Structures de données

C. Algorithme de recherche et d'insertion


Nous l'appellerons RIS [INFO, LEFT, RIGHT, ROOT, AVAIL, ITEM, L].
Considérons un arbre de recherche binaire T en mémoire et un ITEM d’information.
Cet algorithme détermine l’adresse L de l’ITEM dans T ou insère ITEM en tant que
nœud nouveau dans T à l'adresse L.
1. Call FIND [INFO, LEFT, RIGHT, ROOT, ITEM, L, PAR]
2. Si L ≠ NULL, alors Exit
3. a) Si AVAIL = NULL, alors Write OVERFLOW et Exit
b) Poser NEW = AVAIL, LEFT [NEW] = NULL et RIGHT [NEW] = NULL
c) Poser L = NEW, INFO [NEW] = ITEM
4. *Ajouter ITEM à l’arbre+
Si PAR = NULL, Poser ROOT = NEW
Sinon si ITEM < INFO [PAR]
Poser LEFT (PAR) = NEW
Sinon Poser RIGHT [PAR] = NEW
5. Exit [Fin de si]
Il convient de préciser qu’en étape 4, on distingue :
1. L’arbre est vide
2. ITEM est inséré en tant qu’enfant de gauche
3. ITEM est inséré en tant qu’enfant de droite.
V.3.5. Suppression dans un arbre de recherche binaire
Soit T un arbre de recherche binaire et ITEM, un élément d'information donnée. On
se propose d'étudier ici comment supprimer ITEM dans T.
On appliquera d'abord la procédure FIND pour déterminer l'adresse du nœud N qui
contient ITEM, ainsi que l'adresse du nœud parent.
La suppression du nœud dépend du nombre de ses enfants. On peut donc distinguer
les trois cas ci-après :
1. N n'a pas d'enfants, on le supprime de T en remplaçant l'adresse de N dans le
nœud parent P(N) par le pointeur NULL.
2. N a un seul enfant, on le supprime de T en remplaçant son adresse dans P(R)
par celle de son enfant unique.
3. N a deux enfants. Soit S(N) le successeur in-ordonné de N, S(N) n'a pas
d'enfants de gauche. On supprime le nœud N en commençant par supprimer

Prof. BATUBENGA MWAMBA Nz. J.D. 89


Cours d’Algorithmique et Structures de données

S(N) et en remplaçant N par S(N).


Dans tous les cas, l'espace mémoire relatif au nœud N supprimé est renvoyé à
la liste AVAIL.
A. Exemple

Représentation linéaire
Supposons que nous supprimons le nœud 44 de l'arbre T. Ce nœud n'a pas d'enfant.
La suppression est effectuée en affectant simplement NULL au fils de droite de 44.

Représentation linéaire après suppressions du nœud 44.

Prof. BATUBENGA MWAMBA Nz. J.D. 90


Cours d’Algorithmique et Structures de données

2. Supposons que nous supprimons le nœud 75 qui ne possède qu'un enfant. La


suppression est affectée en motivant le pointeur de droite du nœud parent 60 de
manière à ce qu'après suppression, il pointe sur le nœud 66, enfant unique du nœud
75.

3. Supposons maintenant que nous supprimons le nœud 25 qui a deux enfants (15 et
50). On sait que le nœud 33 est le successeur IN-ordonné du nœud 25. La suppression
est affectée en enlevant tout d'abord le nœud 33 puis en le substituant au nœud 25.
En mémoire de l'ordinateur, ce remplacement est effectué en modifiant tout
simplement le pointeur, pas en déplaçant le contenu d'un nœud d'une adresse à une
autre.
Le nœud 75 reste toujours la valeur de INFO *7+. En ce moment, l'arbre devient :

Prof. BATUBENGA MWAMBA Nz. J.D. 91


Cours d’Algorithmique et Structures de données

Remarque : Avant de présenter l'algorithme de suppression, on a besoin des deux


procédures ou deux sous algorithmes. La première relative au cas 1 et 2. La seconde
au cas 3 ou le nœud à supprimer possède 2 enfants.
Première procédure
En résumé, cette procédure supprime le nœud N à l'adresse LOC quand N ne possède
pas 2 enfants. Le pointeur noté PAR donne l'adresse du parent de N.
Si PAR = NULL, le nœud N n'a pas de racine. Alors le pointeur CHILD donne l'adresse
de l'enfant unique de N si CHILD = 0, le nœud N n'a pas d'enfants.
SUP 1 [INFO, LEFT, RIGHT, ROOT, LOC, PAR]
1. Si LEFT [LOC] = NULL et RIGHT [LOC] = NULL alors
Poser CHILD = NULL
Sinon si LEFT *LOC+ ≠ NULL, alors
Poser CHILD = LEFT [LOC]
Sinon
Poser CHILD = RIGHT [LOC]
[Fin de si]
2. Si PAR ≠ NULL alors
Si LOC = LEFT [PAR] alors
Poser LEFT (PAR) = CHILD
Sinon
Poser RIGHT [PAR] = CHILD
Sinon

Prof. BATUBENGA MWAMBA Nz. J.D. 92


Cours d’Algorithmique et Structures de données

Poser ROOT = CHILD


[Fin de si]
3. Return
Deuxième procédure
Elle supprime le nœud N à l'adresse LOC quand N possède deux enfants. Il s'agit
d'une procédure beaucoup plus générale.
Si PAR = NULL, alors N n'a pas de racine
Le pointeur SUC donne l'adresse du successeur IN - ORDONNE N et le pointeur PAR
SUC l'adresse de son parent.
SUP 2 [INFO, LEFT, RIGHT, ROOT, ITEM, LOC, PAR]
1. [Trouver SUC et PARSUC]
(a) Poser PTR = RIGHT (LOC) et SAVE = LOC
(b) Répéter tant que LEFT (PTR) ≠ NULL
Poser SAVE = PTR et PTR = LEFT [PTR]
[fin de la boucle]
(c) Poser SUC = PTR et PAR SVC = SAVE
2. [Supprimer le successeur in-ordonné en appliquant la procédure SUP1]
CALL SUP 1 (INFO, LEFT, RIGHT, foot, SUC, PARSUC)
3. *Remplacer le nœud N par son successeur In-ordonné]
(a) Si PAR ≠ NULL alors
Si LOC = LEFT [PAR] alors
Poser LEFT [PAR] = SUC
Sinon
Poser RIGHT [PAR] = SUC
[fin de si]
Sinon
Poser ROOT = SUC
[fin de si]
(b) Poser LEFT (SUC) = LEFT [LOC] et RIGHT [SUC] = RIGHT [LOC]
4. Return

Prof. BATUBENGA MWAMBA Nz. J.D. 93


Cours d’Algorithmique et Structures de données

B. Algorithme de suppression
Soit un arbre binaire T en mémoire et un ITEM d'information donnée. Cet algorithme
supprime ITEM dans l'arbre T.
1. (Trouver les adresses de ITEM et de son parent, c'est-à-dire en la faire)
CALL FIND [INFO, LEFT, RIGHT, ROOT, ITEM, LOC, PAR]
2. [Est-ce qu'item est dans cet arbre là ?] Si LOC = NUL alors
Ecrire « ITEM n'est pas sur l'arbre » et sortir
3. Si RIGHT *LOC+ ≠ NULL et LEFT *LOC+ ≠ NULL
Alors CALL SUP 2 [INFO, LEFT, RIGHT, ROOT, LOC, PAR]
Sinon CALL SUP 1 [INFO, LEFT, RIGHT, ROOT, LOC, PAR]
[fin de si]
4. (respecter le nœud supprimé à la liste AVAIL. On le fait en :)
Poser LEFT [LOC] = AVAIL et AVAIL = LOC
5. Sortir
V.4. Les arbres généralisés
V.4.1. Généralités
Un arbre généralisé en défini comme un ensemble non vide T des éléments appelés
nœuds tels que T comporte un élément particulier R appelé racine de T. Les autres
éléments de T forment un ensemble ordonné de 0 ou plusieurs arbres disjoints T1, T2,
T3, …, Tn. Dans ces conditions, on dit que T1, T2, T3, ... , Tn sont des sous- arbres de la
racine R, et les racines de T1, T2, ... , Tn sont appelés successeurs de R.
Si N est un nœud qui a pour successeurs S1, S2, S3, ..., Sn. N est dit parent de Si et les Si
sont appelés enfants de N. Les Si sont dits frères les uns des autres.
Les enfants d'un même nœud sont ordonnés de gauche à droite. Un arbre généralisé
peut être vu généralement comme un graphe connexe sans cycle. Il est orienté et
réuni sur un ensemble fini X, tel que.
1. Il y a dans X un nœud distingué appelé « racine » de l'arbre que nous notons
habituellement N.
2. Les autres nœuds, s'il y en a, sont partitionnés en N sous-ensembles disjoints
T1, T2, T3, ..., Tn qui sont alors tous des arbres des racines respectives T 1, T2, T3,
... , Tn ; Si l'on désire les noter ainsi, tel que , il existe un arc
d'origine N et d'extrémité Ti. Ces arbres sont appelés les sous-ensembles de X.
Remarque
1. Une feuille est un nœud qui n'a pas de fils. On l'appelle aussi un nœud
terminal. C'est un sommet de degré 1. Un nœud intérieur appelé encore un
Prof. BATUBENGA MWAMBA Nz. J.D. 94
Cours d’Algorithmique et Structures de données

nœud propre est un nœud qui a au moins un fils.


2. Supposons que T1, T2, T3, ..., TR soit une séquence de nœuds d'un arbre tel que
T1 est le père de T2 qui est à son tour le père de T3, et ainsi de suite, jusqu'à Tk-1
qui est le père de Tk. Alors la suite T1, T2, T3, ..., TR est appelé chemin depuis T1
jusqu'à TR dans l'arbre. Dans ces conditions T1, T2, T3, …, Ti-1 sont les ancêtres
de Ti, et Ti+1, Ti+2, Ti+3 ... Tk sont les descendants de Ti.
3. On appelle longueur d'un chemin, le nombre de nœuds dans le chemin moins
un.
4. La hauteur d'un nœud T est la longueur du plus grand chemin depuis T jusqu'à
une feuille. La hauteur d'un arbre est la hauteur de sa racine.
5. La profondeur ou le niveau d'un nœud est la longueur du chemin depuis la
racine jusqu'à T.
Exemple

Cet exemple nous montre un arbre généralisé, car la racine A possède 3 enfants A, B,
C, D ; et C possède 3 enfants G, H et J.
Il est possible de ramener cet arbre sous forme d'un arbre binaire afin de pouvoir
étudier les différentes opérations sur cette structure de données.
V.4.2. Binarisation d'un arbre généralisé
Soit T un arbre généralisé, dans le souci de faciliter les traitements de données dans
cette structure, nous pouvons lui associer un arbre binaire unique T'.
Nous pouvons lui associer un arbre binaire unique de la manière suivante :
1. Les nœuds de l'arbre binaire T' seront les mêmes que ceux de l'arbre T ;
2. La racine de T' sera la même racine de T ;
3. Si N est un nœud quelconque de l'arbre binaire T', l'enfant de gauche de N
dans T' sera le premier enfant du nœud N dans l'arbre généralisé T. Et l'enfant
de droite de N dans T' sera le frère suivant de N dans l'arbre généralisé T.

Prof. BATUBENGA MWAMBA Nz. J.D. 95


Cours d’Algorithmique et Structures de données

V.4.3. Exercice
Soit l'arbre généralisé T ensemble ci-après. D déterminer l'arbre binaire associé T'

Solution

V.4.4. Remarques
a. L'importance de cette banalisation réside du fait que certains algorithmes
applicables aux arbres binaires peuvent maintenant être appliqués aux arbres
généralisés, Si n est une constante entière, un arbre est dit n- aire quand le
nombre maximum de fils d'un même nœud est n.
Un arbre binaire est un arbre dont chaque nœud au plus 2 fils.
b. Un arbre binaire n'est pas un cas particulier d'un arbre généralisé. Les arbres
binaires et les arbres généralisés sont physiquement des objets différents. Voici les
deux différences fondamentales qui les séparent.
(1) Un arbre binaire T' peut être vide, mais un arbre généralisé T est non vide.

Prof. BATUBENGA MWAMBA Nz. J.D. 96


Cours d’Algorithmique et Structures de données

(2) Soit un nœud N qui ne possède qu'un enfant. Dans un arbre binaire cet enfant
est repéré soit comme enfant de gauche, soit comme un enfant de droite ;
dans un arbre généralisé, les distinctions de cet arbre n'existent pas. Cette
différence peut être illustrée par les arbres T1 et T2 ci-après :

On peut considérer que les arbres T1 et T2 sont différents puisque B est l'enfant
gauche de A dans T1 et l'enfant droite de A dans T2 mais les arbres généralisés T1 et
T2 sont identiques (tandis que les arbres binaires T1 et T2 ne sont pas identiques).
V.4.5. Définition
On appelle forêt F, un ensemble ordonnée des zéros ou plus d'arbres distincts. Si on
supprime la racine ROOT dans un arbre généralisé « T », on obtient la forêt F
constituée par l'ensemble de sous- arbres du ROOT. Réciproquement, si F est une
forêt, nous pouvons lui adjoindre un nœud ROOT de manière à former un arbre
généralisé T dont ROOT est la racine où les sous- arbres de ROOT sont les arbres
originaires de F.
V.4.6. Représentation en mémoire
Soit T un arbre généralisé, il sera représenté en mémoire sous forme d'une liste
chaînée à trois tableaux parallèles : INFO, CHILD, SIBL, plus de variables pointeurs
ROOT et AVAIL (pour tes nœuds qui sont vides). Chaque nœud N de T correspond à
une adresse K telle que :
(1) INFO *K+ contient l'information du nœud N ;
(2) CHILD [K] contient la position du premier enfant de N (position enfant de
gauche). La condition CHILD (KI = û, indique que le nœud N n'a pas d'enfants,
(3) SIBL IK) contient l'adresse du prochain frère de N.
La condition SIBL (K) = 0, indique que le N est le dernier enfant de son parent.
La racine ROOT contient l'adresse R de T. On note que chaque nœud N de
l'arbre T quel que soit le nombre de ces enfants contiendra exactement 3
champs, On peut ainsi représenter une forêt F d'arbre T1, T2, T3, .., Tn en
mémoire de l'ordinateur. Il faut supposer que tous les nœuds racines sont
frères (c'est-à-dire si A1, A2, A3 … : nœuds racines).
Prof. BATUBENGA MWAMBA Nz. J.D. 97
Cours d’Algorithmique et Structures de données

Il convient de préciser que la représentation en machine de l'arbre généralisé et la


représentation chaînée de l'arbre binaire sont exactement les mêmes.
Les noms de tableau CHILD et SIBL de l'arbre généralisé T correspondant
respectivement au nom des tableaux LEFT et RIGHT relativement à l'arbre binaire T'.

Prof. BATUBENGA MWAMBA Nz. J.D. 98

Vous aimerez peut-être aussi