Cours Algorithmique Niveau 2 PDF
Cours Algorithmique Niveau 2 PDF
Cours Algorithmique Niveau 2 PDF
Matière :
Algorithmiques & structures de données
Mots-clés du Support
Algorithme, les Fonctions, les Procédures, la Récursivité, les Enregistrements, les
Pointeurs, Structure de données, Structures de données linéaires, les Fichiers, les Listes, les
Piles, les Files, Structures de données ramifiées, les Arbres, les Graphes, Parcours en
largeur, Parcours en Profondeur, Algorithmes de Recherche, le Tri, les Algorithmes de tri,
la complexité, etc.
ème
Année universitaire : 2 Semestre 2008-2009
Fiche de Présentation
ALGORITHMIQUE 2 ET STRUCTURES DE DONNEES
Objectifs du cours
Fournir aux étudiants des bases rigoureuses en algorithmique, en insistant sur l'aspect
scientifique de la discipline.
Initier les concepts de récursivité, de Pointeur, de la gestion dynamique de la mémoire, de la
complexité d’un algorithme, etc.
Présenter les structures de données typiques, à savoir les listes, les piles, les files, les arbres et
les graphes, ainsi que les algorithmes de traitement qui leur sont associés.
Exploiter les caractéristiques de ces structures pour appliquer des algorithmes de recherche
récursifs.
Formuler les algorithmes de quelques types de tri à savoir le tri par tas, le tri par insertion…
Comprendre la notion de complexité et apprendre la méthode d’estimation de la complexité
des algorithmes.
Stimuler la créativité des étudiants en les incitant à exploiter les solutions vues au cours pour
en élaborer de nouvelles.
Cours 1 : Récursivité 3
ISET Béja Cours Algorithmique 2
Evaluation
Contrôle continu : il est compté comme 38% de la note finale du candidat et regroupe les notes
suivantes :
La note de mini projet : chaque candidat est appelé à participer avec un autre binôme
dans un mini projet. Chaque deux binômes sont appelés à présenter leur travail à la fin
du semestre.
Les notes de tests : un nombre variable de tests au cours des séances de cours.
Une note pour l’assiduité des candidats
La note du devoir surveillé : un examen partielle possible de le faire à partir de la
cinquième semaine de cours.
Examen Final : il s’agit d’un examen de synthèse qui aura lieu à la fin du semestre. Cet
examen est compté comme 62% de la note finale du candidat.
Moyens Pédagogiques
Support de cours papier.
Support de cours numérique.
Série de travaux dirigés : à la fin de chaque chapitre on doit avoir une série d’exercices.
Sujets d’examens antérieurs.
Cours 1 : Récursivité 4
ISET Béja Cours Algorithmique 2
La complexité des
9 S14 Ali El kamel
algorithmes
Le programme est planifié sur 14 semaines seulement. Nous avons décidé de laisser une dernière
semaine pour remédier les problèmes de décalage. Si tout va bien, cette semaine sera réservée pour une
révision à l’examen final.
Cours 1 : Récursivité 5
ISET Béja Cours Algorithmique 2
[4] Introduction au langage C norme iso / ansi , Bernard Cassagne, 2.1 de juin 1998
[6] Initiation au Langage C Niveau 1, M . Berthomier Eric, Version 1.1 du 13 Mars 2000
[9] Support de cours : Programmation C – II, Niveau 2 Par BEN ROMDHAN Mourad
[11] The C programming language , Second Edition Dennis Ritchie & Brian W. Kernighan,
Edition PRENTICE HALL Informatique, 1988.
[12] Le langage C, Dominique GALLAND, Édition DUNOD Informatique, 1989.
Cours 1 : Récursivité 6
ISET Béja Cours Algorithmique 2
Cours 1 : Récursivité 7
ISET Béja Cours Algorithmique 2
Chapitre 1 La récursivité
Remarque :
Exemple :
Cours 1 : Récursivité 8
ISET Béja Cours Algorithmique 2
2.5 Exemple
A, B: 2 paramètres effectifs
x, y: 2 paramètre formels
A, B : variables globales
m: variable locale
Cours 1 : Récursivité 10
ISET Béja Cours Algorithmique 2
Un paramètre passé par variable ne peut être qu’une variable il ne peut pas être une constante ou une
expression.
3.3 Exemple
4- Les fonctions
Nous présentons dans ce paragraphe le concept des fonctions. Pour ce la, nous donnons la définition et la
syntaxe d’une fonction et nous terminons par un exemple illustratif de l’utilisation des fonctions.
4.1 Définition
La fonction possède la même définition que la procédure mais avec seulement 2 différences. D’abord
une fonction possède une valeur de retour. Ensuite la fonction est utilisée dans l’algorithme principal
comme une variable contrairement à la procédure qui est utilisé comme une instruction.
Cours 1 : Récursivité 11
ISET Béja Cours Algorithmique 2
4.2 Syntaxe
Remarque :
Le type de la fonction doit être le même que le type de la variable vf utilisé dans l’algorithme principal pour
recevoir la valeur de retour de la fonction.
4.3 Exemple
5- Algorithmes récursifs
5.1 Définition récursive
Une définition récursive est définie en fonction d'un objet ou d'une action de même nature mais de
complexité moindre.
Par exemple, on peut définir le factoriel d'un nombre « n » positif de deux manières:
Cours 1 : Récursivité 12
ISET Béja Cours Algorithmique 2
définition récursive:
On remarque qu’une définition récursive est composée de deux parties: une partie strictement
récursive et une partie non récursive (base) servant de point de départ à l'utilisation de la définition
récursive.
5.2.1 Définition
Comme pour les définitions récursives, les fonctions récursives sont des fonctions qui sont appelées
depuis leur propre corps de fonction, soit directement soit indirectement, à travers une ou plusieurs
fonctions relais. Si la fonction P appelle directement P, on dit que la récursivité est directe. Si P appelle
une fonction P1, qui appelle une fonction P2 et qui enfin appelle P, on dit qu'il s'agit d'une récursivité
indirecte.
Si la fonction P appelle dans son propre corps la fonction P, il est logique de penser que l’exécution ne
s'arrêtera jamais. Il est donc primordial qu'une des branches de la fonction P permette de stopper la
récursivité.
5.2.2 Exemple
On veut calculer la somme des n premiers entiers positifs. Une définition récursive de cette somme
serait:
Cours 1 : Récursivité 13
ISET Béja Cours Algorithmique 2
L'appel somme (3) suivra le même processus et appellera somme (2) qui lui-même appellera à somme (1) qui
retourne 1.
Cours 1 : Récursivité 14
ISET Béja Cours Algorithmique 2
Dans notre exemple somme (4), la variable locale m est créée 4 fois. Ces variables sont détruites au fur et à
mesure que l'on quitte la fonction comme toute variable locale d'une fonction.
Cours 1 : Récursivité 15
ISET Béja Cours Algorithmique 2
Cours 1 : Récursivité 16
ISET Béja Cours Algorithmique 2
Les types de base se divisent en 2 catégories : les types simples et les types complexes. Ils sont tous des types
prédéfinis, c'est-à-dire des types définis par le langage de programmation.
Entier : ce type représente toute variable qui peut prendre des valeurs entières positives ou négatives. Pour
utiliser ce type, on utilise le mot clé « entier ».
Reel : ce type représente toute variable qui peut prendre des valeurs réelles. Pour utiliser ce type, on utilise le
mot clé « reel »
Caractere : ce type représente toute variable qui peut prendre tout caractère qu’on peut saisir avec un clavier
standard. Pour utiliser ce type, on utilise le mot clé « caractere »
Booleen : ce type représente toute variable à deux valeurs VRAI ou FAUX. Pour utiliser ce type, on utilise le
mot clé « booleen »
Tableau :
o Une variable de type Tableau est formée par l’union de plusieurs cases mémoires. Chacune des cases
contient le même type.
o la déclaration d’un tableau se fait comme suit : « nom : Tableau [i_deb .. i_fin] de type ».
o i_deb et i_fin représentent l’indice de début et l’indice de fin du tableau.
Chaîne de caractères :
o C’est la concaténation de plusieurs caractères qui finie par le marqueur de fin de la chaîne. C’est ainsi un
tableau de caractères qui contient comme dernier caractère un caractère spécial pour représenter le
marqueur de fin de la chaîne.
o La déclaration d’une chaîne de caractères se fait comme suit : « nom : chaine [ Max ] »
o Max est le nombre maximum de caractères dans la chaîne.
Par exemple si on veut représenter les données relatives à une personne telle que le nom et l’âge dans une seule
variable ou une seule structure de donnée, on ne peut pas faire ça avec les types de bases et particulièrement
avec les tableaux. On peut utiliser un nouveau type appelé enregistrement qui contient 2 champs, un pour
représenter le nom de type chaîne de caractère et le 2ème pour représenter l’age de type entier. On remarque
qu’on ne peut pas représenter la personne par un tableau car elle est formée de 2 types différents.
2.2 Définition
L’enregistrement est une structure de données (ou un type) composée, qui est formé par plusieurs autres
structures de données de nature différentes. Il permet ainsi de les regrouper dans une seule structure de
données.
2.3 Syntaxe
Avec :
2.4 Représentation
Les enregistrements sont représentés en mémoire sous forme de suite de zones contiguës qui servent chacune à
représenter les différents champs, ces zones sont de taille différentes puisque chaque champ a un type différent.
Par exemple l’enregistrement personne sera représenter en mémoire comme suit :
3.1 Déclaration
La déclaration d’un enregistrement se fait comme étant un nouveau type définit par l’utilisateur dans
l’algorithme, il doit être déclaré dans la partie Type avant d’être utilisé comme type de variable dans la partie Var.
3.2 Accès
L’enregistrement qui est une structure de données composée de plusieurs champs n’est pas accessible dans sa
totalité, donc pour faire un accès à un enregistrement on accède à tous les champs un par un. Et pour utiliser un
champ, on écrit le nom de la variable de type enregistrement suivi de « point » suivi du champ auquel on veut
accéder : Nom_enregistrement.champ_desire
3.3 Exemple
On veut écrire un algorithme qui lit le nom, le prénom et la moyenne générale d’un étudiant puis il affiche si cet
étudiant a réussi ou non selon que sa moyenne est >= 10 ou non. Utiliser un enregistrement pour représenter
l’étudiant.
4.2 Exemple
Si on veut représenter un Compte Bancaire par un type abstrait il sera composé des données et des
opérations concernant les Comptes Bancaires :
Les données seront :
Les noms des types utilisés pour représenter les données (réels, entiers, …).
La signature décrit la syntaxe du type abstrait mais elle ne le définie pas réellement.
entier
reel
chaîne [20]
1 Introduction
Le but de cette partie est de gérer un ensemble fini d’éléments dont le nombre varie au cours de
l’exécution du programme. Les éléments de cet ensemble peuvent être de différentes sortes : nombres
entiers ou réels, chaînes de caractères ou des objets informatiques plus complexes comme des
identificateurs de processus ou les expressions arithmétiques.
On ne s’intéressera pas aux éléments de l’ensemble en question mais aux opérations que l’on effectue sur
cet ensemble. Cette gestion des ensembles doit, pour être efficace, répondre à deux critères parfois
contradictoires : un minimum d’espace mémoire utilisé et un minimum d’instructions élémentaires pour
réaliser une opération.
Elle peut être créée et détruite au cours de l’exécution du bloc dans lequel elle est déclarée
L’espace mémoire rendu libre peut être récupéré
L’accès à la valeur se fait par un pointeur
3 Les pointeurs
Prenons par exemple un tableau qui contient l'ensemble des livres d'une bibliothèque: ce nombre de livres
ne sera pas fixe, puisqu'on peut ajouter de nouveau ouvrages, et en supprimer également.
On ne va donc pas pouvoir utiliser une déclaration classique à l'aide d'un type, nous allons avoir recours à
une autre variable, c’est le pointeur, qui contiendra l'adresse de la variable dynamique, qui nous permettra
donc d'accéder à l'endroit où se trouve la variable, d'en lire le contenu, de l'effacer, ou de la modifier.
3.1 Définition
Un pointeur est une variable statique dont les valeurs sont des adresses. Une variable dynamique pointée
par P sera noté P^.
Comme nous l'avons vu, un pointeur est une variable qui permet de stocker une adresse, il est donc
nécessaire de comprendre ce qu'est une adresse.
Chacune de ces « cases » (appelées blocs) est identifiée par un numéro. Ce numéro s'appelle adresse.
Il suffit donc de stocker l'adresse de la variable dans un pointeur (il est prévu pour cela) afin de pouvoir
accéder à celle-ci (on dit que l'on « pointe vers la variable »).
Le schéma ci-dessus montre par exemple par quel mécanisme il est possible de faire pointer une variable
(de type pointeur) vers une autre. Ici le pointeur stocké à l'adresse 24 pointe vers une variable stockée à
l'adresse 253 (les valeurs sont bien évidemment arbitraires).
Ils permettent de manipuler de façon simple des données importantes (au lieu de passer à une
fonction un élément très grand en taille, on pourra par exemple lui fournir un pointeur vers cet
élément).
Les tableaux ne permettent de stocker qu'un nombre fixé d'éléments de même type. En stockant
des pointeurs dans les cases d'un tableau, il sera possible de stocker des éléments de taille diverse,
et même de rajouter des éléments au tableau en cours d'utilisation (c'est la notion de tableau
dynamique qui est très étroitement liée à celle de pointeur).
Il est possible de créer des structures chaînées.
Exemple :
La variable pointeur P a pour valeur l’adresse « 43 ». Elle pointe sur l’espace mémoire « P^ » à l’adresse
43 dont le contenu est le caractère « T ».
On déclare un pointeur en précédant son type de base par le caractère « ^ ». Par exemple pour déclarer un
pointeur appelé P sur une variable de type caractère on écrit : P : ^ caractere
Pour utiliser la variable pointée il faut d’abord la créer, ce qui a pour effet de réserver un bloc en
mémoire capable de stocker la valeur de cette variable.
Par exemple pour créer la variable pointé par Pe on écrit : Allouer (Pe), et pour créer la variable pointé
par Pf on écrit : Allouer (Pf).
On remarque que la taille de la variable pointé par Pf est plus grande que la taille de la variable pointé par
Pe puisque la 1ère va contenir la valeur d’un entier et la 2ème va contenir la valeur d’un réel.
Réserve un bloc mémoire de la taille adéquate pour contenir la valeur de la variable pointée par P.
Récupère l’adresse de ce bloc et la met dans la variable P.
Exemple :
3.4.1 Syntaxe
Pour accéder à la variable pointé par un pointeur appelé P on utilise l’opérateur ^. Donc « P^ » désigne la
variable pointée par P.
3.4.2 Exemple
L’algorithme suivant stocke la valeur 10 dans une variable de type pointeur.
3.5.1 Syntaxe
Lorsqu’une variable pointée n’a plus d’utilité, il est possible de la supprimer et de rendre disponible
l’espace mémoire qu’elle occupe. L’instruction qui réalise cette tache est « Liberer ».
Si on a déclaré un pointeur appelé P, l’instruction : « Liberer (P) » supprime la variable pointé par P et
libère l’espace mémoire occupé par cette variable. Par contre la variable pointeur « P » n’est pas
supprimée mais son contenu n’est plus l’adresse de la variable pointé mais une adresse non significative.
3.5.2 Exemple
L’exemple suivant montre l’effet de l’instruction « Liberer » sur la mémoire.
Remarque : l’utilisation de la variable pointé après l’instruction Liberer provoque une erreur.
3.5.1 Initialisation
Pour initialiser un pointeur on peut lui affecte une valeur constante appelé « Nil », cette constante n’est
pas une valeur d’une adresse mémoire, mais elle signifie que le pointeur ne pointe sur rien.
Ainsi si on déclare un pointeur P vers un entier il sera initialisé comme suit : P Nil
On peut aussi initialiser un pointeur à partir d’un autre pointeur comme le montre l’exemple suivant :
Figure a
Si l’instruction : PQ est exécutée, nous passerons à la nouvelle situation illustrée par la figure b.
Et si l’on modifie la valeur de Q^, P^ sera également modifie et restera égal à Q^.
Figure b
Par contre, si l’instruction : P^Q^ est exécutée. On passe à la nouvelle situation illustrée par la figure
c.
Figure c
Le contenu d’une variable pointeur peut être recopié dans une autre variable pointeur grâce à l’instruction
d’affectation, à condition que les 2 pointeurs soient de même sous type.
Soient 2 pointeurs P1 et P2 qui pointent vers le même sous-type par exemple entier. On peut écrire :
« P1 P2 » : cela a pour effet de copier l’adresse contenu dans P2 dans le pointeur P1.
Exemple :
On peut aussi comparer les valeurs des variables pointées par 2 pointeurs.
1- Introduction
Le principal problème des données stockées sous forme de tableaux est que celles-ci doivent être
ordonnées : le "suivant" doit toujours être stocké physiquement après. Un tableau correspond à une
gestion dans un cahier : un adhérent par page. Supposons qu’on veut stocker les adhérents par ordre
alphabétique. Si un nouvel adhérent se présente, il va falloir trouver où l'insérer, effacer toutes les pages
suivantes pour les réécrire une page plus loin, puis insérer le nouvel adhérent. Pour résumer, la faiblesse
d’un tableau provient de son aspect statique :
Une meilleure solution permettant de contourner ces difficultés liées aux tableaux, que nous
venons d’identifier, consiste à introduire la structure de liste chaînée : les pages sont numérotées, sur
chaque page est indiquée la page de l'adhérent suivant, sur le revers de couverture on indique l'adresse du
premier. Ainsi, l'insertion d'un nouvel adhérent se fera avec le minimum d'opérations.
2- Définition
Les listes font parties des structures de données abstraites très utilisées dans les programmes. Une
structure de données abstraite est un type de donnée défini par l’utilisateur sous forme de deux
composantes une pour représenter les données et une pour représenter les opérations d’accès aux données
et ses caractéristiques.
Une liste stocke un ensemble de données et permet de parcourir cet ensemble du début à la fin dans
l'ordre. C’est une suite finie d'éléments, lorsque elle n’est pas vide, son premier élément est appelé tête et
la suite constituée des autres éléments est appelée queue.
Les listes servent pour représenter des structures de données dont la taille change souvent ou pour
stocker des données dans l'ordre en ayant la possibilité de les mettre à jour quand il y a des changements.
o au début de la liste
o à la fin de la liste
o à un rang donné
La recherche d’un élément:
o résultat booléen
o résultat = rang de la première occurrence
o résultat = nombre d'occurrences
La suppression d’un élément:
o caractérisé par sa valeur
o caractérisé par son rang
Le calcul de la longueur d'une liste (nombre d'éléments)
4- Représentation
4.1 Représentation graphique
Ainsi le type Liste est un pointeur vers le type Cellule qui est un enregistrement formé de deux champs :
le 1er contient la valeur de l’élément donc le type peut être un type quelconque
le 2ème contient un pointeur vers la cellule suivante c'est-à-dire il contient l’adresse de cette cellule.
La dernière cellule ne pointe vers rien donc elle doit contenir la valeur Nil.
Le type Liste contient un pointeur vers le type Cellule qui contient l’adresse du 1er élément de la liste.
5.1 ListeVide
La 1ère fonction de base qu’on doit écrire pour pouvoir manipuler des données de type liste est la fonction
ListeVide () qui prend en entrée une liste et retourne vrai si elle est vide et faux sinon.
5.2 PosFin
La fonction PosFin () prend en entrée une liste et retourne un pointeur vers le dernier élément de la liste.
5.3 AjouterTete
La fonction AjouterTete () prend en entrée une valeur à ajouter en tête d’une liste donnée et retourne un
pointeur vers le début de la nouvelle liste.
Cette fonction doit créer une nouvelle variable dynamique pour contenir la nouvelle cellule, puis remplir
le champ valeur et le champ suivant de cette cellule ensuite mettre l’adresse de cette cellule dans le
pointeur vers la tête de liste et retourner cette valeur.
5.4 Ajouter_fin
La procédure Ajouter_fin() prend en entrée une valeur à ajouter à la fin d’une liste donnée.
Fin
5.5 Longueur d’une Liste
Cette fonction calcule le nombre des éléments de la liste. Elle prend comme argument à l’entrée une liste et
retourne en sortie un entier représentant la longueur de la liste.
Version itérative:
xx+1
listeAux listeAux^.suivant
Fintanque
Retourner (x)
Fin
Version récursive:
Sinon
Retourner (1 + long(liste1^.suivant) )
Finsi
Fin
Version itérative :
Var
liste2 : Liste
Début
liste2 liste1
liste2 liste2^.suivant
Fintantque
Retourner (vrai)
Sinon
Retourner (faux)
Finsi
Fin
Version récursive:
Var
Début
Retourner (faux)
Sinon
Si (x = liste1^.valeur) alors
Retourner (vrai)
Sinon
Finsi
Finsi
Fin
1- Introduction
Le but de ce chapitre est de décrire des représentations des structures de données de base à savoir les piles
et les files.
2- Les piles
2.1 Présentation
Une pile est une suite de cellules allouées dynamiquement (liste chaînée) où l’insertion et la suppression
d’un élément se font toujours en tête de liste.
L’image intuitive d’une pile peut être donnée par une pile d’assiettes, ou une pile de dossiers à condition
de supposer qu’on prend un seul élément à la fois (celui du sommet). On peut résumer les contraintes
d’accès par le principe « dernier arrivé, premier sorti » qui se traduit en anglais par : Last In First Out
(figure 1).
La structuration d’un objet en pile s’impose lorsqu’on mémorise des informations qui devront être traitées
dans l’ordre inverse de leur arrivée. C’est le cas, par exemple, de la gestion des adresses de retour dans
l’exécution d’un programme récursif.
En supposant que les éléments de la pile sont des entiers, celle-ci se déclare de la façon suivante :
3- Les Files
3.1 Présentation
Une file est une suite de cellules allouées dynamiquement (liste chaînée) dont les contraintes d’accès sont
définies comme suit :
L’image intuitive d’une file peut être donnée par la queue devant un guichet lorsqu’il n’y a pas de
resquilleurs. On peut résumer les contraintes d’accès par le principe « premier arrivé, premier sorti » qui
se traduit en anglais par : First In First Out (figure 2).
La structuration d’un objet en file s’impose en particulier lorsqu’une ressource doit être partagée par
plusieurs utilisateurs : il y a formation d’une file d’attente.
Un exemple est donné par le spooler d’impression qui est un programme qui reçoit, traite, planifie et
distribue les documents à imprimer dans un système multiprogrammé.
En supposant que les éléments de la file sont des entiers, celle-ci se déclare, alors, de la façon suivante :
Dans le cas où la file est vide, comme la queue, la tête de la file doit également pointer vers le nouvel
élément.
Introduction
Les structures que nous avons considérées jusqu’à présent sont uniquement des structures linéaires, dans
le sens où :
En effet, dans un tableau de taille n, chaque case i (pour 1 < i < n) possède comme unique prédécesseur la
case i−1, et comme unique successeur la case i+1. La case 0 n’a pas de prédécesseur, mais la case 1 est
son unique successeur. La case n n’a elle pas de successeur, mais bien un prédécesseur : la case n−1.
Dans les listes, chaque élément possède un seul et unique champ next, qui référence un successeur
unique, ou qui contient une valeur conventionnelle pour indiquer qu’il n’y a pas de successeur. Par
ailleurs, la tête n’a pas de prédécesseur, et, pour tous les autres éléments e, il n’existe qu’un seul autre
élément e0 dont le champ next référence e. Enfin, les piles et files sont essentiellement des listes.
Nous allons considérer, dans ce chapitre, une généralisation de ces structures linéaires en levant la
restriction numéro 2 : chaque élément sera autorisé à avoir plusieurs successeurs tout en ayant au plus un
seul prédécesseur. On a dès lors affaire à une structure qui se ramifie, puisque, lors d’un parcours de cette
structure, on a, à chaque élément, plusieurs successeurs possibles pour « continuer le parcours ». C’est
pour cette raison que ce type de structures est appelé un arbre
1- Les Arbres
1.1 Définition
Un arbre est un ensemble de nœuds avec une relation de hiérarchie entre Père et Fils
Remarque :
- Un arbre est :
- soit un arbre vide
- soit un nœud racine et un ensemble de (sous)-arbres T1,…, Tn de sorte à ce que la racine de TI
est connecté par un lien direct à la racine.
- la racine est un nœud sans père
- la feuille est un nœud sans fils
- le nœud interne est un nœud admettant au moins un fils
Cours 6 : Arbres & Graphes Page 48
ISET Béja Cours Algorithmique 2
1. Exemple
1.3 Implémentation
Un arbre peut être représenté par un ensemble de nœuds, chaque nœud contient une valeur et un tableau
de N cases qui contiennent des pointeurs pour les N fils du nœud. N représente le nombre maximal de fils
que peut avoir un nœud dans l’arbre.
La figure ci-dessous représente un arbre avec N= 3, donc chaque nœud doit avoir au maximum 3 fils.
1.4.1 ArbreBinVide
Teste si un arbre binaire est vide ou non.
Si (A = Nil) Alors
Retourner (Vrai)
Sinon
Retourner (Faux)
FinSi
Fin
1.4.2 CreerFeuille
Cette fonction crée un nœud contenant une valeur et retourne son adresse en mémoire.
1.4.3 CreerArbreBin
Cette fonction crée un arbre binaire à partir d’un sous arbre droit et un sous arbre gauche existants et
d’une valeur à mettre dans la racine enfin elle retourne l’adresse de la racine.
1.4.4 SupprimeArbreBin
Cette procédure permet de supprimer tous les nœuds d’un arbre binaire dont l’adresse de sa racine est
fournie en paramètre.
- Nœud « b » :
- niveau=1
- hauteur=2
- hauteur de l’arbre=3
Fin
1.5.3 Exemple
Si on prend l’arbre suivant :
2- Les graphes
Remarquons que si nous relâchons les deux hypothèses de l’Introduction, à savoir qu’on autorise
également chaque élément à avoir plusieurs prédécesseurs, on obtient un graphe. Nous présentons dans le
paragraphe suivant la structure de données « Graphe » et les opérations de base concernant cette structure.
2.1 Définition
- Les graphes sont des structures de données qui permettent de représenter un grand nombre de données :
des réseaux, des plans, des trajets, etc.
- Un graphe orienté est un couple G=(S, A) où :
- S représente un ensemble de sommets,
- A représente un ensemble d’arcs.
Soit a A, a= (u, v)
- u est appelé origine de a,
- v est appelé extrémité de a,
- u=prédécesseur (v)
- v=successeur (u)
- l’arc a est noté uv
2.2 Exemple
S= {1, 2, 3, 4,5}
A= {(1,2), (1,3), (2,4), (2,5), (3,1), (3,3), (4,5), (5,1)}
2.3 Implémentation :
Pour implémenter un graphe, nous allons utiliser le mode de représentation par matrice d’adjacence.
Dans un graphe, il faux pouvoir bénéficier des fonctionnalités suivantes :
- créer un graphe
- initialiser un graphe
- ajouter un arc dans un graphe
- supprimer un arc dans un graphe
- ajouter un sommet dans le graphe
- trouver le successeur d’un sommet du graphe
Type
Sommet=entier
Arc = Enreg
Origine : Sommet
Extrémité : Sommet
FinEnreg
Graphe =Enreg
T : Tableau [1..Max][1..Max] de Sommet
NS : entier
FinEnreg
Avec NS=Nombre de sommets dans les graphes.
Max : nombre maximal pour le nombre de sommets du graphe
Fin Si
Retourner (G)
Fin
2.3.8 Exemple
Nous allons créer le graphe suivant :
1- Introduction
Un algorithme de recherche est un type d'algorithme qui cherche, parmi une liste de données, les
éléments satisfaisant certains critères.
En général, le critère de recherche porte sur la valeur d'une clé (ex : chercher un numéro de tel dans un
ensemble de personnes (une personne=nom + adresse + tel...), rechercher un mot dans un dictionnaire
(dictionnaire=mots+définitions+photo+...), ...). Cette clé peut aussi être simplement la valeur de l'objet
(recherche d'un entier dans une liste d'entier, ...).
Les algorithmes de recherche que nous allons présenter retournent un élément quelconque ayant une clé
de la valeur cherchée. Il est parfois plus utile de retourner la place de cet élément dans l'ensemble des
données (pour par exemple effectuer ensuite une suppression, ...).
Dans l’algorithme principal, l’appel sera comme suit : Recherche_dicho(x, L, 1, longueur (L))
Avec longueur (L) est une fonction qui renvoi la longueur de la liste chaînée.
La fonction ieme (milieu, L) est une fonction qui retourne la valeur de la cellule ayant la position milieu
de la liste L.
La fonction longueur (L) est une fonction qui calcule la longueur d’une liste chaînée L.
Le parcours en profondeur est un parcours récursif sur un arbre. Il existe trois ordres pour cette méthode
de parcours.
Dans ce mode de parcours, on traite la racine, puis le sous-arbre gauche, puis le sous-arbre droit.
Ainsi, un parcours préfixé pour cet arbre va nous donner comme résultat :
Remarque :
Traiter(A) : est le traitement à exercer sur l’arbre de racine A, ajout, suppression, recherche, affichage, …
Dans ce mode de parcours, on traite le sous-arbre gauche, puis la racine, puis le sous-arbre droit.
Ainsi un parcours infixé pour cet arbre va nous donner comme résultat :
5 – 8 – 10 – 11 – 13 – 14 – 15
Ainsi un parcours postfixé pour cet arbre va nous donner comme résultat :
5 – 10 – 8 – 13 – 15 – 14 – 11
3.2 Application
Un arbre binaire de recherche (ABR) est un arbre binaire tel que la valeur stockée dans chaque sommet
est supérieure à celles stockées dans son sous-arbre gauche et inférieur à celles de son sous-arbre droit.
Exemple :
1- Introduction
On désigne par "tri" l'opération consistant à ordonner un ensemble d'éléments en fonction de clés sur
lesquelles est définie une relation d'ordre.
Les algorithmes de tri ont une grande importance pratique. Ils sont fondamentaux dans certains domaines,
comme l'informatique de gestion où l'on tri de manière quasi-systématique des données avant de les
utiliser.
Fin
ALGORITHME TRI_SELECTION
CONST N = 100
VAR
V, VT : Tableau [1..N] de entier
i, j, i_min, MIN, MAX : entier
DEBUT
Lire_vecteur (V)
MAX Max_vecteur (V)
FIN
ALGORITHME TRI_insertionS
CONST N = 100
VAR
V : Tableau [1..N] de entier
e, i, j, k, p : entier
trouve : booleen
DEBUT
Pour i de 1 à N Faire
Lire(e)
{chercher la position d'insertion p}
j 1
trouve faux
pi
Tantque (j < i) et (trouve = faux) Faire
Si (V[j] > e) Alors
pj
trouve vrai
FinSi
jj+1
FinTantque
{decaler les cases entre p et i}
Pour k de i a p+1 Pas (-1) Faire
V[k] V[K-1]
FinPour
{mettre la valeur lu dans la case p}
V[p] e
FinPour
{affichage du résultat}
Pour i de 1 a N Faire
Ecrire (V[i])
FinPour
FIN
3.3 Exemple
Les grandes étapes d’évolution du tableau au fil de l'algorithme. En bleu, le tableau trié, en rouge, la
valeur de la mémoire qui contient la valeur à insérer.
CONST N = 100
VAR
V : Tableau [1..N] de entier
bi, bs, e, i, j, k, p : entier
trouve : booleen
DEBUT
Pour i de 1 à N Faire
Lire(e)
{chercher la position d'insertion p}
j 1
bs i
bi i DIV 2
trouve faux
pi
FIN
L'algorithme de tri rapide, "quick sort" en anglais, est un algorithme de type dichotomique. Son
principe consiste à séparer l'ensemble des éléments en deux parties. Pour effectuer la séparation, une
valeur pivot est choisie. Les valeurs sont réparties en deux ensembles suivant qu'elles sont plus
grandes ou plus petites que le pivot. Ensuite, les deux ensembles sont triés séparément, suivant la
même méthode. L'algorithme, est récursif, mais il n'est pas nécessaire de fusionner les deux
ensembles. Le résultat du tri est égal au tri de l'ensemble dont les valeurs sont inférieures au pivot
concaténé à l'ensemble des valeurs supérieures au pivot.
Le choix du pivot est le problème central de cet algorithme. En effet, l'idéal serait de pouvoir répartir
les deux ensembles en deux parties de taille à peu prés égales. Cependant, la recherche du pivot qui
permettrait une partition parfaite de l'ensemble en deux parties égales aurait un coût trop important.
C'est pour cela que le pivot est choisit de façon aléatoire parmi les valeurs de l'ensemble. Dans la
pratique, le pivot est le premier ou le dernier élément de l'ensemble à fractionner. En moyenne, les
deux ensembles seront donc de taille sensiblement égale.
5.3 Exemple :
Les grandes étapes de l'évolution du tableau au fil de l'algorithme : En bleu, les valeurs déja
positionnées, en rose, les valeurs qui servent de pivot pour passer à la ligne suivante.
5.4 Algorithme
Fonction Partition (var T : tableau [1..N] de entier, deb, fin : entier) : entier
Var
i, j, v, p: entier
Debut
i deb-1
j fin
p T[fin]
Repeter
Repeter
i i+1
Jusqu'à ( T[i] ≥ p )
Repeter
j j-1
Jusqu'à ( T[j] ≤ p )
Si ( i< j) Alors
v T[i]
T[i] T[j]
T[j] v
FinSi
Jusqu'à ( i ≥ j )
T[fin] T[i]
T[i] p
Retourner (i)
Fin
Exemple :
10
8 7
2 3 6
V T [k]
j k DIV 2
Debut
max 1
Pour j de 1 à M Faire
Si (T[j] > T[max]) Alors
max j
FinSi
FinPour
v T[max]
T[max] T [M]
Retourner (v)
Fin
{----------------------------------------------------------------------------}
ALGORITHME TRI_TAS
CONST N = 100
VAR
S, T : tableau [1..N] de entier
N, i : entier
DEBUT
Lire_vecteur (S)
Pour i de 1 a N Faire
T[i] S[i]
Insert (i)
FinPour
Pour i de N a 1 PAS (-1) Faire
T[i] SuppMax (i)
FinPour
Affiche_vecteur(T)
FIN
1- Introduction
Un algorithme est une procédure finie et un enchaînement des étapes de résolution d’un problème. Un
algorithme doit être appliqué sur toutes les données possibles du problème et doit fournir une solution
correcte dans chaque cas. En informatique, il est possible de résoudre un problème donné par
implantation d’un algorithme conçu spécification pour la résolution. Cependant, pour un problème
donné, il peur être résolu souvent avec plusieurs algorithmes.
Le problème qui se pose alors : Comment choisir entre les algorithmes? Comment déterminer
l’efficacité d’un algorithme par rapport à un autre ? De ces questions est née la complexité des
algorithmes.
2- Définition
La mesure de la complexité d’un algorithme est une estimation de son temps d’exécution ainsi que de
l’espace mémoire utilisé. Ces 2 mesures sont appelées complexité spatiale et temporelle de
l’algorithme.
La complexité spatiale exprime la taille occupée par les données de l’algorithme dans la mémoire de
l’ordinateur.
3- Calcul de la complexité
Le temps d’exécution d’un algorithme est fonction du nombre d’opérations élémentaires dans cet
algorithme. Par exemple, pour le parcourt d’un tableau et la recherche d’un élément donnée, la
comparaison entre l’élément courant du tableau et l’élément recherché sera prise en compte.
La complexité d’un algorithme dépend de plusieurs facteurs, tel que la taille des données à traiter et le
type d’opérations à effectuer, par exemple une addition est plus rapide qu’une multiplication.
Il n’existe pas de méthode formelle pour calculer la complexité d’un algorithme mais il y a des règles
qui peuvent être appliquées.
On note T (Op) : le temps d’exécution d’une opération Op, voici les règles qui peuvent être appliquées
lors du calcul du temps d’exécution d’un algorithme :
- Si Op est une instruction élémentaire alors T (Op) est considéré comme constant et il est noté
O(1).
- Si Op est une instruction conditionnelle (si condition alors op1 sinon op2) alors T (Op) est
estimé à T (condition) + Max (T (op1) + T (op2)).
- Si Op est une boucle (Pour i de 1 à N faire Opération) alors T (Op) est estimé à Somme (T
(Opération)) qui représente la somme des temps des opérations de chaque itération.
4- Type de complexité
Les algorithmes usuels peuvent être classés en un certain nombre de grandes classes de complexité.
Les algorithmes sous-linéaires, dont la complexité est en général en O(log n). C'est le cas de la
recherche d'un élément dans un ensemble ordonné fini de n éléments (recherche d’un élément
dans un tableau de taille n).
Les algorithmes linéaires en complexité O(n) sont considérés comme rapides, comme
l'évaluation de la valeur d'une expression composée de n symboles ou les algorithmes de tri de
tableaux de n éléments.
Plus lents sont les algorithmes de complexité située entre O(n2) et O(n3), c'est le cas de la
multiplication des matrices de taille n.
Puisque on calcule la complexité temporelle en faisant une approximation, on utilise donc les notations
suivantes:
p
O(n ) : complexité polynomiale
Dans cette fonction, on exécute (n-1) tests de comparaison. La complexité temporelle est donc de
l’ordre de (n-1) : elle est noté O(n).
Algorithme Produit_Matrice
var
u,v, w : Tableau[1..n] de entier
i : entier
Debut
pour i de 1 à n faire
pour j de 1 à n faire
w[i,j] u[i,j] * v[i,j]
finpour
finpour
fin
La complexité temporelle de cet algorithme est la somme des temps nécessaire pour faire les multiplications sur
une ligne de la matrice w à savoir n * le temps d’une multiplication qui est considéré comme constante donc
O(n). Ce calcul est répété pour chaque ligne donc la complexité précédente doit être multipliée par n.
Finalement on peut en déduire que l’algorithme est de complexité égale à O(n 2).