0% ont trouvé ce document utile (0 vote)
19 vues36 pages

Cours Algo 2

algorithme

Transféré par

Hanen ghazouani
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PPTX, PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
19 vues36 pages

Cours Algo 2

algorithme

Transféré par

Hanen ghazouani
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PPTX, PDF, TXT ou lisez en ligne sur Scribd
Vous êtes sur la page 1/ 36

RAPPEL SUR LES POINTEURS

ET LES LISTES CHAINÉES


Assuré par Mme Hanene
Ghazouani
Les pointeurs
• En algorithmique, les pointeurs ne sont pas des

structures ou des types de données intrinsèques, comme


dans des langages comme C ou C++, mais ils sont
souvent utilisés de manière conceptuelle pour expliquer
comment manipuler directement des éléments de
structures de données (comme des tableaux, des listes
chaînées, etc.) via des références.
Qu'est-ce qu'un pointeur en algorithmique ?

• En algorithmique, un pointeur est une variable ou une

référence qui permet de se référer directement à un


emplacement de mémoire. Le pointeur ne contient pas la
valeur de la donnée, mais l'adresse de la donnée (le lieu
où elle est stockée).
• L'idée est de travailler sur des adresses plutôt que de

copier des valeurs directement, ce qui peut rendre les


algorithmes plus efficaces en termes de mémoire et de
temps.
Concepts clés en algorithmique liés aux
pointeurs
1. Références et adresses : Un pointeur fait référence à
l'emplacement où une donnée est stockée, plutôt qu'à la
donnée elle-même. Cela permet de manipuler les éléments
dans leur emplacement d'origine sans les copier.
2. Allocation dynamique : L'usage de pointeurs est
souvent lié à l'allocation dynamique de mémoire.
Par exemple, en travaillant avec des structures comme des
listes chaînées, on crée des éléments dans des zones de
mémoire spécifiques et on les relie avec des pointeurs.
Concepts clés en algorithmique liés aux
pointeurs
3 . Modification de variables via pointeurs : Un pointeur peut être
utilisé pour modifier directement la valeur d'une variable à l'endroit où elle
est stockée en mémoire. Ceci est utile dans des algorithmes où l'on doit
modifier plusieurs éléments d'une structure sans créer de copies.
4. Structures de données avec pointeurs : Les pointeurs sont
essentiels pour la mise en œuvre de certaines structures de données
comme :
 Listes chaînées : Chaque nœud d'une liste contient une donnée et un

pointeur vers le nœud suivant.


 Arbres binaires : Chaque nœud contient une donnée et deux

pointeurs, chacun vers un enfant (gauche et droit).


Exemple avec une liste chaînée :
Voici un exemple d'usage de pointeurs dans une liste chaînée simple.
Créer une structure de liste chaînée et permettre l'ajout de nouveaux éléments à la liste.
Structure Noeud
entier valeur
Noeud pointeurVersSuivant
Procédure ajouterEnTete(Liste, valeur)
NouveauNoeud ← allouer Noeud
NouveauNoeud.valeur ← valeur
NouveauNoeud.pointeurVersSuivant ← Liste.tete
Liste.tete ← NouveauNoeud
Procédure afficherListe(Liste)
NoeudCourant ← Liste.tete
Tant que NoeudCourant ≠ NULL afficher NoeudCourant.valeur
NoeudCourant ← NoeudCourant.pointeurVersSuivant
Début Liste ← allouer Liste
Liste.tete ← NULL
ajouterEnTete(Liste, 10)
ajouterEnTete(Liste, 20)
ajouterEnTete(Liste, 30)
afficherListe(Liste)
Explication
• Structure de liste chaînée :
• Chaque Noeud contient une valeur et un pointeur vers le suivant.
• On alloue dynamiquement un nouveau nœud lorsqu'on souhaite
ajouter un élément à la liste.
• ajouterEnTête() :
• Cette procédure crée un nouveau nœud et le place en tête de la
liste. Elle modifie directement la liste via des pointeurs.
• afficherListe() :
• Cette procédure traverse la liste en suivant les pointeurs de
chaque nœud jusqu'à la fin (quand le pointeur est NULL).
Pointeurs et performances
• L'utilisation des pointeurs améliore l'efficacité des

algorithmes dans plusieurs cas, car elle permet d'éviter de


copier des grandes quantités de données. Les structures
dynamiques comme les listes chaînées, les piles, et les
arbres reposent principalement sur les pointeurs pour
gérer la mémoire de manière flexible et dynamique.
• Ainsi, en algorithmique, les pointeurs permettent de

gérer la mémoire de façon plus fine et de manipuler


des structures complexes de manière plus efficace.
Pointeurs et performances
• Les pointeurs sont un concept fondamental en

programmation, surtout dans des langages comme C, C+


+, et d'autres langages basés sur des bas niveaux de
mémoire.
Principaux concepts liés aux pointeurs
• Déclaration d'un pointeur : Pour déclarer un pointeur, il faut utiliser le

symbole * avant le nom de la variable. Le type du pointeur doit


correspondre au type de la variable qu'il pointe.
int *ptr; // Déclare un pointeur à un entier
• Initialisation d'un pointeur : Un pointeur peut être initialisé en stockant

l'adresse mémoire d'une variable avec l'opérateur &.


int a = 10; int *ptr = &a; // Le pointeur 'ptr' pointe vers l'adresse de 'a'
• Déréférencement d'un pointeur : Déréférencer un pointeur signifie

accéder à la valeur stockée à l'adresse mémoire pointée par le pointeur.


Pour cela, on utilise l'opérateur *.
• int valeur = *ptr; // Accède à la valeur de 'a' à travers le pointeur
Principaux concepts liés aux pointeurs
• Pointeur nul : Un pointeur peut être initialisé à une valeur spéciale

appelée "pointeur nul" (généralement NULL ou nullptr en C++


moderne). Cela signifie qu'il ne pointe vers aucune adresse valide.
int *ptr = NULL;
• Pointeur et tableaux : Les tableaux et les pointeurs sont

étroitement liés. Le nom d'un tableau est en réalité l'adresse de


son premier élément, donc on peut traiter un tableau comme un
pointeur.
int arr[3] = {1, 2, 3}; int *ptr = arr; // 'ptr' pointe vers le premier
élément de 'arr'
Exercice :

Voici un petit exercice avec pointeurs en C, accompagné de la solution.


Écrivez un programme qui utilise des pointeurs pour échanger les valeurs de
deux variables entières.
• Objectif :

Utiliser des pointeurs pour modifier les valeurs de variables en dehors de la


fonction.
• Instructions :

1. Déclarez deux variables entières a et b et initialisez-les avec des valeurs.


2. Créez une fonction echanger(int *x, int *y) qui échange les valeurs de a et
b en utilisant des pointeurs.
3. Affichez les valeurs de a et b avant et après l'appel de la fonction
d'échange.
Solution
Algorithme EchangerParPointeurs
• Entrée : Deux entiers X et Y
• Sortie : Les valeurs échangées de X et Y
Fonction Echanger(Adresse a, Adresse b)
Début
Temp ← *a
*a ← *b
*b ← Temp
Fin
Début
Lire X, Y
Afficher ("Avant l'échange : X =", X, " Y =", Y ) // Appel de la fonction en passant les adresses
Echanger(Adresse(X), Adresse(Y))
Afficher ("Après l'échange : X =", X, " Y =", Y)
Fin
Solution
#include <stdio.h> // Fonction pour échanger deux entiers en utilisant des pointeurs
void echanger(int *x, int *y)
{
int temp = *x; // Sauvegarder la valeur pointée par x
*x = *y; // Copier la valeur de y dans x
*y = temp; // Copier la valeur de temp (ancienne valeur de x) dans y
}
int main() {
int a = 5;
int b = 10;
printf("Avant l'échange :\n");
printf("a = %d, b = %d\n", a, b); // Appel de la fonction echanger pour inverser les valeurs de a et b
echanger(&a, &b);
printf("Après l'échange :\n");
printf("a = %d, b = %d\n", a, b);
return 0; }
Explication du programme
• Fonction echanger(int *x, int *y) :
• La fonction prend deux pointeurs en paramètres.
• Un troisième entier temporaire temp est utilisé pour stocker la
valeur de *x.
• Ensuite, la valeur de *y est attribuée à *x, et enfin, temp est affecté
à *y. Cela échange les valeurs.
• Fonction main() :
• Deux variables entières a et b sont initialisées avec 5 et 10
respectivement.
• Les adresses de a et b sont passées à la fonction echanger(), qui
modifie directement les valeurs à ces adresses.
• Le programme affiche les valeurs avant et après l'échange.
Sortie du programme
• Avant l'échange : a = 5, b = 10

• Après l'échange : a = 10, b = 5


Exercice :

Écrire un algorithme qui permet d'inverser les éléments d'un tableau en utilisant des
pointeurs. Vous devez manipuler directement les adresses des éléments du tableau
au lieu de passer par des indices classiques.
Objectif :
Utiliser des pointeurs pour échanger les valeurs des éléments d’un tableau, de sorte
que le premier élément devienne le dernier, le deuxième devienne l'avant-dernier, etc.
Instructions :
• Demandez à l'utilisateur la taille du tableau.

• Créez un tableau dynamique (simulé en algorithmique) pour stocker les éléments.

• Écrivez une procédure qui utilise des pointeurs pour inverser les éléments du

tableau.
• Affichez le tableau avant et après l'inversion.
Solution Procédure inverserTableau(Tableau,
taille) gauche ← 0 // Pointeur vers le
• Début // Demander la taille du début du tableau droite ← taille - 1 //
tableau afficher "Entrez la taille du Pointeur vers la fin du tableau
tableau : « Tant que gauche < droite // Échanger les
• lire taille éléments pointés par gauche et droite
• // Déclarer un tableau dynamique temp ← Tableau[gauche]
(simulé) et demander à l'utilisateur Tableau[gauche] ← Tableau[droite]
d'entrer les éléments Tableau ← Tableau[droite] ← temp // Déplacer les
allouer tableau de taille éléments pointeurs vers le centre du tableau
gauche ← gauche + 1
• Pour i ← 0 à taille - 1
droite ← droite – 1
• afficher "Entrez l'élément ", i + 1, " : FinTantQue
« FinProcédure
• lire Tableau[i] // Appeler la procédure d'inversion
• FinPour inverserTableau(Tableau, taille)
• // Afficher le tableau avant inversion // Afficher le tableau après inversion
afficher "Tableau après inversion :"
• afficher "Tableau avant inversion :" Pour i ← 0 à taille - 1
• Pour i ← 0 à taille – 1 afficher Tableau[i]
• afficher Tableau[i] FinPour
• FinPour Fin
Explication
• Tableau dynamique :
• L'utilisateur entre la taille du tableau, et un tableau est créé (alloué
dynamiquement dans une implémentation réelle).
• Les éléments sont ensuite saisis par l'utilisateur et stockés dans le tableau.

• Procédure inverserTableau :
• Deux pointeurs, gauche et droite, sont utilisés pour parcourir le tableau. gauche
commence au début du tableau, et droite commence à la fin.
• Dans chaque itération, les éléments aux positions gauche et droite sont
échangés.
• Les pointeurs se rapprochent l’un de l’autre à chaque étape (le pointeur gauche
est incrémenté, et droite est décrémenté), jusqu’à ce qu'ils se rencontrent ou se
croisent, ce qui marque la fin de l'inversion.
• Affichage du tableau :
• Le tableau est affiché une première fois avant l'inversion.
• Après avoir appelé la procédure inverserTableau, le tableau est affiché une
seconde fois avec les éléments dans l'ordre inversé.
Exemple d'exécution
• Entrée :

• Entrez la taille du tableau : 5 Entrez l'élément 1 : 10

Entrez l'élément 2 : 20 Entrez l'élément 3 : 30 Entrez


l'élément 4 : 40 Entrez l'élément 5 : 50
Exemple d'exécution
• Sortie:

• Tableau avant inversion : 10 20 30 40 50

• Tableau après inversion : 50 40 30 20 10


Conclusion
• Cet exercice montre comment utiliser des pointeurs

conceptuels pour inverser les éléments d'un tableau en


algorithmique. L'approche utilisant des pointeurs est plus
efficace que de copier les valeurs dans un tableau
temporaire, car elle évite l'utilisation de mémoire
supplémentaire et minimise les opérations inutiles.
LES LISTES CHAINÉES
Definition
• Les listes chaînées (ou listes simplement chaînées) sont

une structure de données fondamentale en algorithmique,


particulièrement utile lorsque la taille des données n'est
pas fixe ou lorsque des insertions et suppressions
fréquentes sont nécessaires. Contrairement aux tableaux,
qui ont une taille fixe, une liste chaînée est une structure
dynamique où chaque élément (appelé nœud) est relié
au suivant par un pointeur.
Concepts de base d'une liste chaînée :

• Nœud (ou maillon) : Chaque élément d'une liste chaînée est

appelé un nœud. Un nœud contient :


• Une donnée (ou valeur) : l'élément stocké.
• Un pointeur vers le nœud suivant dans la liste.

• Pointeur vers la tête de la liste : Une liste chaînée

commence par un pointeur vers le premier nœud, appelé la


tête de la liste. Si la liste est vide, ce pointeur est NULL.
• Pointeur nul (NULL) : Le dernier nœud de la liste ne pointe

vers aucun autre nœud, donc son pointeur vers le suivant est
NULL, ce qui indique la fin de la liste.
Schéma d'une liste chaînée :

• [tête] --> [nœud1: valeur | pointeur] --> [nœud2: valeur |

pointeur] --> NULL


Structure d'un nœud de la liste chaînée

• Chaque nœud contient deux éléments :

• Valeur : La donnée stockée dans le nœud.

• PointeurVersSuivant : Un lien vers le nœud suivant dans

la liste.
Opérations principales sur une liste chaînée

• Insertion :
• On peut insérer un nouvel élément au début, à la fin ou à une position
donnée dans la liste.
• L'insertion au début est souvent l'opération la plus simple et rapide.

• Suppression :
• On peut supprimer un élément à partir de la tête, à une position donnée, ou
à la fin de la liste.
• Il faut ajuster les pointeurs pour maintenir la chaîne de nœuds.

• Parcours :
• Parcourir une liste chaînée consiste à traverser tous les nœuds un par un
en suivant les pointeurs, jusqu'à atteindre NULL.
• Recherche :
• On peut chercher un élément en parcourant la liste et en comparant les
valeurs de chaque nœud.
Insertion en tête d'une liste chaînée

Procédure insérerEnTête(Liste, valeur)


• NouveauNoeud ← allouer Noeud

• NouveauNoeud.valeur ← valeur

• NouveauNoeud.suivant ← Liste.tete

• Liste.tete ← NouveauNoeud
Insertion à la fin d'une liste chaînée :

• Procédure insérerEnFin(Liste, valeur)

NouveauNoeud ← allouer Noeud


NouveauNoeud.valeur ← valeur
NouveauNoeud.suivant ← NULL
Si Liste.tete = NULL
Liste.tete ← NouveauNoeud Sinon
NoeudCourant ← Liste.tete
Tant que NoeudCourant.suivant ≠ NULL
NoeudCourant ← NoeudCourant.suivant
FinTantQue
NoeudCourant.suivant ← NouveauNoeud
FinSi
Suppression d'un élément en tête
Procédure supprimerEnTête(Liste)
Si Liste.tete ≠ NULL
NoeudTemporaire ← Liste.tete
Liste.tete ← Liste.tete.suivant
libérer NoeudTemporaire
FinSi
Parcours d'une liste chaînée :

• Procédure parcourirListe(Liste)

NoeudCourant ← Liste.tete
Tant que NoeudCourant ≠ NULL
afficher NoeudCourant.valeur
NoeudCourant ← NoeudCourant.suivant
FinTantQue
Avantages d'une liste chaînée :

• Flexibilité : La taille d'une liste chaînée peut changer

dynamiquement, ce qui la rend plus flexible qu'un tableau à


taille fixe.
• Insertion/Suppression rapides : Ajouter ou supprimer un

élément en tête ou à une position donnée (lorsque l'élément


est trouvé) est rapide et ne nécessite pas de déplacement
des autres éléments, contrairement aux tableaux.
• Utilisation efficace de la mémoire : La mémoire n'est

allouée que lorsqu'un nouvel élément est ajouté.


Inconvénients d'une liste chaînée
• Accès séquentiel : Contrairement aux tableaux, où l'accès à

un élément est direct via un indice, accéder à un élément dans


une liste chaînée nécessite de parcourir la liste depuis la tête.
• Espace mémoire supplémentaire : Chaque nœud nécessite

de l'espace supplémentaire pour stocker le pointeur vers le


nœud suivant.
• Temps de parcours : Les opérations comme la recherche d'un

élément prennent plus de temps (linéaire) car il faut parcourir la


liste à partir de la tête.
Variantes de listes chaînées :

• Liste doublement chaînée : Chaque nœud contient deux

pointeurs : un vers le nœud suivant et un autre vers le


nœud précédent. Cela permet de parcourir la liste dans
les deux sens.
• Liste circulaire : Dans une liste circulaire, le dernier

nœud pointe vers le premier, formant un cycle.

Vous aimerez peut-être aussi