Diviser pour Régner : Complexité et Tri Fusion

Documents pareils
# let rec concat l1 l2 = match l1 with [] -> l2 x::l 1 -> x::(concat l 1 l2);; val concat : a list -> a list -> a list = <fun>

Complexité. Licence Informatique - Semestre 2 - Algorithmique et Programmation

ALGORITHMIQUE II NOTION DE COMPLEXITE. SMI AlgoII

1 de 46. Algorithmique. Trouver et Trier. Florent Hivert. Mél : Florent.Hivert@lri.fr Page personnelle : hivert

Chapitre 7. Récurrences

1 Recherche en table par balayage

Algorithmique et Programmation

Résolution d équations non linéaires

MIS 102 Initiation à l Informatique

Programmation linéaire

Quelques Algorithmes simples

Suites numériques 3. 1 Convergence et limite d une suite

ARBRES BINAIRES DE RECHERCHE

Recherche dans un tableau

Première partie. Préliminaires : noyaux itérés. MPSI B 6 juin 2015

Chapitre VI - Méthodes de factorisation

Souad EL Bernoussi. Groupe d Analyse Numérique et Optimisation Rabat http ://

Quelques tests de primalité

Algorithmes récursifs

Licence Sciences et Technologies Examen janvier 2010

Les deux points les plus proches

Initiation à l algorithmique

Cryptographie RSA. Introduction Opérations Attaques. Cryptographie RSA NGUYEN Tuong Lan - LIU Yi 1

Exercices - Polynômes : corrigé. Opérations sur les polynômes

Les arbres binaires de recherche

Optimisation non linéaire Irène Charon, Olivier Hudry École nationale supérieure des télécommunications

Rappels sur les suites - Algorithme

Corrigé des TD 1 à 5

Vérification de programmes et de preuves Première partie. décrire des algorithmes

ÉPREUVE COMMUNE DE TIPE Partie D

Exercices - Fonctions de plusieurs variables : corrigé. Pour commencer

Simulation de variables aléatoires

Algorithmique et Programmation, IMA

Le théorème de Perron-Frobenius, les chaines de Markov et un célèbre moteur de recherche

Programmation C++ (débutant)/instructions for, while et do...while

Exercices types Algorithmique et simulation numérique Oral Mathématiques et algorithmique Banque PT

Image d un intervalle par une fonction continue

La fonction exponentielle

Résolution de systèmes linéaires par des méthodes directes

La NP-complétude. Johanne Cohen. PRISM/CNRS, Versailles, France.

Leçon 01 Exercices d'entraînement

Exo7. Matrice d une application linéaire. Corrections d Arnaud Bodin.

Cours de C++ François Laroussinie. 2 novembre Dept. d Informatique, ENS de Cachan

1/24. I passer d un problème exprimé en français à la réalisation d un. I expressions arithmétiques. I structures de contrôle (tests, boucles)

CCP PSI Mathématiques 1 : un corrigé

Chapitre 3. Quelques fonctions usuelles. 1 Fonctions logarithme et exponentielle. 1.1 La fonction logarithme

Cryptographie et fonctions à sens unique

Rappel. Analyse de Données Structurées - Cours 12. Un langage avec des déclaration locales. Exemple d'un programme

INITIATION AU LANGAGE C SUR PIC DE MICROSHIP

Université Paris-Dauphine DUMI2E 1ère année, Applications

Suites numériques 4. 1 Autres recettes pour calculer les limites


Commun à tous les candidats

Optimisation Discrète

1. Structure d un programme C. 2. Commentaire: /*..texte */ On utilise aussi le commentaire du C++ qui est valable pour C: 3.

Introduction à l étude des Corps Finis

Plus courts chemins, programmation dynamique

UEO11 COURS/TD 1. nombres entiers et réels codés en mémoire centrale. Caractères alphabétiques et caractères spéciaux.

Polynômes à plusieurs variables. Résultant

I. Ensemble de définition d'une fonction

Objectifs du cours d aujourd hui. Informatique II : Cours d introduction à l informatique et à la programmation objet. Complexité d un problème (2)

4. Les structures de données statiques

Conventions d écriture et outils de mise au point

Théorèmes de Point Fixe et Applications 1

Journées Télécom-UPS «Le numérique pour tous» David A. Madore. 29 mai 2015

Initiation à la programmation en Python

Groupe symétrique. Chapitre II. 1 Définitions et généralités

Programmation Par Contraintes

Feuille TD n 1 Exercices d algorithmique éléments de correction

Quelques algorithmes simples dont l analyse n est pas si simple

Algorithmique, Structures de données et langage C

Architecture des Systèmes d Information Architecture des Systèmes d Information

IN Cours 1. 1 Informatique, calculateurs. 2 Un premier programme en C

CHAPITRE V. Recherche et tri

Chapitre 2. Eléments pour comprendre un énoncé

Chapitre 6. Fonction réelle d une variable réelle

Grandes lignes ASTRÉE. Logiciels critiques. Outils de certification classiques. Inspection manuelle. Definition. Test

Une dérivation du paradigme de réécriture de multiensembles pour l'architecture de processeur graphique GPU

Les algorithmes de base du graphisme

Chapitre 2 Le problème de l unicité des solutions

Chapitre 5 : Flot maximal dans un graphe

* très facile ** facile *** difficulté moyenne **** difficile ***** très difficile I : Incontournable T : pour travailler et mémoriser le cours

Programmation Linéaire - Cours 1

Logiciel Libre Cours 3 Fondements: Génie Logiciel

Algorithmes de recherche

Cours Informatique Master STEP

Découverte de Python

3 Approximation de solutions d équations

CHAPITRE 5. Stratégies Mixtes

Cours 1 : La compilation

LES MÉTHODES DE POINT INTÉRIEUR 1

introduction Chapitre 5 Récursivité Exemples mathématiques Fonction factorielle ø est un arbre (vide) Images récursives

Table des matières. Introduction

Théorie des Graphes Cours 3: Forêts et Arbres II / Modélisation

LES TYPES DE DONNÉES DU LANGAGE PASCAL

Plan du cours. Historique du langage Nouveautés de Java 7

Algorithmique I. Algorithmique I p.1/??

Une introduction rapide à Coq

TP3 : Manipulation et implantation de systèmes de fichiers 1

Transcription:

Diviser pour Régner : Complexité et Tri Fusion 1 Notion de Complexité Nous allons étudier la complexité des algorithmes étudiés. Il s agit, en général, d estimer le nombre d opérations «élémentaires» (dont la définition peut varier selon le problème) en fonction de la taille de l entrée de l algorithme. Nous nous restreignons à la complexité au pire cas, c est-à-dire que nous voulons une borne supérieure du nombre d opérations. D autres analyses de complexité existent, par exemple la complexité en moyenne sur des entrées aléatoires. Par exemple, pour étudier la complexité d un algorithme de tri sur des listes, nous nous intéresserons au nombre d appels récursifs de la fonction de tri, ou au nombre de comparaisons effectuées (qui peuvent être coûteuses selon ce que contient la liste). 1.1 Notations Étant donné deux fonctions f et g dans N ou R, nous dirons que f(x) = O(g(x)) (x ) s il existe M, N tels que x > M, f(x) N g(x) (notation de Landau 1 ). Intuitivement, cela signifie que f ne croît pas plus vite que g, on dira que g domine f. On écrira f(x) = Θ(g(x)) s il existe M, N 1 et N tels que x > M, N 1 g(x) f(x) N g(x). Nous écrirons le plus souvent O(n) avec n la taille de l entrée car le plus souvent il suffit de se placer dans N. En général, si une fonction f est la somme de f 1,..., f n alors la complexité de f est le maximum des complexités de f 1,..., f n. La notion de taille d entrée sera le plus souvent le nombre d éléments pour une liste, un tableau ou une matrice, ou le nombre de bits pour un entier (i.e., log (n)). Un certain nombre de fonctions apparaissent souvent lors des études de complexité, et forment une hiérarchie (il va de soi qu une complexité plus faible est préférable) : 1. Le O signifie "ordre" de la fonction. 1

1. Exemples Simples Notation Complexité... O(1) constante (pour toute entrée) O(ln(n)) logarithmique O(n) linéaire O(n ln(n)) quasi-linéaire O(n ) quadratique O(n c )(c > 1) polynômiale O(e n ) exponentielle Étudions la complexité au pire cas de quelques algorithmes que nous avons déjà rencontrés. Recherche dans une liste Pour chercher un élément dans une liste (ou un tableau), le plus simple est de la traverser récursivement. Étudions la fonction mem : int -> int list -> bool qui renvoie true ssi l élément concerné est dans la liste : let rec mem x l = match l with [] -> false y :: _ when x=y -> true _ :: l -> mem x l Cette fonction est de complexité O(n) au pire cas (avec n la longueur de la liste, et en considérant le nombre d appels récursifs ou le nombre de tests d égalité). Cela se démontre facilement, car si on cherche un élément dans une liste à laquelle il n appartient pas, il faut la traverser en entier (n appels récursifs donc). Tri par Insertion Le tri par insertion est un algorithme de tri sur les listes très simple. Il s agit de parcourir une liste non triée, en ajoutant chaque élément dans une seconde liste que l on maintient triée. let rec insert x l = match l with [] -> [x] y :: _ when x <= y -> x :: l y :: l -> y :: insert x l let tri_ insertion l = let rec aux acc l = match l with [] -> acc x :: l -> aux ( insert x acc ) l in aux [] l

(* ou, de maniere equivalente : *) let tri_ insertion l = fold_left ( fun acc x -> insert x acc ) [] l L analyse se fait en deux temps. D abord, remarquons que la fonction insert peut prendre n opérations pour insérer un élément x dans une liste l de longueur n (si y l, x > y). Le pire cas pour notre implémentation est lorsque la liste à trier est déjà triée ; en effet, la complexité est O(n ) dans ce cas. Montrons par récurrence sur n qu une liste l n = [1; ;... ; n] requiert n 1 k=0 k appels récursifs. Le cas de base est trivial ; supposons que ce soit le cas pour l n et montrons-le pour l n+1. Par définition le tri des n premiers éléments est similaire, on se retrouve avec un accumulateur qui contient n éléments triés, dans lequel on va insérer l élément n + 1. Cela demande n + 1 appels récursifs par la remarque précédente sur la fonction insert. Par conséquent le nombre total d appels est n 1 k=0 k + n = n la complexité est bien quadratique. Diviser pour Régner k=0 k. Puisque n 1 k=0 k = n(n 1) = Θ(n ), La complexité des algorithmes précédents peut être améliorée dans certains cas en utilisant le principe dit diviser pour régner. Le principe de base est de diviser un gros problème en un ou plusieurs plusieurs sous-problème de taille plus réduite qu on peut traiter indépendamment de la même manière, récursivement. Une seconde phase où les résultats de ces traitements récursifs sont collectés peut être nécessaire. La clé de cette technique repose sur cette subdivision récursive en problèmes plus petits. Un exemple très simple consiste à deviner le nombre, compris entre 0 et 100, qu un ami a en tête, par une suite de comparaisons. Il est logique de commencer par comparer à 50, puis à 5 ou 75 selon que le nombre est plus petit ou plus grand que 50, et ainsi de suite. Nous allons nous pencher sur deux algorithmes très classiques qui relèvent de ce paradigme : la recherche par dichotomie (dans un tableau) et le tri-fusion. D autres algorithmes, tels que la multiplication de Karatsuba, l exponentiation rapide ou la recherche de plus proches points dans le plan, relèvent aussi de cette technique..1 Rechercher Dichotomique dans un Tableau Nous avons vu que la recherche d un élément dans une ĺiste était de complexité linéaire. Il en est de même pour un tableau ; dans le cas où ce tableau est trié on peut toutefois faire mieux. Le problème à résoudre, plus précisément, consiste à rechercher un élément x dans un tableau v de longueur n, trié par ordre croissant, et, s il est présent, de retourner son indice dans le. en anglais, divide and conquer. On remarquera l impérialisme expansionniste de la perfide Albion. 3

tableau. Voici la version naïve et la version par dichotomie, toutes deux de type a vect -> a -> int option (elles retournent None si l élément n est pas présent dans le tableau) : let search_ naive v x = let rec aux i = if i = vect_ length v then None else if v.( i) = x then Some i else aux (i +1) in aux 0 let search_ good v x = let i = ref 0 and j = ref ( vect_ length v - 1) in let result = ref None in while!i <=!j &&! result = None do let middle = (!i +!j) / in if v.( middle ) = x then result := Some middle else if v.( middle ) < x then i := middle + 1 else j := middle - 1 done ;! result Analysons maintenant le second algorithme. Il s agit de rechercher, dans le tableau v, la valeur x. Cependant, nous ne cherchons x que dans l intervalle des indices [i, i + 1,..., j] (avec i j comme l indique la condition de boucle). Intuitivement, nous procédons de la même manière que pour chercher un nom dans un annuaire. À chaque itération, nous comparons v.[ i+j ] et x (rappelons que. désigne la partie entière) : si x = v.[ i+j ] l algorithme s arrête avec succès ; si x < v.[ i+j + j ] alors x ne peut être que dans la partie de v entre i et i 1 ; sinon x, s il est présent dans v, doit être entre i + j + 1 et j.. Cet algorithme termine car à chaque itération qui ne termine pas directement (c est-à-dire en excluant le cas où on trouve x), l intervalle j i décroit strictement (et reste positif car i j est obligatoire pour continuer à boucler). Notons T (n) le nombre d itérations de l algorithme sur une entrée de taille n au pire cas. On a : { T ( n T (n) = ) + 1 si n 1 sinon 4

On peut donc dominer la complexité sur une entrée de taille n par celle sur une entrée de taille log(n). En ce ramenant à ce dernier cas (tableau dont la taille est une puissance de ), on prouve par récurrence que la complexité de cet algorithme de O(ln(n)) (plus précisément O(log (n)), ce qui est équivalent). En effet, la suite (T ( n )) n N est trivialement égale à la suite 1,, 3,... n par récurrence, donc T ( n ) = O(n), et donc T (n) = O(log (n)) pour n puissance de. Il existe des théorèmes plus puissants permettant d analyser facilement la complexité des algorithmes diviser pour régner.. Tri Fusion Le tri fusion est un tri optimal sur les listes, de complexité O(n ln(n)). Il s agit de décomposer une liste en deux sous-listes chacune deux fois plus petites, de les trier séparément, puis de fusionner les résultats en une liste triée. Commençons par l opération de fusion. La fonction merge prend en argument deux listes triées et les fusionne en une liste triée, sans enlever les doublons (la longueur du résultat est donc la somme des longueurs des entrées). Cette fonction termine car au moins un de ses arguments décroit strictement, et l autre décroit ou reste identique 3. On peut également démontrer par induction que si l1 et l sont triées, alors merge l1 l est également triée et contient tous les éléments de l1 et l avec la bonne multiplicité. let rec merge l1 l = match l1, l with [], _ -> l _, [] -> l1 x::l1, y::l -> if x < y then x :: merge l1 l else if x > y then y :: merge l1 l else x :: y :: merge l1 l Il nous faut ensuite une fonction split qui décompose une liste en deux listes de longueurs à peu près égales 4. Plusieurs techniques fonctionnent, mais nous allons en choisir une qui choisit alternativement un élément sur deux pour chaque liste. Cette fonction va diviser le problème en sous-problèmes deux fois plus petits. let split l = let rec split_ aux acc1 acc l = match l with [] -> acc1, acc x::l -> split_aux acc (x:: acc1 ) l in split_aux [] [] l Enfin, la fonction principale, merge_sort, trie trivialement les listes de taille 0 ou 1, et traite les autres cas par récursion : 3. On peut utiliser l ordre sur les multi-ensembles de listes, pour obtenir rigoureusement un ordre bien fondé sur les arguments ; le multi-ensemble {l 1, l } décroit strictement à chaque appel récursif. 4. Si la liste est de longueur impaire, une des sous-listes doit être plus petite que l autre. 5

let rec merge_ sort l = match l with [] -> [] [x] -> [x] _ -> let l1, l = split l in let l1 = merge_ sort l1 in let l = merge_ sort l in merge l1 l Avec les mêmes notations, après avoir facilement prouvé que la complexité de merge et split était O(n), on exprime la complexité au pire cas de merge_sort par { T ( n T (n) = ) + n si n 1 sinon Pour les puissances de, on a T ( n ) = T ( n 1 ) + n et on va montrer par récurrence sur n que T ( n ) = n n+1 : T ( n ) = n + T ( n 1 ) = n + (n 1) n = n + (n 1) n+1 = n+1 + (n 1) n+1 = n n+1 Par conséquent, T ( n ) = log ( n ) n+1, et en majorant T (n) par T ( log(n) ) on obtient T (n) = Θ( (log (n)) log(n) +1 ) = O(n ln(n)) (tous les logarithmes étant équivalents en complexité, à constante multiplicative près). Cet algorithme est donc plus efficace asymptotiquement que le tri par insertion qui est quadratique au pire cas. 6