Cours ENSG 2A, Septembre 2002 Guillaume Caumon

Slides:



Advertisements
Présentations similaires
La boucle for : init7.c et init71.c
Advertisements

Introduction Langage très répandu Noyau Linux VLC … Des avantages indéniables mais aussi des contraintes ! Ceci nest quun rapide tour.
Rappels C.
Développement logiciel sur micro-contrôleurs PIC en C
Cours n° 7 Standard Template Library II.
Cours n° 6 Standard Template Library I.
Introduction: Concepts de la programmation
Piles, files et listes: notions théoriques
Structures de données et complexité
Sensibilisation à l’Algorithmique
Au programme du jour …. Un peu plus de structures de données
Introduction à l’Algorithmique
Sensibilisation à l’Algorithmique et structure de données
GEF 243B Programmation informatique appliquée
Cours n° 8 Conception et Programmation à Objets
La programmation générique avec la STL EIUMLV - Informatique et Réseaux 99 Benoît ROMAND.
Approfondissement du langage
C.
JAV - TD 6 Structures de données JAVA
FLSI602 Génie Informatique et Réseaux
8. Les tableaux P. Costamagna – ISEN N1.
Structures collectives en Java
Structures de données linéaires
Algorithmique et Programmation
II. Chaînage, SDD séquentielles
Leçon 6 : Structures de données dynamiques IUP 2 Génie Informatique Méthode et Outils pour la Programmation Françoise Greffier.
Bibliothèque standard du C++
8PRO100 Éléments de programmation Allocation dynamique de la mémoire.
La fonction alloue un bloc de taille size. Il faut indiquer la taille du bloc que lon veut allouer. Le premier exemple: #include void main()
II. Chaînage, SDD séquentielles
Les Classes les structures en C (struct) regroupent des variables : structuration de l'analyse mais problèmes de cohérence problèmes de sécurité d'accès.
Standard Template Library (STL)
Structures de données IFT-2000
Les fichiers indexés (Les B-arbres)
Les pointeurs Modes d’adressage de variables. Définition d’un pointeur. Opérateurs de base. Opérations élémentaires. Pointeurs et tableaux. Pointeurs et.
IFT-2000: Structures de données
Structures de données IFT-2000
Structures de données IFT-2000 Abder Alikacem Standard Template library Édition Septembre 2009 Département dinformatique et de génie logiciel.
Présentation Structures de Données et TDA
Points importants de la semaine Les allocations programmées de mémoire. Les enregistrements.
Un langage de programmation hybride
8PRO107 Éléments de programmation
Standard Template Library
Structures de données IFT-10541
Plan cours La notion de pointeur et d’adresse mémoire.
Structures de données IFT-2000
Le langage C Structures de données
2.1 - Historique Chapitre 2 : Introduction au langage C++
Le langage C Rappel Pointeurs & Allocation de mémoire.
Ch. PAUL - Piles et Files à l'aide de listes chainées
LES PILES ET FILES.
Les Pointeurs et les Tableaux Statiques et Tableaux Dynamiques
La notion de type revisitée en POO
ALGORITHMIQUE ET PROGRAMMATION C
La fonction alloue un bloc de taille size. Il faut indiquer la taille du bloc que l’on veut allouer. Le premier exemple: #include void main()
Structures de données élémentaires dans le contexte du TP #1
Arbres binaires et tables de hachage
Chaînage et LSC : motivation et principe Manipuler la LSC : exemples Variantes : LDC, liste circulaire, … Etude de cas : réalisation d’un buffer clavier.
II. Chaînage, SDD séquentielles
ETNA – 1ème année Guillaume Belmas –
1 École des Mines de Saint-Etienne. 158, cours Fauriel Saint-Etienne Cedex 2. Tél Fax Jean-Jacques Girardot
ISBN Chapitre 10 L'implémentation des sous- programmes.
Classe 1 CSI2572 Autres modificateurs de déclaration de variables: & volatile & register & static & auto & extern & const volatile Indique au compilateur.
Conception de Programmes - IUT de Paris - 1ère année Conception de Programmes Objectifs et organisation du cours Introduction à la P.O.O.
Structures de données avancées : Principales structures de fichiers
Chaînage et LSC : motivation et principe Manipuler la LSC : exemples Variantes : LDC, liste circulaire, … Etude de cas : réalisation d’un buffer clavier.
3ième Classe (Mardi, 23 Septembre) CSI2572. O jourd'8: E Allocation de mémoire E Déallocation de mémoire E Tableaux (n dimensions) E Arithmetique des.
1 ALGORITHMIQUE AVANCEE IUT Vélizy – RT2 FA Laurent Marsan.
Informatique 2A Langage C 5ème séance. Déroulement de la séance 5 1 ère partie Étude des chaînes de caractères 2 ème partie Les structures 3.
M. BENJELLOUN : 2005 Le but final est de programmer un jeu où l'ordinateur choisira un nombre aléatoire entre 0 et 100 que vous devez deviner.
Transcription de la présentation:

Cours ENSG 2A, Septembre 2002 Guillaume Caumon Algorithmique Cours ENSG 2A, Septembre 2002 Guillaume Caumon http://www.ensg.inpl-nancy.fr/~caumon/Teach

Introduction Un cours d’algorithmique a Géol… !??? Algorithme = suite d’actions que devra effectuer un automate pour arriver à partir d’un état initial, en un temps fini, à un résultat Ce cours est un approfondissement du cours d’info de C de 1ere année. Pourquoi faire de l’info a Geol ??? Modélisation de plus en plus employée dans l’industrie et la recherche. Elements finis (modélisation d’écoulements, déformations et rupture) Géostatistique et Analyse des données Modélisation géométrique Systèmes experts et recherche opérationnelle 20 % de la promo99 dans l’info… Cette année: TDs et projets 2A de programmation (Cf Christine Fay-Varnier) Eventuellement projet de labo

Plan Mémoire, pointeurs (1h) Organisation d’un programme (1h) Structures de données: listes, arbres, tables... (8h) Algorithmique: exemple des tris (2h)

Partie I La mémoire

Les mémoires... RAM (Random Access Memory): 32 / 64 Mo Le disque dur: quelques Go La mémoire virtuelle: temps d’accès 1000 fois plus long. La mémoire vive est donc limitée: un programme va donc avoir a gérer au mieux la mémoire disponible en fonction des besoins => allocations dynamiques, et usage de pointeurs.

Mémoire et exécution Code objet du programme Valeurs constantes Données statiques Valeurs constantes Pile Piles d’appels de fonctions Pile: Pour chaque fonction empilée, sont stockés : les arguments la valeur de retour l’adresse de retour les variables locales Tas Allocation dynamique de mémoire

Intérêts des pointeurs Gestion de l’espace mémoire en cours d’exécution Modifications de variables passées en paramètres de fonction Représentation de tableaux: accès direct et indexé Fonctions virtuelles: elles font intervenir des pointeurs de fonction, qui seront abordes dans les parcours d’arbres. Références croisées Fonctions virtuelles en programmation objet

Rappels sur les pointeurs int* a; a Déclaration d’un pointeur vers un entier

Rappels sur les pointeurs int* a; int* a = NULL; a Déclaration d’un pointeur vers un entier et initialisation à “NULL”

Rappels sur les pointeurs malloc(3*sizeof(int)); Allocation dynamique de place mémoire (pour 3 entiers)

Rappels sur les pointeurs int* a = malloc(3*sizeof(int)); int* a = (int*)malloc(3*sizeof(int)); a *a *a  a[0] *(a+1)  a[1] etc. int* a1 = &a[1] Allocation dynamique et assignement

Rappels sur les pointeurs free(a); a = NULL; a *a Bilan: Un pointeur est une variable contenant l’adresse d’une zone de mémoire. Il permet de gérer la mémoire “a distance” en allouant/desallouant dynamiquement. Le type d’un pointeur est un raccourci pour parler du type d’élément pointé. En d’autres termes, un pointeur occupe une taille fixe (32 ou 64 bits, en général, la taille d’une adresse), alors que le type détermine la taille de l’élément pointé. Cela permet par exemple d’utiliser des pointeurs non typés (void*), pour référencer des zones de mémoire. Dans ce cas, c’est seulement au moment de l’accès a ces zones mémoire qu’on aura besoin de connaître le type d’élément stocké. Désallocation dynamique

Rappels sur les pointeurs int* a = (int*)malloc(3*sizeof(int)); int* a = (int*)calloc(3, sizeof(int)); Pb de malloc pour les tableaux: pas d’initialisation. -> calloc Coût mémoire de realloc: Alloue un nouveau bloc de mémoire Copie les valeurs de l’ancien bloc dans le nouveau Libère l’ancien bloc Retourne l’adresse du nouveau bloc a = (int*)realloc(4*sizeof(int));

Partie II Survol d’architecture logicielle

Programme ?? Exécutable(s) Librairies et fichiers objets bin Exécutable(s) a.out .exe lib .o .so .lib .dll Librairies et fichiers objets Programme include Fichiers de description (header) .h Description du point de vue du programmeur: Repertoire contenant un ensemble de fichiers src Fichiers d’implantation (source code) .c

But du Jeu Fichiers source = Instructions dans un langage de programmation Application, qui parle directement a l’ordinateur Hiérarchie des langages de programmation

Problèmes a résoudre Complexité, Coût de maintenance taille temps La taille augmente… Corrections de bugs, pardon, de bogues. Changements de spécifications + maintien de la compatibilité avec les anciennes versions Nouvelles fonctionnalités temps

Organisation et réutilisation… Organisation du code : En fonctions, structures, etc. En fichiers Réutilisation : Du code (fichiers source) Des fichiers binaires Réutilisation d’un programme à l’autre

Programmes et librairies En C : Exécutable ↔ main() Pour la réutilisation, on utilise des bibliothèques (ou librairies) de fonctions : d’une description de chaque fonction (dans des fichier “.h” d’en-tête, ou header), et du code compilé correspondant (des “.lib” et “.dll” sous PC, des “.so” et “.a” sous UNIX) Analogie: Bibliothèque = voiture Interface (fichiers d’en-tête)  {pédales d’accélérateur, de frein, d’embrayage, volant, levier de vitesse, etc.} Documentation  code de la route et manuel d’utilisation code objet (librairies binaires (.dll, .so))  moteur, plaquettes et disques de freins, cardans, etc. Exemple de programmes utilisant la bibliothèque: Aller de Nancy à Paris Aller faire des courses Etc.

La Compilation : Résumé Fichiers d’en-tête C Code source C .c .h Préprocesseur Code pré-processé Librairies Compilateur 1) Preprocessing : - Expansion des headers inclus Afin d’éviter les inclusions multiples, une fichier header doit commencer par: #ifndef myfile_h #define myfile_h /* Corps du fichier: déclaration de fonctions, structures, etc */ #endif C’est un bon exemple pour dérouler l’action du preprocesseur. Le preprocesseur effectue aussi des substitutions dans le code #define HUGE 10000000 2) Compilation: Chargée de générer le code machine; définition des symboles déclarés dans les fichiers d’en-tête. Le compilateur n’est pas votre ennemi, au contraire !!! 3) Edition de lien: mise en commun et recherche du code correspondant a tous le symboles Parler des types d’erreurs aux différentes étapes .lib .so Fichier(s) Objet Editeur de liens exécutable .o a.out .exe

Qualité d’un programme Architecture claire Réutilisabilité Structures de données + Algorithmes + Documentation + Tests de robustesse +

Introduction aux structures de données Partie III Introduction aux structures de données

“Comment Organiser au Mieux l’Information Introduction Problème métaphysique: “Comment Organiser au Mieux l’Information dans un Programme ?” Tableaux int tab[10]; Structures de données struct Data_t { int index_; char* value_; } Data_t; Structures Problèmes récurrents: représenter de l’information avec les outils disponibles: variables, pointeurs, fonctions...

Les tableaux Accès indexé (de 0 à n-1 pour un tableau de n éléments) Stockage compact Taille fixe, en général Réajustement de taille coûteux en temps Insertion d’élément onéreuse en temps.

Liste chaînée : Spécifications Créer une liste vide Ajouter un élément (début / fin / milieu) Retirer un élément (début / fin / milieu) Détruire une liste Notre but est d’écrire une librairie de programmation qui pourra être utilise par du code client (n’importe quel programme qui a besoin d’utiliser des listes chaînées). Etapes: Spécifications : que veut-on faire ? Que veut-on fournir au code client ? Définition d’une structure de données adaptée aux spécifications Ecriture des fichiers d’en-tête correspondant A la structure de données choisie Aux spécifications choisies. Ecriture des fichiers d’implantation Compilation et tests Note sur les spécifications : on peut bien entendu effectuer ces opérations sur des tableaux, mais: C’est lourd a implémenter Cela risque de nécessiter beaucoup d’allocations/réallocations, ce qui peut prendre très longtemps pour des listes contenants de très gros objets. On va donc définir une nouvelle structure de données plus rapide pour répondre a ces spécifications. Remarque avancée: en pratique les tableaux sont couramment utilises pour l’implémentation de listes; en général, on utilise des heuristiques pour optimiser la taille des tableaux pour éviter un surdimensionnement aussi bien que de trop nombreuses réallocations. Un autre type de méthode très efficace consiste à utiliser une liste de tableaux pour profiter de la rapidité des tableaux et de la flexibilité des listes. L’implantation de telles structures nous mènerait trop loin, c’est pourquoi nous allons décrire une structure illustrant bien les concepts, quoique non optimale. Trier les éléments d’une liste

Liste chaînée : Structures Noeud Tête Tête Noeud Exemple de structures satisfaisant aux critères. Liste chaînée (en haut) et liste doublement chaînée (en bas)

Liste chaînée : Structures Node_t p_last p_data p_next nb_elements p_first List_t Data_t Un premier pas vers une formulation informatique

Liste chaînée : Header typedef struct List_t { struct Node_t* p_first_; struct Node_t* p_last_; int nb_elements_; } List_t; typedef struct Node_t { struct Data_t* p_data_; struct Node_t* p_next_; } Node_t; « struct » est nécessaire, car le « typdef » n’est pas encore interprété entre les {} illustration de l’intérêt des pointeurs: sans pointeur, on aurait besoin de connaître la taille de l’élément; un pointeur a une taille fixe (taille d’une adresse), c’est la taille de la zone pointée qui est variable. typedef struct Data_t { ... } Data_t;

Liste chaînée : Header List_t* list_create( void ); int list_insert_item( List_t* list, Data_t* item ); int list_append_item( int list_insert_item_before( List_t* list, Data_t* to_insert, Data* list_item

Liste chaînée : Header int list_destroy( List_t* list ); int list_empty( List_t* list ); Data_t* list_remove_head( List_t* list ); Data_t* list_remove_tail( List_t* list ); int list_remove_item( List_t* list Data_t* item ); int list_sort( List_t* list );

Liste chaînée : Utilisation Avant d’aller plus loin, vérifions si nos spécifications sont suffisantes... Pour cela, nous allons écrire un programme qui utilise les fonctions du fichier list.h, sans nous préoccuper de la façon dont elles sont implantées. But du programme: construire et trier une liste d’entiers par ordre croissant. Exemple de fichier main.c: Noter: le passage de paramètre par adresse avec la fonction scanf. les schémas de boucle while(...){...} do{...} while(...) L’allocation dynamique de chaque Data_t est nécessaire (sinon, les valeurs entrées sont perdues) Que se passe-t-il a la fin de la fonction ? Il n’y a aucune desallocation de mémoire !!! #include <list.h> #define TRUE 1 #define FALSE 0 typedef int Bool_t; typedef int Data_t; int main() { int value = 0; Data_t* p_data = NULL; List_t* p_list = list_create(); if( p_list == NULL ) { /* Message d’erreur */ return 1; } printf( “Entrez les valeurs positives; entrez –999 pour terminer\n” ); scanf( “%d”, &value ); while( value != -999 ) { p_data = MALLOC( Data_t ); *p_data = value; if( p_data == NULL ) { /* Erreur */ if( ! list_insert_item( p_list, p_data ) ) { return list_sort( p_list );

Liste chaînée : Implantation Cf. code écrit au tableau; pour résumer les principales règles à suivre: Toujours tester la validité d’un pointeur avant de l’utiliser. S’assurer de ne jamais perdre l’adresse d’une zone allouée dynamiquement. Dans un programme, toute allocation par malloc ou calloc doit être suivie d’une désallocation par free Fichier list.c: #include “list.h” #include “defs.h” /** Dans defs.h : defs de Bool_t, #define MALLOC(T) (T*)malloc( sizeof(T) ), etc. */ void list_error( char* message ) { printf( “List Error: %s\n”, message ); } List_t* list_create( void ) { List_t* p_new_list = MALLOC( List_t ); if( p_new_list == NULL ) { list_error( “Could not allocate new list” ); return NULL; p_new_list->p_first_ = NULL; p_new_list->p_last_ = NULL; p_new_list->nb_elements_ = 0; retrurn p_new_list; int list_insert_item( List_t* p_list, Data_t* p_data ) { if( p_list == NULL || p_data == NULL ) { list_error( “NULL Parameter !” ); return FALSE; p_node = MALLOC( Node_t ); if( p_node == NULL ) { ... } p_node ->p_data_ = p_data; p_node->p_next_ = p_list->p_first_; p_list->p_first_ = p_node; p_list->nb_elements_++; return TRUE; etc.

Liste chaînée : Spécialisations Pile, ou Tas (Stack): structure LIFO void push(Data_t*) Data_t* pop(void) Stockage temporaire d’information, tout en conservant une notion d’ordre. Usages: Parcours de graphes version itérative de la récursion: la pile système est limitée. Evaluation d’expressions parenthésées; exemple simplifié 5 * (3 + 4) push(5) * ( push(3) + push(4) ) => on peut evaluer le contenu des () push( pop() + pop() ) On peut evaluer le * Push( pop() * pop() ) => la pile contient le resultat. printf( “%d”, pop() );

Liste chaînée : Spécialisations File, ou queue : structure FIFO void push(Data_t*) Data_t* pop(void) Principe des files d’attente. Variante: priority queue: tri des éléments au fur et a mesure de l’insertion. Exemple d’application: allocation de ressources en programmation système (multi tache)

Introduction à la complexité Annuaire avec noms et coordonnées t = a · N2 temps t t = a · 2N t = a · N t = a · logN 10 000 abonnes: algorithme en o(n) ~ 10 000 secondes ~ 3 heures recherche d’un nom o( n2) ~ 10^8 secondes ~ 3000 heures : tri par ordre alphabétique bourrin o( 2n ) plusieurs siècles o( Log N ) ~ 4 secondes o( N log N ) ~ 12 heures tri par ordre alphabétique “quick sort” On aimerait bien écrire annuaire[“Guillaume”] = .... Au lieu de annuaire[4762] = ... => tables ----------------------------------------------------------------- Lien avec les listes: insertion d’un élément: o(1) retrait en queue: o(N), lie à l’implantation: avec une liste doublement chaînée, on aurait o(1). => compromis entre place mémoire utilisée et performance. comparaison avec les tableaux. nombre d’abonnés N

Sets ou Bags et tables Stocker une seule fois le même élément dans le conteneur. Pas d’ordre Accès rapide Tables : Associent une clé a un élément dans le conteneur. Fonction de hachage: f: espace des données -- [0, nb_elements-1] Réalisation : on utilise des opérateurs qui garantissent l’unicité du mapping clef / index. Besoin de fonctions de hachage

Structures de données linéaires Taille fixe Accès direct Tableaux Taille variable Accès séquentiel Listes chaînées Unicité des éléments Accès rapide Sets, Bags Associe une clé unique et une valeur. Accès rapide Tables

Structures de données hiérarchiques: les Arbres Racine B1 B2 B3 B7 B6 B5 B4 F2 F1 Defs: Racine : noeud sans parent Branche : noeud avec parent et enfants Feuilles : noeud sans enfants Types: binaires, ternaires, non structurés Applications: - arbre généalogiques - intelligence artificielle, - compression de données - Systèmes de fichiers, - Structures de données géométriques (octrees, etc.) et jeux Principalement utilise dans tous les algorithmes “diviser pour régner”: Recherche dans un arbre équilibré : O( log n ) B8 B10 B9 F4 F5 F3 F7 F8 F6 F10 F9

Arbres: Spécifications Créer un arbre Parcours pre-order Parcours post-order Parcours in-order Ajout / retrait d’un noeud Détruire un arbre

Arbres: Structure de données TreeNode_t p_parent p_first_child p_data p_next Structure proposée très générale: nombre variable d’enfants. Optimisations possibles pour des arbres binaires ou ternaires, par ex: pas besoin de p_next: Exemple, pour un arbre binaire: typedef struct TreeNode_t { struct TreeNode_t* p_parent_; struct TreeNode_t*p_children_[2]; // tableau de 2 pointeurs vers des TreeNode_t Data_t* p_data_; } TreeNode_t; Data_t

Tree.h typedef struct TreeNode_t { struct TreeNode_t* p_parent_; struct TreeNode_t* p_first_child_; Data_t* p_data_; struct TreeNode_t* p_next_; } TreeNode_t; TreeNode_t* tree_add_node( TreeNode_t* p_parent, Data_t* p_data ); Fichier tree.c #include “tree.h” TreeNode_t* tree_add_node( TreeNode* p_parent, Data_t* p_data ) { TreeNode_t* p_new_node = NULL; TreeNode_t* p_prev = NULL; if( p_data == NULL ) { return NULL; } p_new_node = MALLOC( TreeNode_t ); if( p_new_node == NULL ) { return NULL; } p_new_node->p_parent_ = parent; p_new_node->p_first_child_ = NULL; p_new_node->p_data_ = p_data; if( parent != NULL ) { #ifdef INSERT_ITEM /* First strategy: insert the node in the list of children */ p_new_node->p_next_ = p_parent ->p_first_child_; p_parent -> p_first_child_ = p_node; } #endif #ifdef APPEND_ITEM /* Second strategy: append the node to the list of children */ p_new_node->p_next = NULL; p_prev = p_parent->p_first_child_; if( p_prev == NULL ) { p_parent->p_first_child_ = p_new_node; } else { while( p_prev->p_next_ != NULL ) { p_prev = p_prev->p_next_; p_prev->p_next_ = p_new_node; return p_new_node;

Tree.h TreeNode_t* tree_find_root( TreeNode_t* p_parent, Data_t* p_data ); void tree_preorder( TreeNode_t* p_root, void(* do_it)( Data_t* ) ); TreeNode_t* tree_find_root( TreeNode_t* p_node ) { TreeNode_t* p_parent = p_node; if( p_parent == NULL ) { return NULL; } while( p_parent->p_parent_ != NULL ) { p_parent = p_parent->p_parent_; } void tree_postorder( TreeNode_t* p_root, void(* do_it)( Data_t* ) );

Tree.h void tree_inorder( TreeNode_t* p_root, void(* do_it)( Data_t* ) ); TreeNode_t* tree_delete_branch( TreeNode_t* branch );

Arbres: parcours pre-order 1 2 5 3 4 6 10 Principe: depuis la racine, descendre la hiérarchie quand on arrive sur une feuille, on remonte jusqu’ à une branche non visitée auparavant. void tree_preorder( TreeNode_t* p_root, void(* do_it)( Data_t* ) ) { TreeNode_t* p_node = NULL; if( p_root == NULL ) { /* ... */ } (* do_it)( p_root->p_data_ ); p_node = root->p_first_child_; while( p_node != NULL ) { Tree_preorder( p_node, do_it ); p_node = p_node ->p_next_; } return; 7 8 9 Pointeurs de fonctions...

Arbres: parcours post-order 10 3 9 1 2 7 8 Principe: On opère d’abord sur les feuilles puis sur les branches. void tree_postorder( TreeNode_t* p_root, void (* do_it)( Data_t* ) ) { TreeNode_t* p_node = NULL; if( p_root == NULL ) { return; } p_node = p_root->p_first_child_; while( p_node != NULL ) { tree_postorder( p_node, do_it ); p_node = p_node->p_next_; } (*do_it)( p_root->p_data_ ); return; 4 5 6

Arbres: parcours in-order 4 2 9 1 3 6 10 Parcours de gauche à droite: Sur la position courante: l’enfant le plus à gauche a la priorité puis la position courante puis les autres enfants void tree_inorder( TreeNode_t* p_root, void (* do_it)( Data_t* ) ) { TreeNode_t* p_node = NULL; if( p_root == NULL ) { return; } /* p_root is a leaf */ if( p_root_->p_first_child_ == NULL ) { (*do_it)( p_root->p_data_ ); return; } tree_inorder( p_root->p_first_child_, do_it ); (*do_it) ( p_root->p_data_ ); p_node = root -> p_first_child_->p_next_; while( p_node != NULL ) { tree_inorder( p_node, do_it ); p_node = p_node->p_next_; 5 7 8

Structures de données complexes: Les Graphes Types et définitions : ouvert, fermé, connexe, non connexe, Cyclique/acyclique orienté / non orienté valués / non valués Applications : Réseaux routiers/ferroviaires, aérien, Ordonnancement de taches en Recherche opérationnelle problèmes de flux (hydraulique, files d’attente, etc.) Opérations: chemin le plus court fermeture transitive tri topologique flux maximum ... N15 N16 N17 N18

Algorithmes et complexité : Partie IV Algorithmes et complexité : exemple des tris

Exemple : Algorithmes de tri Applications: bases de données géométrie algorithmique .... Tri d’un tableau de taille n : n! possibilités Fonctions: de comparaison “<“ d’echange Etude de diverses méthodes de tri: But: quantifier la quantité d’appels aux 2 fonctions de base, et en déduire la complexité de l’algorithme.

Tri par remplacement Besoins Algo min_index, max_value, comp tableaux entree, sortie int max = max_value(entree) Pour i de 1 à n Faire: int j <- min_index(entree) sortie[i] <- entree[j] entree[j] <- max FinPour Algo /* Returns the highest value in the array ‘data’ */ int get_max_value( int* data, int nb_elements ) { int i = 0; int max_value = data[0]; for( i = 1; i < nb_elements; i++ ) { if( data[i] > max_value ) { max_value = data[i]; } return max_value; /* Returns the index of the lowest value in the array ‘data’ */ int get_min_id(int* data, int nb_elements ) { int min_i = 0; int min_value = data[0]; if( data[i] < min_value ) { min_value = data[i]; min_i = i; return min_i; int* sort( int* data, int nb_elements ) { int j = 0; int* sorted_data = CALLOC( int, nb_elements ); int max_value = get_max_value( data, nb_elements ); for( i = 0; i < nb_elements – 1; i++ ) { j = get_min_id( data ) sorted_data[i] = data[j]; data[j] = max_value; sorted_data[nb_elements-1] = max_value; return sorted_data; Complexité en temps : o(n(n-1)) ~ o(n2) Mémoire: duplication du tableau.

Tri par permutation Besoins Algo min_index, swap, comp Tableau entrée Pour i de 1 à n Faire: int j <- min_index(entree,i) Si j ≠ i Faire swap(entree[i],entree[j]) FinSi FinPour Algo - min_index cherche uniquement dans la partie non triée du tableau - on va trier le tableau directement: coût mémoire plus faible Optimisation du tri précèdent: /** * Returns the index of the lowest value in the array ‘data’, between * items ‘top’ to ‘nb_element – 1’ */ int get_min_id(int* data, int top, int nb_elements ) { int i = 0; int min_i = 0; int min_value = data[top]; for( i = top; i < nb_elements; i++ ) { if( data[i] < min_value ) { min_value = data[i]; min_i = i; } return min_i; void sort( int* data, int dimension ) { j = get_min_id( data, i ); // search only from i to n for( i=0; i<dimension; i++ ) { int j = 0; if( j != i ) { swap( v[i], v[j] ) } Complexité en temps : o(n(n-1)/2) ~ o(n2) Mémoire: tableau non dupliqué

Tri à bulles: Principe Echange de deux éléments adjacents du tableau. Besoins swap, comp Fonction de comparaison: peut être paramétrable; exemple: tri d’un annuaire: par entreprise par nom par numéro de téléphone par adresse

Tri à bulles: Algo Tableau tab Booleen permute <- vrai int i <- 0 Tant que permute Faire permute = faux Pour j de n-1 à i + 1 Faire Si tab[j-1] > tab[j] Faire swap( tab[j-1], tab[j] ) permute = vrai FinSi FinPour i++ Finttque void sort( int* data, int nb_elements ) { Bool_t permutated = TRUE; int j = 0; int i = 0; for( j = nb_elements; j > i; j-- ) { permutated = FALSE; while( permutated ) { permutated = true; swap( v[j-1], v[j] ); if( data[j-1] > data[j] ) { } i += 1;

Tri à bulles: Commentaires Complexité en temps : o(n(n-1)/2) ~ o(n2) Mémoire: tableau non dupliqué

Tri par sélection 4que On sépare le tableau en p ensembles. On cherche un minimum pour chaque sous-ensemble On prend le minimum de ces minima. On échange cet élément avec le premier élément etc. trié sous-table 1 sous-table p ... n ( p + n/p ) tests o( n n ) pour p = n

Tri par segmentation (quicksort) Méthode “diviser pour régner” : On recherche une valeur pivot Tj. On échange les valeurs de sorte que : tout élément de [T0,Tj-1] soit inférieur a Tj, tout élément de [Tj+1, Tn] soit supérieur a Tj On pivote récursivement sur [T0,Tj-1] et [Tj+1, Tn].

Tri par segmentation (quicksort) 3 1 4 6 3 2 9 5 7 1 8 2 seg (0,11) 3 1 2 6 3 2 9 5 7 1 8 4 seg (0,4); seg (4,11) seg (0,2); seg(3,4); seg(5,11) seg (0,11) seg (0,4); seg (5,11) seg(5,10) seg(6,10) seg(7,10) 3 1 2 1 3 2 9 5 7 6 8 4 2 1 2 1 3 3 9 5 7 6 8 4 2 1 2 1 3 1 1 2 2 3 3 9 5 7 6 8 4 3 9 5 7 6 8 4 4 5 7 6 8 9 1 1 2 2 3 9 4 5 7 6 8 // splits the array in two parts around an arbitrary value (pivot) // the value is the min index value // Best results if the pivot is the median value of the array. int segmentation( int* data, int inf, int sup ) { int pivot = data[inf]; int i = inf+1; int j = sup; while( i <= j ) { if( data[i] <= pivot ) { i++; } else { while( data[j] > pivot ) { j--; } if( i < j ) { swap( data[i], data[j] ); swap( data[inf], data[j] ); return j; void quick_sort( int* data, int inf, int sup ) { if( inf < sup ) { int place = segmentation( v, inf, sup ); quick_sort( v, inf, place-1 ); quick_sort( v, place, sup ); 4 5 7 6 8 7 6 8 6 7 8 5

Tri par segmentation (quicksort) Complexité dans le cas favorable : log2(n) nombre de segmentations 0 permutations o(n log2 n) comparaisons Complexité dans le cas défavorable : n nombre de segmentations o(n2) permutations o(n2) comparaisons

Conclusion Fonctionnement d’un programme et d’un ordinateur Programmation en C Algorithmique et structures de données sont liées. Les exemples étudiés ont été implantés en C, mais le langage est plus un outil qu’une fin en soi. Les algorithmes ne sont qu’une partie des concepts impliques dans un programme, par ex: la structure, l’interface, le design avec des langages plus évolués, la documentation, etc. Bon courage pour vos projets !

References Web Aho et al. Structures de donnees et algorithmes, Addisson-Wesley / InterEditions. 1989. Aho et Ullman. Concepts fondamentaux de l’informatique, Dunod. 1993. Sedgewick. Algorithmes en C. Addisson-Wesley / InterEditions. 1991.

Annexe: Pointeurs de fonction But : paramétrer des fonctions par d’autres fonctions pour modifier leur actions. Déclaration : type (* nom_de_fonction) ([arguments]); Utilisation : (* nom_de_fonction) (arg1, arg2, arg3,...); Exemple: liste chaînée d’éléments: on peut définir une fonction générique qui parcours la liste est appelle une fonction sur chacun des éléments, par exemple: imprime le contenu du noeud a l’écran (sortie std) imprime le contenu du noeud dans un fichier imprime le contenu du noeud, de son prédécesseur et de son successeur etc.

Annexe: Pointeurs de fonction short tab[10] short carre( short v ) { return a * a; } void imprimer( int nb_elems, short (* function )( short ) ) { for( i = 0; i < nb_elems; ++i ) { printf( “%d ”, (* function) ( tab[i] ) ); } int main() { for( i = 0; i < 10; i++ ) { tab[i] = n; imprimer( 10, carre ); Retour aux arbres