Previous Next Contents

Introduction

En 30 ans, l'informatique est devenue une industrie importante: 10% des investissements hors bâtiments des sociétés françaises. Au recensement de 1982, 50000 cadres techniciens se déclaraient informaticiens, 150000 en 1991. Une certaine manière de pensée et de communication a découlé de cette rapide évolution. L'informatique apparaît comme une discipline scientifique introduisant des problèmes nouveaux ou faisant revivre d'autres. Certains pensent que l'informatique est la partie constructive des mathématiques, puisqu'on ne s'y contente pas de théorèmes d'existence, mais du calcul des objets. D'autres y voient les mathématiques concrètes, puisque le domaine a l'exactitude et la rigueur des mathématiques, et que la confrontation des idées abstraites à la réalisation de programmes interdit le raisonnement approximatif. Une autre communauté est intéressée surtout par la partie physique du domaine, car une bonne part des progrès de l'informatique est due au développement foudroyant de la micro-électronique.

La jeunesse de l'informatique permet à certains de nier son aspect scientifique: ``les ordinateurs ne sont que des outils pour faire des calculs ou des machines traitement de texte''. Malheureusement, beaucoup de ``fous de la programmation'' étayent l'argument précédent en ignorant toute considération théorique qui puisse les aider dans leurs constructions souvent très habiles. Regardons la définition du mot hacker fournie dans The New Hacker's Dictionary [40], dictionnaire relu et corrigé électroniquement par une bonne partie de la communauté informatique.

hacker [originally, someone who makes furniture with an axe] n. 1. A person who enjoys exploring the details of programmable systems and how to stretch their capabilities, as opposed to most users, who prefer to learn only the minimum necessary. 2. One who programs enthusiastically (even obsessively) or who enjoys programming rather than just theorizing about programming. 3. A person capable of appreciating hack value. 4. A person who is good at programming quickly. 5. An expert at a particular program, or one who frequently does work using it or on it; as in ``a Unix hacker''. 6. An expert or enthusiast of any kind. One might be an astronomy hacker, for example. 7. One who enjoys the intellectual challenge of creatively overcoming or circumventing limitations. 8. [deprecated] A malicious meddler who tries to discover sensitive information by poking around. Hence password hacker, network hacker. See cracker.
Hacker est un mot courant pour désigner un programmeur passionné. On peut constater toutes les connotations contradictoires recouvertes par ce mot. La définition la plus infamante est ``2. One who ... enjoys programming rather than just theorizing about programming.''. Le point de vue que nous défendrons dans ce cours sera quelque peu différent. Il existe une théorie informatique (ce que ne contredit pas la définition précédente) et nous essaierons de démontrer qu'elle peut aussi se révéler utile. De manière assez extraordinaire, peu de disciplines offrent la possibilité de passer des connaissances théoriques à l'application pratique aussi rapidement. Avec les systèmes informatiques modernes, toute personne qui a l'idée d'un algorithme peut s'asseoir derrière un terminal, et sans passer par une lourde expérimentation peut mettre en pratique son idée. Bien sûr, l'apprentissage d'une certaine gymnastique est nécessaire au début, et la construction de gros systèmes informatiques est bien plus longue, mais il s'agit alors d'installer un nouveau service, et non pas d'expérimenter une idée. En informatique, théorie et pratique sont très proches.





D'abord, il n'existe pas une seule théorie de l'informatique, mais plusieurs théories imbriquées: logique et calculabilité, algorithmique et analyse d'algorithmes, conception et sémantique des langages de programmation, bases de données, principes des systèmes d'exploitation, architectures des ordinateurs et évaluation de leurs performances, réseaux et protocoles, langages formels et compilation, codes et cryptographie, apprentissage et zero-knowledge algorithms, calcul formel, démonstration automatique, conception et vérification de circuits, vérification et validation de programmes, temps réel et logiques temporelles, traitement d'images et vision, synthèse d'image, robotique, ...





Chacune des approches précédentes a ses problèmes ouverts, certains sont très célèbres. Un des plus connus est de savoir si P = NP, c'est-à-dire si tous les algorithmes déterministes en temps polynomial sont équivalents aux algorithmes non déterministes polynomiaux. Plus concrètement, peut-on trouver une solution polynomiale au problème du voyageur de commerce. (Celui-ci a un ensemble de villes à visiter et il doit organiser sa tournée pour qu'elle ait un trajet minimal en kilomètres). Un autre est de trouver une sémantique aux langages pour la programmation objet. Cette technique de programmation incrémentale est particulièrement utile dans les gros programmes graphiques. A ce jour, on cherche encore un langage de programmation objet dont la sémantique soit bien claire et qui permette de programmer proprement. Pour résumer, en informatique, il y a des problèmes ouverts.





Quelques principes généraux peuvent néanmoins se dégager. D'abord, en informatique, il est très rare qu'il y ait une solution unique à un problème donné. Tout le monde a sa version d'un programme, contrairement aux mathématiques où une solution s'impose relativement facilement. Un programme ou un algorithme se trouve très souvent par raffinements successifs. On démarre d'un algorithme abstrait et on évolue lentement par optimisations successives vers une solution détaillée et pouvant s'exécuter sur une machine. C'est cette diversité qui donne par exemple la complexité de la correction d'une composition ou d'un projet en informatique (à l'Ecole Polytechnique par exemple). C'est aussi cette particularité qui fait qu'il y a une grande diversité entre les programmeurs. En informatique, la solution unique à un problème n'existe pas.





Ensuite, les systèmes informatiques représentent une incroyable construction, une cathédrale des temps modernes. Jamais une discipline n'a si rapidement fait un tel empilement de travaux. Si on essaie de réaliser toutes les opérations nécessaires pour déplacer un curseur sur un écran avec une souris, ou afficher l'écho de ce qu'on frappe sur un clavier, on ne peut qu'être surpris par les différentes constructions intellectuelles que cela a demandées. On peut dire sans se tromper que l'informatique sait utiliser la transitivité. Par exemple, ce polycopié a été frappé à l'aide du traitement de texte LATEX [31] de L. Lamport (jeu de macros TeX de D. Knuth [26]), les caractères ont été calculés en METAFONT de D. Knuth [27], les dessins en gpic version Gnu (R. Stallman) de pic de B. Kernighan [23]. On ne comptera ni le système Macintosh (qui dérive fortement de l'Alto et du Dorado faits à Xerox PARC [51, 32]), ni OzTeX (TeX sur Macintosh), ni les éditeurs QED, Emacs, Alpha, ni le système Unix fait à Bell laboratories [44], les machines Dec Stations 3100, 5000, Sun Sparc2, ni leurs composants MIPS 2000/3000 [21] ou Sparc, ni le réseau Ethernet (Xerox-PARC [36]) qui permet d'atteindre les serveurs fichiers, ni les réseaux Transpac qui permettent de travailler à la maison, ni les imprimantes Laser et leur langage PostScript, successeur d'InterPress (J. Warnock à Xerox-PARC, puis Adobe Systems) [2], qui permettent l'impression du document. On peut évaluer facilement l'ensemble à quelques millions de lignes de programme et également à quelques millions de transistors. Rien de tout cela n'existait en 1960. Tout n'est que gigantesque mécano, qui s'est assemblé difficilement, mais dont on commence à comprendre les critères nécessaires pour en composer les différents éléments. En informatique, on doit composer beaucoup d'objets.





Une autre remarque est la rapidité de l'évolution de l'informatique. Une loi due à B. Joy dit que les micro-ordinateurs doublent de vitesse tous les deux ans, et à prix constant. Cette loi n'a pas été invalidée depuis 1978! (cf. le livre de Hennessy et Patterson [19]). Si on regarde les densités des mémoires ou des disques, on constate que des machines de taille mémoire honorable de 256 kilo-Octets en 1978 ont au minimum 32 Méga-Octets aujourd'hui. De même, on est passé de disques de 256 MO de 14 pouces de diamètre et de 20 cm de hauteur à des disques 3,5 pouces de 4 GO (Giga-Octets) de capacité et de 5cm de hauteur. Une machine portable de 3,1 kg peut avoir un disque de 500 MO. Il faut donc penser tout projet informatique en termes évolutifs. Il est inutile d'écrire un programme pour une machine spécifique, puisque dans deux ans le matériel ne sera plus le même. Tout programme doit être pensé en termes de portabilité, il doit pouvoir fonctionner sur toute machine. C'est pourquoi les informaticiens sont maintenant attachés aux standards. Il est frustrant de ne pouvoir faire que des tâches fugitives. Les standards (comme par exemple le système Unix ou le système de fenêtres X-Window qui sont des standards de facto) assurent la pérennité des programmes. En informatique, on doit programmer en utilisant des standards.





Une autre caractéristique de l'informatique est le côté instable des programmes. Ils ne sont souvent que gigantesques constructions, qui s'écroulent si on enlève une petite pierre. Le 15 janvier 1990, une panne téléphonique a bloqué tous les appels longue distance aux Etats-Unis pendant une après-midi. Une instruction break qui arrêtait le contrôle d'un for dans un programme C s'est retrouvée dans une instruction switch qui avait été insérée dans le for lors d'une nouvelle version du programme de gestion des centraux d'AT&T. Le logiciel de test n'avait pas exploré ce recoin du programme, qui n'intervenait qu'accidentellement en cas d'arrêt d'urgence d'un central. Le résultat de l'erreur fut que le programme marcha très bien jusqu'à ce qu'intervienne l'arrêt accidentel d'un central. Celui-ci, à cause de l'erreur, se mit à avertir aussitôt tous ses centraux voisins, pour leur dire d'appliquer aussi la procédure d'arrêt d'urgence. Comme ces autres centraux avaient tous aussi la nouvelle version du programme, ils se mirent également à parler à leurs proches. Et tout le système s'écroula en quelques minutes. Personne ne s'était rendu compte de cette erreur, puisqu'on n'utilisait jamais la procédure d'urgence et, typiquement, ce programme s'est effondré brutalement. Il est très rare en informatique d'avoir des phénomènes continus. Une panne n'est en général pas le résultat d'une dégradation perceptible. Elle arrive simplement brutalement. C'est ce côté exact de l'informatique qui est très attrayant. En informatique, il y a peu de solutions approchées. En informatique, il y a une certaine notion de l'exactitude.





Notre cours doit être compris comme une initiation à l'informatique, et plus exactement à l'algorithmique et à la programmation. Il s'adresse à des personnes dont nous tenons pour acquises les connaissances mathématiques: nulle part on n'expliquera ce qu'est une récurrence, une relation d'équivalence ou une congruence. Il est orienté vers l'algorithmique, c'est-à-dire la conception d'algorithmes (et non leur analyse de performance), et la programmation, c'est-à-dire leur implantation pratique sur ordinateur (et non l'étude d'un --- ou de plusieurs --- langage(s) de programmations). Les cours d'algorithmique sont maintenant bien catalogués, et la référence principale sur laquelle nous nous appuierons est le livre de Sedgewick [47]. Une autre référence intéressante par sa complétude et sa présentation est le livre de Cormen, Leiserson et Rivest [10]. Il existe bien d'autres ouvrages: Gonnet et Baeza-Yates [15], Berstel-Pin-Pocchiola [8], Manber [35], Graham-Knuth-Patashnik [17].... Il faut aussi bien sûr signaler les livres de Knuth [28, 29, 30]. Les cours de programmation sont moins clairement définis. Ils dépendent souvent du langage de programmation ou du type de langage de programmation choisi. Nous nous appuierons sur le polycopié de P. Cousot [11], sur le livre de Kernighan et Ritchie pour C [22], sur le livre de Wirth pour Pascal [20], sur le livre de Nelson [39] ou Harbison pour Modula-3[18].





Le cours est organisé selon les structures de données et les méthodes de programmation, utilisées dans différents algorithmes. Ainsi seront considérés successivement les tableaux, les listes, les piles, les arbres, les files de priorité, les graphes. En parallèle, on regardera différentes méthodes de programmation telles que la récursivité, les interfaces ou modules, la compilation séparée, le backtracking, la programmation dynamique. Enfin, nous essaierons d'enchaîner sur différents thèmes. Le premier d'entre eux sera la programmation symbolique. Un autre sera la validation des programmes, notamment en présence de phénomènes asynchrones. Enfin, on essaiera de mentionner les différentes facettes des langages de programmation (garbage collection, exceptions, modules, polymorphisme, programmation incrémentale, surcharge). Nous fournirons pour chaque algorithme deux versions du programme (quand nous en montrerons son implémentation): une dans le langage Java et une autre en Caml (langage enseigné en classes préparatoires). Une introduction à Java figurera en annexe, ainsi qu'un rappel de Caml. La lecture des chapitres et des annexes peut se mener en parallèle. Le lecteur qui connaît les langages de programmation n'aura pas besoin de consulter les annexes. Celui qui démarre en Java ou en Caml devra plutôt commencer par les appendices.





Le choix de considérer les langages Java et Caml est dicté par deux considérations. Ce sont d'abord des langages fortement typés. Il est impossible de finir un programme par une violation de la mémoire ou une erreur système irrémissible. Les deux langages vérifient la validité des index dans les accès aux tableaux ou des références dans les accès aux structures de données dynamiques. Par ailleurs, les deux langages ont une gestion automatique de la mémoire, car ils disposent d'un glaneur de cellules (garbage collector en anglais) permettant de récupérer automatiquement l'espace mémoire perdu. Cela simplifie notablement l'écriture des programmes et permet de se concentrer sur leur essence. L'allocation mémoire, elle, reste toujours explicite et on pourra toujours comprendre l'espace mémoire exigé par un programme. Autrefois, le cours étais fait en Pascal et en C; il sera possible de disposer d'appendices donnant les programmes de ce cours dans ces deux langages.

Historiquement, Java est un langage orienté-objet, quoique nous utiliserons peu cette particularité dans notre cours qui doit garder un aspect élémentaire. Il provient de Simula-67, Smalltalk, Modula-3[39] et C++[49]. Caml est un langage plutôt fonctionnel, dialecte de ML, dont il existe plusieurs implémentations: Caml et SML/NJ développés à l'INRIA et à Bell laboratories. Il provient de Lisp et de Scheme[1]. Depuis peu de temps, Caml dispose d'une très efficace extention orientée objet, Objective Caml (Ocaml). Les deux langages n'ont pas l'efficacité de C ou C++, quoique ceci reste à démontrer dans la manipulation des structures de données dynamiques. Mais à l'ère des machines RISC, il vaut mieux essayer d'écrire des programmes simples et compréhensibles. C'est le style que nous voulons adopter ici. La structure de nos programmes sera très souvent indépendante du langage de programmation, comme en attesteront les quatre versions de chacun de nos programmes en Pascal, C, Caml ou Java.

Pour être bien clair, prenons l'exemple du calcul de p. Un programme C esthétiquement correct, qui utilise la formule p/4= arctan(1)= Sn=0n=¥ (-1)n/(2n+1), est

#include <stdio.h>

#define N 10000

main()
{
    float pi = 0;
    int sign = 1;
    int n = 1;
    int m = 1;

    while (n < N) {
        pi = pi + (float)sign / m;
        m = m + 2;
        sign = -sign;
        ++n;
    }
    pi = 4 * pi;
    printf ("%.6f\n", pi);
}
Mais le programme C suivant [40] est interdit dans notre cours.

#define _ -F<00 || --F-OO--;
int F=00,OO=00;
main(){F_OO();printf("%1.3f\n", 4.*-F/OO/OO);}F_OO()
{
            _-_-_-_
       _-_-_-_-_-_-_-_-_
    _-_-_-_-_-_-_-_-_-_-_-_
  _-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
  _-_-_-_-_-_-_-_-_-_-_-_-_-_
    _-_-_-_-_-_-_-_-_-_-_-_
       _-_-_-_-_-_-_-_-_
            _-_-_-_
 }
Ce programme fait un nombre inconsidéré d'appels au préprocesseur et beaucoup trop d'effets de bord (cf.  page X). Il est illisible. Il faut avoir une conception simple et donc esthétique des programmes. On n'est plus à l'époque où tout se joue à l'instruction machine près. La puissance des ordinateurs a rendu très viable la programmation sans optimisations excessives. Tout l'art du programmeur est de comprendre les quelques endroits critiques d'un programme, en général très peu nombreux, où les optimisations sont nécessaires. Donc le programme cryptique précédent, ou tout autre bourré d'optimisations inutiles sera mauvais et donc interdit.

Dans cet exemple, typiquement, la convergence du calcul de p est mauvaise. Inutile donc de chercher à optimiser ce programme. Il faut changer de formule pour le calcul. La formule de John Machin (1680-1752) fait converger plus vite en calculant p/4 = 4 arctan(1/5) - arctan(1/239). Une autre technique consiste à taper sur la commande suivante sur une machine de l'Ecole Polytechnique

% maple
    |\^/|      MAPLE V
._|\|   |/|_.  Copyright (c) 1981-1990 by the University of Waterloo.
 \  MAPLE  /   All rights reserved.  MAPLE is a registered trademark of
 <____ ____>   Waterloo Maple Software.
      |        Type ? for help.
> evalf(Pi,1000);

3.14159265358979323846264338327950288419716939937510582097494459230781\
6406286208998628034825342117067982148086513282306647093844609550582231\
7253594081284811174502841027019385211055596446229489549303819644288109\
7566593344612847564823378678316527120190914564856692346034861045432664\
8213393607260249141273724587006606315588174881520920962829254091715364\
3678925903600113305305488204665213841469519415116094330572703657595919\
5309218611738193261179310511854807446237996274956735188575272489122793\
8183011949129833673362440656643086021394946395224737190702179860943702\
7705392171762931767523846748184676694051320005681271452635608277857713\
4275778960917363717872146844090122495343014654958537105079227968925892\
3542019956112129021960864034418159813629774771309960518707211349999998\
3729780499510597317328160963185950244594553469083026425223082533446850\
3526193118817101000313783875288658753320838142061717766914730359825349\
0428755468731159562863882353787593751957781857780532171226806613001927\
876611195909216420199

> quit;
%

Previous Next Contents