Académique Documents
Professionnel Documents
Culture Documents
NSI Term
NSI Term
Sciences Informatiques
Classe de Terminale
spécialité - 2020
Sandrine Piquard
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
0.1 Sous-épreuve écrite de 3 h 30 (sur 12 points) . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
0.2 Sous-épreuve pratique de 1 heure (sur 8 points) . . . . . . . . . . . . . . . . . . . . . . . . . 3
1 Structure de données 4
1.1 Listes, piles, files : structures linéaires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.2 Dictionnaires, index et clé . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.3 Vocabulaire de la programmation orientée objet : classes, attributs, méthodes, objets . . . . . . 12
1.4 Arbres : structures hiérarchiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
1.5 Graphe : structure relationnelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
Exos : ... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
2 Bases de données 28
2.1 De quoi est fait le modèle fonctionnel ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
2.2 Quelle démarche adopter pour représenter un diagramme de classe ? . . . . . . . . . . . . . . 30
2.3 Modèle physique de données . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
2.4 Modèle relationnel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
2.5 Base de données relationnelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
2.6 Système de gestion de bases de données relationnelles . . . . . . . . . . . . . . . . . . . . . . 39
2.7 Installation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
2.8 On y va ! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
2.9 Exécuter une requête . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
2.10 Pour aller plus loin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
2.11 Langage SQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
2.12 Interroger la base de données . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
1
TABLE DES MATIÈRES
5 Langages et programmation 67
5.1 Calculabilité, décidabilité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
5.2 Récursivité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
5.3 Corrigés . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
5.4 Modularité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
5.5 Paradigmes de programmation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
5.6 Mise au point de programme ; gestion des bugs . . . . . . . . . . . . . . . . . . . . . . . . . 79
6 Algorithmique 81
6.1 Algorithmes sur les arbres binaires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
6.2 Algorithmes des arbres binaires de recherche . . . . . . . . . . . . . . . . . . . . . . . . . . 87
6.3 Applications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
6.4 Algorithmes sur les graphes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
6.5 Méthode « diviser pour régner » . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
6.6 Programmation dynamique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
6.7 Recherche textuelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
Exos : ... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
7 Grand Oral 97
7.1 Définition et objectifs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
7.2 Épreuve orale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
7.3 Évaluation de l’épreuve . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
7.4 Format et déroulement de l’épreuve . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
7.5 Composition du jury . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
Mediagraphie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
8 Programme officiel 98
8.1 Préambule . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
8.2 Éléments de programme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
abcefghijklmnopqrstuvwxyz
La démarche de projet est au cœur de l’enseignement de la spécialité NSI mais ne se prête pas à une évaluation
à l’écrit ou sur machine en temps limité.
L’épreuve obligatoire terminale orale permet à l’élève de présenter son projet et de valoriser le travail qu’il a
conduit durant sa formation.
L’épreuve terminale obligatoire de spécialité est composée de deux sous-épreuves : une épreuve écrite et
une épreuve pratique.
La sous-épreuve pratique permettant d’évaluer les compétences en programmation, cette évaluation n’est pas un
objectif de la sous-épreuve écrite. Toutefois le candidat peut être évalué sur la compréhension d’un algorithme
ou d’un programme, d’un point de vue plus théorique que pratique.
3
Chapitre 1 Structure de données
Les listes chaînées sont une structure de donnée très souple et efficace. Il ne faut pas confondre cette structure
de liste avec les listes Python qui sont en fait une structure bien plus riche, que l’on pourrait qualifier de tableau
dynamique.
Les piles, les files et les listes chaînées sont des objets abstraits, ils n’existent pas nécessai-
rement dans tous les langages mais ce sont des structures de données à connaître que nous
pouvons créer si nécessaire.
Par exemple, en C mais aussi en java (initialement), le tableau dynamique (que l’on peut étendre en fonction
des besoins) n’existe pas. On doit choisir dès le début, puisque ce sont des langages fortement typés, la taille
du tableau que l’on va utiliser. Si le tableau est trop petit... on est coincé. Lorsque l’on code l’objet « tableau
dynamique » en C, on dit que l’on implémente l’objet abstrait.
Les listes chaînées permettent de construire un tableau dynamique dans ces langages, puisque
chaque élément contient l’adresse mémoire de l’élément suivant, les données ne sont donc plus
nécessairement stockées les unes à la suite des autres en mémoire : on n’a plus de problème
de taille.
4
CHAPITRE 1. STRUCTURE DE DONNÉES
Admettons que l’on veuille insérer un élément entre l’élément1 qui pointe vers l’adresse 0001 et l’élément2
qui est donc à l’adresse 0010. On crée un élément1bis qui pointe vers une adresse libre, par exemple 0101, on
change le pointeur de l’élément1 vers l’adresse 0101.
Supprimer un élément se fera en modifiant l’adresse de l’élément précédent, vers l’élément suivant.
Dans une liste chaînée, chaque élément contient la donnée et un pointeur vers l’élément suivant. On accède à la
liste via un pointeur a qui pointe toujours vers le premier élément (Figure 1.1) :
On peut imaginer, pour notre exemple du garagiste, une boîte à chaussures contenant toutes les fiches client de
la journée. Notre garagiste peut les parcourir une à une, insérer au milieu une nouvelle fiche pour un client qu’il
voudrait intercaler, etc...
Exercice n°1 :
1. Décrire en langage naturel des algorithmes simples permettant de réaliser les opérations ci-dessus. Vous
pourrez lorsque c’est utile vous aider de schémas.
2. Citez un avantage de la liste chaînée par rapport au tableau de taille fixe ou dynamique (comme le type
list en python).
Corrigé succinct : Créer nouvel élément ; mettre adresse du pointeur a à la fin du nouvel élément, puis l’adresse
du nouvel élément dans le pointeur a.
Exemples d’utilisation Les piles sont extrêmement utiles en informatique et vous les utilisez quotidienne-
ment, parfois même sans vous en rendre compte :
❧ La fonction « annuler » (Ctrl+Z) de votre traitement de textes par exemple est une pile : quand vous tapez
Ctrl+Z, vous annulez la dernière opération effectuée. Quand vous faites une nouvelle opération, celle-ci
est mémorisée au sommet de la pile. Vous ne pouvez pas annuler l’avant dernière opération sauf à annuler
la dernière.
❧ Le bouton retour de votre navigateur internet fonctionne également à l’aide d’une pile. Les pages web
consultées lors de votre navigation sur une page sont empilées et le bouton retour permet d’accéder à la
dernière page présente sur la pile.
❧ Certaines calculatrices fonctionnent à l’aide d’une pile pour stocker les arguments des opérations : c’est
le cas de beaucoup de calculatrices de la marque HP, dont la première calculatrice scientifique ayant
jamais été produite : la HP 35 de 1972, en notation polonaise inverse.
Exemple de calcul avec une pile Le mode de calcul avec une pile s’appelle RPN (Reverse Polish Notation
ou notation polonaise inverse). Dans cette logique postfixée, on saisit d’abord les arguments de l’opération puis
en dernier, l’opération à réaliser.
Exemple : Pour faire 2 + 3 on empilera 2, puis 3 et enfin on invoquera la fonction +. Cette logique est extrê-
mement efficace et rapide, en particulier dans les enchaînements d’opérations car elle ne nécessite pas de saisir
des parenthèses. Elle permet aussi de faire moins d’erreurs de calcul car on est obligé de réfléchir aux priorités
des opérations au moment de la saisie.
Exercice n°2 :
1. On tape sur la HP-45 la séquence de touche suivante : 12 ENTER 4 ENTER 3 x -. Quel est le calcul
effectué ? Quel est le résultat sur le sommet de la pile ?
2. On souhaite effectuer le calcul (12 − 4) ∗ 3. Quelle séquence de touche doit-on prévoir ?
Implémentation en Python L’implémentation des piles en python se fait facilement à l’aide des méthodes
append() et pop() du type list :
✞
1 ma_pile.append(ma_valeur) # permet d’empiler une valeur
2 ma_pile.pop() # permet de dépiler une valeur
3 len(ma_pile) # renvoie la longueur de ma_pile
✝ ✆
Exercice n°3 :
Dans l’implémentation python proposée ci-dessus, quelle est la commande à taper pour connaître la valeur au
sommet de ma_pile sans la dépiler ?
Exemple d’utilisation Dans le domaine informatique, on retrouve par exemple les files dans les files d’im-
pression où le premier document envoyé à l’imprimante sera le premier document à être imprimé.
Opérations valides sur les files Les opérations permises sur les files sont similaires aux piles. Il est possible :
❧ d’enfiler un nouvel élément (ajouter un élément à la fin de la file),
❧ de défiler le premier élément saisi (celui au début de la file),
❧ de consulter la valeur du premier élément au début de la file sans le sortir de la file,
❧ de tester si la file est vide,
❧ de connaître le nombre d’éléments dans la file.
Il n’est pas possible d’accéder directement au nième élément d’une file : Il faudrait défiler les n − 1 premiers
éléments et de ce fait, détruire partiellement notre file.
Implémentation en Python L’implémentation des files en python se fait de manière analogue aux piles :
✞
1 ma_file.append(ma_valeur) # permet d’enfiler une valeur
2 ma_file.pop(0) # permet de défiler une valeur
3 len(ma_file) # renvoie la longueur de ma_file
✝ ✆
Exercice n°4 :
1. Dans l’implémentation python proposée ci-dessus, quelle est la commande à taper pour connaître la
valeur du premier élément de la file ?
2. On considère la file constituée des entiers 25, 31, 1, 54, 13.
2.1. On ajoute la valeur 35 à cette file puis on sort 2 valeurs. Quel est alors le contenu de la file.
2.2. Le contenu de la file change-t-il si on effectue ces opérations dans le sens contraire ? (on sort 2
valeurs puis on ajoute 35)
Exercice n°5 :
1. Créez un script python dans lequel vous définissez une file constituée des entiers 25, 31, 1, 54, 13.
2. Créez une fonction qui utilise la méthode append pour ajouter un élément à la fin de la file.
3. Créez une fonction qui utilise la méthode pop pour supprimer un élément au début de la file.
4. Créez une fonction qui permet de consulter la valeur du premier élément au début de la file, sans le sortir
de la file.
5. Créer une fonction qui renvoie False si la file est vide.
1.2.3 Hachage
La notion de hachage est omniprésente en informatique et est au cœur du fonctionnement des dictionnaires.
Le hachage est un mécanisme permettant de transformer la clé en un nombre unique permettant l’accès à la
donnée, un peu à la manière d’un indice dans un tableau.
Exercice n°6 :
Une autre utilisation du hachage est la détection de la modification d’un fichier.
1. Créer avec un éditeur de texte un texte de plusieurs paragraphes. (Vous pouvez générer un contenu quel-
conque sur internet grâce au site https://fr.lipsum.com/)
2. Dans un terminal, tapez la commande md5sum mon_fichier.txt en adaptant bien sûr le nom du
fichier.
3. Modifiez très légèrement votre fichier original en ajoutant un espace à la fin par exemple. Recalculer le
hash MD5. Que constatez-vous ?
Ainsi la fonction de hachage peut mettre en évidence des différences qui seraient invisibles à l’œil nu.
4. En Python, tester la fonction de hachage grâce à la fonction hash() :
✞
1 >>> hash("Lecluse")
2 1004192856731890808
✝ ✆
5. Vérifiez à l’aide de quelques exemples que cette fonction hash() est bien une fonction de hachage.
Table de Hachage
Regardez la vidéo ci-dessous sur les tables de hachage.https://youtu.be/IhJo8sXLfVw
Ce qui est important à retenir c’est que la recherche dans une table de hachage est indépendante du nombre
d’éléments dans cette table. Dans un tableau ou une liste chaînée au contraire, la recherche d’un élément prend
un temps proportionnel au nombre d’éléments dans la structure( complexité linéaire en O(n)).
Dans un dictionnaire, les clés sont stockées dans une table de hachage, ce qui explique le fait que le dictionnaire
est optimisé pour la recherche sur les clés.
L’état est défini par les valeurs des attributs de l’objet à un instant t.
Par exemple, pour un téléphone, certains attributs sont variables dans le temps comme allumé ou éteint, d’autres
sont invariants comme le modèle de téléphone.
Le comportement est défini par les méthodes de l’objet : en résumé, les méthodes définissent à quoi sert l’objet
et/ou permettent de modifier son état.
L’identité est définie à la déclaration de l’objet (instanciation) par le nom choisi, tout simplement.
En programmation orientée objet, on fabrique de nouveau types de données correspondant aux besoin du
programme. On réfléchit alors aux caractéristiques des objets qui seront de ce type et aux actions possibles à
partir de ces objets.
Ces caractéristiques et ces actions sont regroupées dans un code spécifique associé au type de données, appelé
classe.
✞
1 # Dans la console PYTHON
2 >>>l = [1,5,2]
3 >>>l.sort()
4 >>>l[1,2,5]
5
✝ ✆
✞
1 class Carte: # Définition de la classe
2 """Une carte d’un jeu de 32 ou 54 cartes"""
3 def __init__(self,valeur,couleur): # méthode 1 : constructeur
4 self.valeur = valeur # 1er attribut valeur {de 2 à 14 pour as}
5 self.couleur = couleur # 2e attribut {’pique’,’carreau’,’coeur’,’trefle’}
6
✝ ✆
Création d’une instance de la classe Carte :
✞
1 # Dans la console PYTHON
2 >>>x=Carte(5,’carreau’)
3
✝ ✆
Lorsque l’on créé un objet, son constructeur est appelé implicitement et l’ordinateur alloue de la mémoire pour
l’objet et ses attributs. On peut d’ailleurs obtenir l’adresse mémoire de notre objet créé x.
✞
1 # Dans la console PYTHON
2 >>>x = Carte(5,’carreau’)
3 >>>x
4 <__main__.Carte object at 0x7f7f57d4ae90>
5 # Rappel: un nombre qui commence par 0x...........
6
✝ ✆
Par ailleurs, l’obtention de la valeur d’un attribut d’un objet se fait par l’utilisation de l’opérateur d’accessibilité
point : nom_objet.nom_attribut .
Cela peut se lire ainsi de droite à gauche nom_attribut appartenant à l’instance nom_objet :
✞
1 # Dans la console PYTHON
2 >>>x = Carte(5,’carreau’)
3 >>>x.valeur
4 5
5 >>>x.couleur
6 ’carreau’
✝ ✆
La variable self
La variable self, dans les méthodes d’un objet, désigne l’objet auquel s’appliquera la méthode.
Elle représente l’objet dans la méthode en attendant qu’il soit créé.
✞
1 # Dans l’éditeur PYTHON
2 class Carte: # Définition de la classe
3 """Une carte d’un jeu de 32 ou 54 cartes"""
4 def __init__(self,valeur,couleur): # constructeur
5 self.valeur = valeur # 1er attribut
6 self.couleur = couleur # 2e attribut
✝ ✆
✞
1 # Dans la console PYTHON
2 >>>x = Carte(5,’carreau’)
3 >>>y = Carte(14,’pique’)
✝ ✆
Dans cet exemple, la méthode __init__ (constructeur) est appelée implicitement.
"self" fait référence à l’objet x dans la première ligne et à l’objet y dans la seconde.
Pour utiliser ou modifier les attributs, on utilisera de préférence des méthodes dédiées dont le rôle est de faire
l’interface entre l’utilisateur de l’objet et la représentation interne de l’objet (ses attributs).
Les attributs sont alors en quelque sorte encapsulés dans l’objet,c’est à dire non accessibles directement par le
programmeur qui a instancié un objet de cette classe.
Pour obtenir la valeur d’un attribut nous utiliserons la méthode des accesseurs(ou "getters") dont le nom est
généralement : getNom_attribut() . Par exemple ici :
✞
1 # Dans l’éditeur PYTHON
2 class Carte: # Définition de la classe
3 """Une carte d’un jeu de 32 ou 54 cartes"""
4 def __init__(self,valeur,couleur): # méthode 1 : constructeur
5 self.valeur = valeur # 1er attribut valeur {de 2 à 14 pour as}
6 self.couleur = couleur # 2e attribut{’pique’,’carreau’,’coeur’,’trefle’}
7
Exercice n°7 :
Créer deux autres méthodes permettant de récupérer la valeur de la carte et la couleur avec les "getters" (acces-
seurs) : getCouleur() et getValeur()
Exercice n°8 :
1. Créer le mutateur de l’attribut couleur sous la forme setCouleur(self,c).
2. Créer une carte c2, un Roi de cœur puis modifier sa valeur en la passant à une Dame.
3. Modifier la couleur de la carte c2 en la passant à pique.
4. Modifier la carte c2 en la passant à 8 de carreau.
Un arbre est défini par divers éléments : des nœuds, des feuilles, des racines, des branches. On
peut aussi le définir sur le modèles des liens de parenté avec des parents et des enfants, des
ancêtres, des frères. Les branches permettent d’aller de la racine jusqu’à chaque feuille.
Les arbres sont des cas particuliers des graphes présentés dans la section suivante. On peut donc aussi utiliser
le vocabulaire des graphes et parler de sommets, d’arêtes et de chemins.
C:
Explorer.exe 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3
Donc
❧ soit l’arbre est vide,
❧ soit il est composé d’une racine et d’un sous-arbre droit et d’un sous-arbre gauche.
Les deux sous-arbres sont eux aussi des arbres binaires.
On peut utiliser la structure de liste en Python pour implémenter une structure d’arbre. Par exemple pour un
arbre binaire :
❧ une liste vide pour un arbre vide,
❧ une liste contenant 3 éléments, la valeur ou clé de la racine, le sous-arbre gauche et le sous-arbre droit.
Il est nécessaire de définir les fonctions pour :
❧ créer un arbre vide ou un arbre dont la racine est donnée ;
❧ savoir si un arbre est vide ou pas ;
❧ obtenir le sous-arbre gauche ou le sous-arbre droit à partir d’une racine ;
❧ insérer un nœud.
Construisons un arbre binaire pour lequel chaque élément inséré obéit à la règle suivante : si la valeur à insérer
est inférieure à la racine, on l’insère dans le sous-arbre gauche, sinon on l’insère dans le sous-arbre droit.
✞
1 def creer_arbre(r=None):
2 """
3 Renvoie un arbre vide ou un arbre de racine r
4 """
5 if r:
6 return [r, [], []]
7 else:
8 return []
9
10
11 def arbre_vide(a):
12 return a==[] # le résultat est True ou False
13
14 def fils_gauche(a):
15 if not arbre_vide(a):
16 return a[1]
17
18 def fils_droit(a):
19 if not arbre_vide(a):
20 return a[2]
21
22 def insere(a, val):
23 if arbre_vide(a):
24 a.append(val)
25 a.append([])
26 a.append([])
27 elif val <= a[0]:
28 # si la valeur a insérer est inf à la racine,
29 #on l’insère dans le sous-arbre gauche
30 insere(a[1], val)
31 else:
32 #sinon on l’insère dans le sous-arbre droit
33 insere(a[2], val)
✝ ✆
Et on peut ajouter dans le script les lignes de test :
✞
1
2 if __name__ == "__main__":
3 a = creer_arbre(12)
4 print(arbre_vide(a))
5 insere(a,15)
6 insere(a,14)
7 insere(a,8)
8 insere(a,17)
9 print(a)
✝ ✆
Ce qui donne :
12
8 15
14 17
Définition 2 : La hauteur d’un arbre est le nombre de niveau des nœuds. Un nœud de niveau k est le fils d’un
nœud de niveau k − 1. La racine du nœud a le niveau 0.
Définition 3 : La profondeur d’un nœud est le nombre de nœuds de la branche partant de la racine et allant
jusqu’au nœud considéré, extrémité comprise.
15
12 17
11 14 16 21
L’arbre ci-dessus est un arbre complet, car chaque nœuds a exactement deux fils, sauf les feuilles.
Si l’arbre a une hauteur égale à 4, alors le nombre de nœuds est donné par :
1 + 21 + 22 + 23 = 24 − 1
soit 15 nœuds et 23 = 8 feuilles.
De manière générale, dans un arbre binaire complet, le nombre de feuilles est 2h−1 et le
nombre de nœuds est 2h − 1.
Dans le cas de l’arbre complet, on peut définir cet arbre binaire complet par une liste simple :
✞
1 >>> arbre = [15, 12, 17, 11, 14, 16, 21]
✝ ✆
L’astuce est que les deux fils du nœud d’indice i se trouvent aux indices 2i + 1 et 2i + 2.
Nous verrons les algorithmes sur les arbres dans le chapitre 6.
Une seconde manière de procéder est de raisonner de manière récursive : les deux enfants d’un nœud sont des
arbres.
✞
1 class Arbre:
2 def __init__(self, val):
3 self.valeur = val
4 self.gauche= None
5 self.droite = None
6
7 def __str__(self):
8 return str(self.valeur)
9
23 monarbre = Arbre(15)
24
25 liste = [12, 5, 8, 19, 14, 16]
26 for n in liste:
27 monarbre.inserer(n)
28
29 print(monarbre)
30 print(monarbre.gauche,monarbre.droite )
31 print(monarbre.gauche.gauche,monarbre.gauche.droite, monarbre.droite.gauche,
monarbre.droite.droite )
32 print(monarbre.gauche.gauche.droite)
✝ ✆
Exercice n°9 :
Dessiner un arbre binaire de recherche contenant les valeurs 11, 13, 14, 15, 17, 18, 19 satisfaisant la condition
donnée dans chacun des cas ci-dessous :
1. Dans le premier cas, la hauteur de l’arbre est 3.
2. Dans le deuxième cas, l’arbre n’a qu’une feuille au niveau 4, qui n’est ni la plus petite ni la plus grande
valeur.
Définition 4 : Graphe : un graphe est un ensemble de sommets reliés entre eux par des arcs.
Matrice d’adjacence
Reprenons notre graphe :
Le programme ci-dessus encapsule une telle matrice dans une classe Graphe. Le constructeur de cette classe
prend en argument le nombre de sommets et alloue la matrice. La méthode ajouterArc permet d’ajouter un
arc au graphe. On peut alors tester :
✞
1 if __name__ == "__main__":
2 g = Graphe(4)
3 g.ajouterArc(0,1)
4 g.ajouterArc(0,3)
5 g.ajouterArc(1,2)
6 g.ajouterArc(3,1)
✝ ✆
Exercice n°10 :
1. Que fait la méthode arc ?
Dictionnaire d’adjacence
On peut représenter le graphe précédent en utilisant un dictionnaire d’adjacence. Le graphe est alors un diction-
naire qui associe à chaque sommet l’ensemble de ses voisins.
La première conséquence est que les sommets ne sont pas limités à des entiers et qu’il n’est pas nécessaire de les
connaître tous a priori. En effet, il suffit d’ajouter une nouvelle entrée au dictionnaire pour ajouter un nouveau
sommet au graphe. L’ensemble des sommets du graphe est exactement l’ensemble des clé du dictionnaire. On
peut alors parcourir tous les sommets du graphe avec la boucle :
✞
1 for s in graphe:
2 ...
✝ ✆
Les voisins du sommet s sont donnés par l’ensemble graphe[s], on peut donc les parcourrir en faisant :
✞
1 for v in graphe[s]:
2 ...
✝ ✆
La deuxième conséquence est que complexité de cette nouvelle représentation est alors proportionnelle au
nombre de voisin de s, indépendamment du nombre de sommet du graphe.
✞
1 """
2 Graphe représenté par un dictionnaire d’adjacence
3 """
4
5 class Graphe:
6 """
7 Un graphe représenté par undictionnaire d’adjacence,
8
9 """
10 def __init__(self):
11 self.adj = {}
12
13 def __str__(self):
14 return str(self.adj)
15
16 def ajouterSommet(self, s):
17 if s not in self.adj:
18 self.adj[s]= set()
19
28 def sommets(self):
29 return list (self.adj)
30
Il suffit donc que les opération ajouetrArc et supprimerArc ajoutent et enlèvent systématiquement une
paire d’arc :
✞
1 self.adj[s1][s2]=True
2 self.adj[s2][s1]=True
✝ ✆
ou
✞
1 self.ajouterSommet(s1)
2 self.ajouterSommet(s2)
3
4 selj.adj[s1].add(s2)
5 selj.adj[s2].add(s1)
✝ ✆
Exercice n°11 :
1. Ajouter à la classe Graphe du programme graphs.py une méthode afficher pour afficher le
graphe sous la forme suivante :
✞
1 0 -> 13
2 1 -> 23
3 2 -> 3
4 3 -> 1
✝ ✆
c’est à dire une ligne par sommet, avec pour chacun la liste de ses voisins.
2. Ajouter une méthode degre(s) qui donne le nombre d’arcs issus du sommet s.
3. Ajouter une méthode nbArcs() qui donne le nombre total d’arcs du graphe.
4. Ajouter une méthode supprimerArcs(s1,s2) pour supprimer l’arc entre les sommets s1 et s2.
On pourra utiliser la méthode remove.
Exercice n°12 :
1. Ajouter à la classe Graphe du programme graph2.py une méthode afficher pour afficher le
graphe sous la forme suivante :
✞
1 0 {1, 3}
2 1 {2, 3}
3 3 {1}
4 2 {3}
✝ ✆
c’est à dire une ligne par sommet, avec pour chacun la liste de ses voisins. L’ordre des sommets n’a pas
d’importance.
2. Ajouter la méthode nbSommets() qui donne le nombre de sommets du graphe.
3. Ajouter une méthode degre(s) qui donne le nombre d’arcs issus du sommet s.
4. Ajouter une méthode supprimerArcs(s1,s2) pour supprimer l’arc entre les sommets s1 et s2.
On pourra utiliser la méthode remove.
https://www.numerique-sciences-informatiques.fr/terminale_nsi/cours_structure_donnees_pile_file.php
https://pixees.fr/informatiquelycee/n_site/nsi_term.html
https://www.math93.com/images/pdf/NSI/terminale/NSI_Classes.pdf
Le développement des traitements informatiques nécessite la manipulation de données de plus en plus nom-
breuses. Leur organisation et leur stockage constituent un enjeu essentiel de performance. Le recours aux bases
de données relationnelles est aujourd’hui une solution très répandue. Ces bases de données permettent d’orga-
niser, de stocker, de mettre à jour et d’interroger des données structurées volumineuses utilisées simultanément
par différents programmes ou différents utilisateurs. Cela est impossible avec les représentations tabulaires
étudiées en classe de première.
Des systèmes de gestion de bases de données (SGBD) de très grande taille (de l’ordre du pétaoctet) sont au
centre de nombreux dispositifs de collecte, de stockage et de production d’informations.
L’accès aux données d’une base de données relationnelle s’effectue grâce à des requêtes d’interrogation et de
mise à jour qui peuvent par exemple être rédigées dans le langage SQL (Structured Query Language). Les
traitements peuvent conjuguer le recours au langage SQL et à un langage de programmation.
Nous allons voir comment structurer une base de donnée : la manière de structurer ces données s’appelle le
domaine fonctionnel.
Puis nous verrons comment gérer une base de données et effectuer des requêtes dans cette base de données.
Un objet
En informatique, les objets correspondent à des concepts plutôt qu’à des objets physiques : un compte en
banque, une équation mathématique, une histoire. Les objets peuvent également être virtuels : internet, un
mirage, un objet virtuel en optique géométrique.
Un objet informatique est défini à la fois par des informations et par des comportements : on dit qu’un objet
encapsule des informations et un comportement.
28
CHAPITRE 2. BASES DE DONNÉES
Définition 5 : La notion d’objet : Un objet est un élément autonome, réel (un crayon, une voiture...) ou
abstrait (un ensemble, une liste...). En UML tout objet possède un ensemble d’attributs (sa structure) et un
ensemble de méthodes (son comportement). Un attribut est une variable destinée à recevoir une valeur. Une
méthode est un ensemble d’instructions prenant des valeurs en entrée et modifiant les valeurs des attributs ou
produisant un résultat.
Un objet peut également être anonyme, on précède alors son nom d’un double point. Exemple : élèves et
professeurs (cf. figure 2.2).
Une classe
Dans la démarche orientée objet, on définit :
Définition 6 : La classe : Une classe est le modèle abstrait d’un objet qui représente une entité, un élément du
domaine fonctionnel. Tous les objets ayant les mêmes propriétés sont rassemblés dans ce qu’on appelle une
classe. Une classe définit les propriétés de tous les objets qui lui sont associés. Une classe est schématisée par
un rectangle dans lequel le nom de la classe n’est pas souligné. Il y figure les attributs et les comportements
de tous les objets de cette classe (nom, liste des attributs)(cf. figure 2.3).
Les classes doivent être nommées avec un nom pertinent (ex Restaurant ne pourra pas contenir aussi les bars,
sinon il faut changer le nom en « établissement ».
Une instance
Au sein d’une classe, les objets portent le nom d’instance de la classe. Le terme instance insiste sur l’apparte-
nance à une classe alors que le terme objet est plus générique.
Définition 7 : L’instance : L’instance est un objet de la classe, elle est créée à partir de la classe et elle
possède les mêmes attributs et comportements que toutes les autres instances de la classe.
Le nom d’une classe est au singulier, constitué d’un nom commun précédé ou suivi d’un ou plusieurs adjectifs
qualifiant le nom. Le nom est représentatif de l’ensemble des instances constituant la classe.
Les attributs contiennent les données encapsulées dans les objets de la classe. Les opérations représentent les
comportements des objets de cette classe.
Par exemple dans un logiciel concernant la sécurité sociale, une classe "Personne" pourrait définir les attributs
"NumeroSS" qui représente le numéro de sécurité sociale, "Nom", "Prénom" et le comportement des instances
pourrait indiquer qu’elles peuvent changer d’adresse (cf. figure 2.7).
En UML :
Pour le nom des classes :
❧ au singulier
❧ avec des lettres non accentuées
❧ commençant par une majuscule
❧ utilisant la notation chameau (une majuscule au début de chaque mot suivant)
Ex : PierrePrecieuse
Pour les attributs :
❧ au singulier
❧ avec des lettres non accentuées
❧ commençant par une minuscule
❧ utilisant la notation chameau (une majuscule au début de chaque mot suivant)
Ex : couleurYeux
Dans le MPD :
Pour le nom des tables et des attributs :
❧ au singulier
❧ avec des lettres non accentuées
❧ en minuscules
❧ les mots étant séparés par des underscores (_)
Ex : pierre_precieuse, couleur_yeux
Exercice n°13 :
Par groupe de 2 ou 3 :
1. 10 minutes : brain storming : dans le but de concevoir une base de données, trouver un sujet (en ayant
peut-être à l’esprit un site internet par-dessus) qui pourrait être décrit par une classe, des attributs de la
classe et des actions applicables à la classe.
2. 10 minutes : structurer vos objets en utilisant la notation UML,
3. 30 minutes : représenter votre modèle physique de données à la manière des Figures 2.3 à 2.7, sur une
grande feuille en laissant de la place pour compléter le schéma.
F IGURE 2.8 – Relation d’instanciation entre une classe et une de ses instances.
Définition 8 : Le lien qui existe entre deux classes d’objets est appelé une association.
Exemple : Dans une médiathèque un adhérent peut emprunter au maximum trois exemplaires. Sur place il peut
consulter autant d’exemplaires qu’il le souhaite.
Il existe un lien entre les objets de la classe "Adhérent" et les objets de la classe "Exemplaire". Cette association
est représentée par un trait plein entre les deux classes. Elle peut avoir un nom (un verbe à l’infinitif en général)
par exemple "emprunter" et "consulter" (cf. figure 2.9).
2.4.3 Cardinalité
Les cardinalités de l’association représentent le nombre d’instances impliquées dans l’association.
Dans l’exemple de la figure 2.9, la cardinalité "0..3" indique qu’un adhérent peut être associé à 0, 1, 2 ou 3
livres, c’est-à-dire qu’il peut emprunter au maximum trois livres. A l’inverse un livre ne peut être emprunté que
par un seul adhérent (cardinalité "0..1"). Le symbole "*" sert à représenter une valeur potentiellement infinie.
Les cardinalités se trouvent du coté de l’objet qu’elles décrivent.
2.4.4 Rôle
Sur le lien peuvent également figurer les noms des rôles des classes. Ils permettent de lever des ambiguïtés.
Sans indication de rôles dans la figure 2.10 nous ne pouvons pas savoir si c’est la société d’intérimaire qui
fournit du personnel à une société de nettoyage ou si c’est une société de nettoyage qui travaille dans les locaux
d’une société d’intérim. Dans le diagramme de la figure 2.10 c’est la seconde hypothèse qui est la bonne.
2.4.9 Contrainte
Une contrainte permet de préciser le contexte du modèle en positionnant des restrictions. Elles sont indiquées
entre deux accolades. Elles sont le plus souvent exprimées en langage naturel. On relie la contrainte avec les
éléments concernés par un trait en pointillé (cf. figures 2.14).
Dans ce cas, afin de vous assurer de pouvoir identifier un tuple de manière unique, il y a deux solutions :
1. identifier un tuple par le nom ET le prénom ;
2. ajouter un attribut (une colonne) pour accueillir un identifiant dont on sera sur qu’il sera unique.
Cet identifiant n’a rien de concret. Parfois, on ajoute le nom de la table en préfixe (ami_id).
Définition 9 : Lors de la création de la table dans la base de données, un attribut spécifique sera indiqué
comme l’identifiant des tuples de la table : c’est la clé primaire, ou primary key (PK) en anglais.
Dans le modèle physique de données (MPD), la clé primaire est située dans la partie du milieu et porte l’indi-
cation PK :
ami
id : INTEGER NOT NULL [PK]
prenom : VARCHAR
nom : VARCHAR
couleur_yeux : VARCHAR
Pour assurer la cohérence du modèle relationnel, la clé primaire doit être unique et surtout, elle ne doit jamais
changer.
Avant de faire un lien entre les tuples de différentes tables (ou instances de classes), il faut pouvoir les identifier
de manière unique et certaine. La clé primaire va servir de d’identifiant unique pour relier les tuples de la table
à ceux d’une autre table !
Définition 10 : Clé étrangère : Dans une table, on ajoute une colonne qui fait référence à la clé primaire
d’une autre table : c’est ce que l’on appelle une clé étrangère ou foreign key (FK) en anglais.
La valeur de la clé étrangère dans un tuple n’est rien d’autre que la valeur de la clé primaire du tuple lié.
Imaginons l’association pilote.machine entre un Motard et une Moto.
F IGURE 2.17 – Exemple de clés primaires qu’il faut compléter d’une clé étrangère
Exercice n°14 :
1. Reprenez votre schéma de base de données,
2. définissez le modèle relationnel : les relations, la cardinalité, les rôles éventuellement, les clés primaires
et clés étrangère qui mettent en relation ce qui deviendra vos tables de données.
3. Quand le résultat vous semble complet : remettre au propre votre schéma.
Exercice n°15 :
1. En partant de la relation FILMS ci-dessus, créez une relation REALISATEURS (attributs de la relation
REALISATEURS : id, nom, prénom, ann_naissance) à l’aide d’une recherche.
2. Modifiez ensuite la relation FILMS afin d’établir un lien entre les relations FILMS et REALISATEURS.
3. Préciser l’attribut qui jouera le rôle de clé étrangère.
On appelle schéma relationnel l’ensemble des relations présentes dans une base de données.
Quand on vous demande le schéma relationnel d’une base de données, il est nécessaire de fournir les informa-
tions suivantes :
❧ Les noms des différentes relations ;
❧ Pour chaque relation, ma liste des attributs avec leur domaine respectif ;
❧ Pour chaque relation, la clé primaire et éventuellement la clé étrangère.
Voici un exemple pour les relation LIVRES et AUTEURS :
AUTEURS (id : INT, nom : TEXT, prenom : TEXT, ann_naissance : INT, langue_ecriture :
TEXT)
LIVRES (id : INT, titre : TEXT, #id_auteur : INT, ann_publi : INT, note : INT)
Les attributs soulignés sont les clés primaires, le # signifie qu’on a une clé étrangère.
2.7 Installation
Nous allons utiliser les Raspberry.
Pour cela commencez par installer « DB Browser for SQLite » : https://sqlitebrowser.org/
2.7.2 Debian
Note that Debian focuses more on stability rather than newest features. Therefore packages will typically
contain some older version, compared to the latest release.
2.8 On y va !
Après avoir lancé « DB Browser for SQLite » vous devriez avoir quelque chose qui ressemble à ceci :
Cliquez sur « Nouvelle base de données » après avoir choisi un nom pour votre base de données (par exemple :
« db_livres.db ») Une fenêtre s’ouvre, cliquez sur annuler : nous avons juste crée la base de données, mais elle
ne contient encore aucune table.
Cliquez sur l’onglet « Executer SQL » ; la fenêtre change de forme :
Copier la commande SQL dans la fenêtre SQL1. Exécuter en appuyant sur le triangle vert ou sur F5. Un
message de bonne exécution apparaît.
Nous avons déclaré une table est ses attributs, en précisant le domaine de chaque attribut. On peut ajouter la
définition de la clé primaire, qui évitera les erreurs sur Id. (Pour cela effacer d’abord la table pour en créer une
nouvelle).
Les données sont saisies dans la table, on le vérifie par l’onglet : « Parcourir les données ».
Ecrivez et testez une requête permettant d’obtenir uniquement les titres des livres écrits par Philip K.Dick.
Essayez maintenant des requêtes de ce type :
✞
SELECT titre, auteur, ann_publi
FROM LIVRES
WHERE auteur=’Asimov’ AND ann_publi > 1953
✝ ✆
En triant les résultats :
✞
SELECT titre, ann_publi
FROM LIVRES
WHERE auteur=’Asimov’ ORDER BY ann_publi DESC
✝ ✆
Que se passe-t-il si vous tapez ceci ?
✞
SELECT auteur
FROM LIVRES
✝ ✆
Pour résoudre ce problème, saisissez :
✞
SELECT DISTINCT auteur
FROM LIVRES
✝ ✆
Remarquez que nous aurions pu définir, dans la table LIVRES que le nom de l’auteur se trouve dans une autre
table et utiliser une Foreign Key.
Pour cela remonter sur l’onglet « Structure de la base de données » et cliquez sur la table LIVRES. Supprimer
la table (faites une sauvegarde avant), et Définissez une clé étrangère :
✞
CREATE TABLE LIVRES
(id INT, titre TEXT, id_auteur INT, ann_publi INT, note INT, PRIMARY KEY (id),
FOREIGN KEY (id_auteur) REFERENCES AUTEURS(id));
✝ ✆
✞
INSERT INTO LIVRES
(id, titre, id_auteur, ann_publi, note)
VALUES
(1 , ’1984’, 1, 1949,10),
(2 , ’Dune’, 2, 1965 ,8),
(3 , ’Fondation’, 3, 1951 ,9),
(4 , ’Le meilleur des mondes’,4, 1931 ,7),
(5 , ’Fahrenheit 451 ’, 5, 1953 ,7),
(6 , ’Ubik’, 6, 1969 ,9),
(7 , ’Chroniques martiennes’, 5, 1950 ,8),
(8 , ’La nuit des temps’, 7, 1968 ,7),
(9 , ’Blade Runner’, 6, 1968 ,8),
(10 , ’Les Robots’, 3, 1950 ,9),
(11 , ’La planète des singes’, 8, 1963 ,8),
(12 , ’Ravage ’, 7, 1943 ,8),
Exercice n°16 :
Dans le cas d’uns jointure, il est tout à fait possible de sélectionner certains attributs et pas d’autres.
1. Afficher le nom le prénom et le titre de la table AUTEURS en y joignant les informations de la table
LIVRES
2. Affichez le titre, le nom et le prénom de la table LIVRES e, y joignant les informations de la table
AUTEURS
3. Affichez le titre, le nom et le prénom de la table LIVRES e, y joignant les informations de la table
AUTEURS dont les années de publications sont postérieures à 1950.
Nous avons vu la jointure la plus simple INNER JOIN, il faut savoir qu’il en existe des plus
complèxes (CROSS JOIN, LEFT JOIN, RIGHT JOIN) mais qui ne sont pas au programme
cette année.
Exercice n°17 :
1. En suivant la même logique, supprimer à l’aide de DELETE le livre dont le titre est Hypérion
2. Puis supprimer tout ceux publiés avant 1945.
/* Voici un commentaire
sur plusieurs lignes */
✝ ✆
Les chaînes de caractères sont mises entre simples guillemets ’ :
✞
SELECT * FROM projet
WHERE nom = ’Mon super projet’;
✝ ✆
Les éléments sont nommés en utilisant la notation pointée element.sous_element :
✞
--- afficher la table "projet" se trouvant dans le schéma "public" :
SELECT * FROM public.projet;
✝ ✆
Par habitude, bien que cela ne soit pas obligatoire, les commandes SQL, et plus généralement tous les mots clés
du langage, sont écrits en majuscules. Cela améliore la lisibilité des requêtes.
Les espaces multiples et les retours à la ligne n’ont pas de signification en SQL. N’hésitez pas à les utiliser pour
obtenir une requête SQL plus lisible :
✞
-- je suis sur que vous trouvez celle-ci plus lisible :
SELECT
projet.nom,
ticket.numero,
ticket.titre
FROM
public.ticket
JOIN public.projet ON projet.id = ticket.projet_id
WHERE
projet_id = 10
AND date >= ’2016-01-01’
AND date <= ’2016-01-20’;
-- que celle là :
Toutes les commandes SQL utilisables avec PostgreSQL et leurs syntaxes sont décrites dans la documentation :
http://docs.postgresql.fr/9.6/sql-commands.html.
Création des séquences
✞
CREATE SEQUENCE public.niveau_bug_id_seq;
✝ ✆
Cela va créer la séquence niveau_bug_id_seq dans le schéma public.
✞
ALTER SEQUENCE public.niveau_bug_id_seq OWNED BY public.niveau_bug.id;
✝ ✆
Cela va associer la séquence niveau_bug_id_seq à la colonne id de la table niveau_bug. Ainsi, si la colonne est
supprimée, la séquence sera automatiquement supprimée.
ON DELETE NO ACTION signifie qu’en cas de suppression d’une ligne dans la table utilisateur, si une ligne
de la table commentaire utilise la valeur de la clé primaire de cette ligne dans sa clé étrangère, alors une erreur
est produite.
ON UPDATE NO ACTION signifie qu’en cas de modification de la valeur d’une clé primaire dans la table
utilisateur, si une ligne de la table commentaire utilise cette valeur dans sa clé étrangère, alors une erreur est
produite.
NOT DEFERRABLE signifie que les erreurs (dues au ON DELETE NO ACTION et ON UPDATE NO AC-
TION) sont remontées immédiatement et non à la validation de la transaction. (Nous verrons ce qu’est une
transaction plus loin dans ce cours.)
✞
ALTER TABLE public.bug_version_affectee ADD CONSTRAINT
version_bug_version_affectee_fk
FOREIGN KEY (version\_affectee_projet_id, version_affectee_numero)
REFERENCES public.version (projet_id, numero)
ON DELETE NO ACTION
ON UPDATE NO ACTION
NOT DEFERRABLE;
✝ ✆
Ici la clé étrangère est constituée d’un couple de colonnes (version_affectee_projet_id ;version_affectee_numero)
pointant sur la clé primaire elle aussi constituée de deux colonnes (projet_id ;numero).
Avec cette instruction, je viens d’ajouter l’utilisateur Joe Dalton dans la table utilisateur.
La colonne id a pour valeur par défaut nextval(’utilisateur_id_seq’). Comme je n’ai précisé aucune valeur pour
la colonne id dans la requête INSERT, une valeur a été attribuée automatiquement par PostgreSQL en utilisant
la séquence utilisateur_id_seq.
Les transactions
Il est possible de regrouper les instructions dans une transaction comme s’il s’agissait d’un seul bloc.
Une transaction est ouverte avec l’instruction BEGIN TRANSACTION ; et elle est clôturée de deux manières :
soit en la validant (instruction COMMIT ;)
soit en l’annulant (instruction ROLLBACK ;)
Ainsi, les modifications apportées par les commandes dans la transaction se seront pas visibles en dehors
de la transaction tant que celle-ci n’aura pas été validée. De même, si la transaction est annulée, alors les
modifications ne seront pas appliquées.
Cela est particulièrement utile pour former un ensemble d’instructions cohérent.
Imaginez par exemple que vous vouliez supprimer un projet de la base. À cause des clés étrangères, vous allez
aussi devoir supprimer les lignes des autres tables qui référencent ce projet (ticket, version) et ceci en cascade
(tables bug, evolution, bug_version_affectee...).
Vous allez donc :
démarrer une transaction
faire toutes les requêtes de DELETE de la grappe concernée par le projet (en commençant par les feuilles)
valider la transaction
Ce qui va se faire comme ceci :
✞
BEGIN TRANSACTION; -- Ouverture de la transaction
/*
DELETE FROM bug_version_affectee ...
DELETE FROM bug ...
...
DELETE FROM ticket ...
DELETE FROM version ...
*/
id nom prenom
4 Dalton Joe
5 Dalton William
6 Dalton Jack
7 Dalton Averell
Vous pourrez utiliser les valeurs 4, 5, 6 et 7 dans la colonne responsable_id de la table projet mais aucune autre
valeur.
nom
Bracame
Dalton
Dalton
Dalton
Dalton
Vous pouvez supprimer les lignes en doublon grâce au mot clé DISTINCT :
✞
SELECT DISTINCT nom FROM utilisateur;
✝ ✆
Cette requête ne va plus retourner que 2 lignes :
nom
Bracame
Dalton
Opérateur LIKE
L’opérateur LIKE (et NOT LIKE) permet de faire une recherche par motif dans un texte :
_ correspond à 1 caractère
% correspond à une chaîne de 0 à plusieurs caractères
Voici le résultat des conditions de sélection avec les noms d’utilisateurs Bracame et Dalton :
SELECT ... WHERE Résultat
nom LIKE ’Dal%’ Dalton
nom LIKE ’dal%’
nom LIKE ’%a%’ Bracame, Dalton
nom LIKE ’_a%’ Dalton
nom LIKE ’a’
nom LIKE ’Bracame’ Bracame
Opérateur IN
Les opérateurs IN et NOT IN permettent de vérifier si la valeur est présente ou non dans une liste :
✞
-- Afficher les informations des projets 18 et 42
SELECT * FROM projet
WHERE id IN (18, 42);
Afficher la liste des projets gérés par les utilisateurs dont le nom est « Dalton ».
Voici comment faire avec l’opérateur IN :
✞
-- le croisement se fait sur projet.responsable_id = utilisateur.id
Maintenant que nous avons vu comment fonctionnent les requêtes SQL, nous allons travailler sur les re-
quêtes SQL effectuées depuis un programme Python (Sur les raspberry). Sources : https://pixees.fr/
informatiquelycee/n_site/nsi_term_projet_1.html
conn = sqlite3.connect(’baseDonnees.db’)
cur = conn.cursor()
cur.execute("CREATE TABLE LIVRES(id INT, titre TEXT, auteur TXT, ann_publi INT,
note INT)")
conn.commit()
cur.close()
conn.close()
✝ ✆
Analysons le programme ci-dessus :
Ce programme va vous permettre de vous "connecter" à une base de données (si cette dernière n’existe pas, elle
sera créée). Ensuite nous créons une table (une relation) nommée LIVRES, cette table contient 4 attributs : id
(de type entier), titre (de type texte), auteur (de type texte), ann_publi (de type entier) et note (de type entier).
Entrons un peu dans les détails en analysant le programme ligne par ligne :
✞
import sqlite3
✝ ✆
Nous commençons par importer la bibliothèque sqlite3. Cette bibliothèque va nous permettre d’effectuer des
requêtes SQL sur une base de données. Comme dans le cours sur les bases de données, nous utiliserons le
SGBD SQLite.
✞
conn = sqlite3.connect(’baseDonnees.db’)
cur = conn.cursor()
✝ ✆
Nous créons un objet de type "connection" (conn) qui va nous permettre d’interagir avec la base de données
"baseDonnees.db" (comme dit plus haut, si cette base de données n’existe pas, elle est créée). Vous devriez
donc avoir un fichier "baseDonnees.db" dans le même répertoire que votre fichier Python.
Nous créons ensuite un objet de type "cursor" à partir de l’objet de type "connection". Cet objet de type "cursor"
va nous permettre de manipuler la base de données et d’obtenir des résultats lorsque nous effectuerons des
requêtes.
✞
cur.execute("CREATE TABLE LIVRES(id INT, titre TEXT, auteur TXT, ann_publi INT,
note INT)")
✝ ✆
La méthode "execute" de notre objet de type "cursor" nous permet d’effectuer une requête SQL. Cette requête
SQL est en tout point identique à ce que nous avons vu dans le cours sur les bases de données.
✞
conn.commit()
✝ ✆
Pour véritablement exécuter les requêtes, il est nécessaire d’appliquer la méthode "commit" à l’objet de type
"connection".
56
CHAPITRE 3. BASE DE DONNÉES AVEC PYTHON
✞
cur.close()
conn.close()
✝ ✆
Avant de terminer le programme, il nécessaire de "fermer" l’objet de type "cursor" et l’objet de type "connec-
tion".
Nous allons systématiquement retrouver cette structure dans nos futurs programmes :
❧ création d’un objet de type "connection"
❧ création d’un objet de type "cursor"
❧ préparation d’une ou plusieurs requête(s) (méthode "execute" sur l’objet de type "cursor")
❧ exécution réelle des requêtes (méthode "commit" sur l’objet de type "connection")
❧ "fermeture" de l’objet de type "cursor" puis de l’objet de type "connection"
Si vous exécutez une deuxième fois le programme proposé au "À faire vous-même 1", vous aurez droit à une
erreur : "OperationalError : table LIVRES already exists". Afin d’éviter ce genre de problème, il est possible de
modifier le programme afin que la requête de création de la table LIVRES ne se fasse pas si la table LIVRES
existe déjà :
conn = sqlite3.connect(’baseDonnees.db’)
cur = conn.cursor()
cur.execute("CREATE TABLE IF NOT EXISTS LIVRES(id INT, titre TEXT, auteur TXT,
ann_publi INT, note INT)")
conn.commit()
cur.close()
conn.close()
✝ ✆
Comme vous pouvez le constater, si vous exécutez le programme plusieurs fois de suite, il n’y a plus d’erreur.
conn = sqlite3.connect(’baseDonnees.db’)
cur = conn.cursor()
cur.execute("CREATE TABLE IF NOT EXISTS LIVRES(id INT, titre TEXT, auteur TXT,
ann_publi INT, note INT)")
conn.commit()
cur.close()
conn.close()
✝ ✆
Rien de particulier à signaler, la requête INSERT est identique à ce qui a été vu dans le cours sur les bases de
données.
conn = sqlite3.connect(’baseDonnees.db’)
cur = conn.cursor()
data = (1,’1984’,’Orwell’,1949,10)
cur.execute("CREATE TABLE IF NOT EXISTS LIVRES(id INT, titre TEXT, auteur TXT,
ann_publi INT, note INT)")
cur.execute("INSERT INTO LIVRES(id,titre,auteur,ann_publi,note) VALUES(?, ?, ?,
?, ?)", data)
conn.commit()
cur.close()
conn.close()
✝ ✆
Première chose à remarquer, nous avons créé un tuple (data) contenant toutes les informations. En effet, la
méthode "execute" peut prendre un deuxième paramètre un tuple contenant les données à insérer. Les points
d’interrogation présents dans la requête indiquent l’emplacement des données à insérer. Le premier ? sera rem-
placé par le premier élément du tuple (dans notre cas 1), le deuxième ? sera remplacé par le deuxième élément
du tuple (dans notre cas ’1984’) et ainsi de suite...
Si l’on désire insérer plusieurs données, il est possible de regrouper toutes les données à insérer dans un tableau
et d’utiliser la méthode "executemany" à la place de la méthode "execute".
conn = sqlite3.connect(’baseDonnees.db’)
cur = conn.cursor()
datas = [
(1,’1984’,’Orwell’,1949,10),
(2,’Dune’,’Herbert’,1965,8),
(3,’Fondation’,’Asimov’,1951,9),
(4,’Le meilleur des mondes’,’Huxley’,1931,7),
(5,’Fahrenheit 451’,’Bradbury’,1953,7),
(6,’Ubik’,’K.Dick’,1969,9),
(7,’Chroniques martiennes’,’Bradbury’,1950,8),
(8,’La nuit des temps’,’Barjavel’,1968,7),
(9,’Blade Runner’,’K.Dick’,1968,8),
(10,’Les Robots’,’Asimov’,1950,9),
(11,’La Planète des singes’,’Boulle’,1963,8),
(12,’Ravage’,’Barjavel’,1943,8),
(13,’Le Maître du Haut Château’,’K.Dick’,1962,8),
(14,’Le monde des A’,’Van Vogt’,1945,7),
(15,’La Fin de l éternité’,’Asimov’,1955,8),
(16,’De la Terre à la Lune’,’Verne’,1865,10)
]
cur.execute("CREATE TABLE IF NOT EXISTS LIVRES(id INT, titre TEXT, auteur TXT,
ann_publi INT, note INT)")
cur.executemany("INSERT INTO LIVRES(id,titre,auteur,ann_publi,note) VALUES(?,
?, ?, ?, ?)", datas)
conn.commit()
cur.close()
conn.close()
✝ ✆
conn = sqlite3.connect(’baseDonnees.db’)
cur = conn.cursor()
datas = [
(’1984’,’Orwell’,1949,10),
(’Dune’,’Herbert’,1965,8),
(’Fondation’,’Asimov’,1951,9),
(’Le meilleur des mondes’,’Huxley’,1931,7),
(’Fahrenheit 451’,’Bradbury’,1953,7),
(’Ubik’,’K.Dick’,1969,9),
(’Chroniques martiennes’,’Bradbury’,1950,8),
(’La nuit des temps’,’Barjavel’,1968,7),
(’Blade Runner’,’K.Dick’,1968,8),
(’Les Robots’,’Asimov’,1950,9),
(’La Planète des singes’,’Boulle’,1963,8),
(’Ravage’,’Barjavel’,1943,8),
(’Le Maître du Haut Château’,’K.Dick’,1962,8),
(’Le monde des A’,’Van Vogt’,1945,7),
cur.close()
conn.close()
✝ ✆
Ouvrez le fichier "baseDonnees.db" à l’aide du logiciel "DB Browser for SQLite" et vérifiez que les données
ont bien été ajoutées à la table LIVRES.
Vous pouvez constater que nous avons bien l’attribut "id", même si ce dernier n’a pas été renseigné dans les
données (absence d’id dans le tableau datas). Désormais l’id sera incrémenté automatiquement grâce au "id
INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE" (attention il est nécessaire d’utiliser INTEGER
à la place du INT habituel) présent dans la requête de création de la table LIVRES. Attention, de bien penser
à supprimer un ? dans la requête d’insertion (chaque tuple contient maintenant 4 éléments (nous en avions 5
quand l’id n’était pas géré automatiquement)).
Il est tout à fait possible de rajouter une nouvelle donnée :
conn = sqlite3.connect(’baseDonnees.db’)
cur = conn.cursor()
nvx_data = (’Hypérion’,’Simmons’,1989,8)
cur.close()
conn.close()
✝ ✆
Ouvrez le fichier "baseDonnees.db" à l’aide du logiciel "DB Browser for SQLite" et vérifiez que les données
ont bien été ajoutées à la table LIVRES.
Vous pouvez remarquer que le nouvel enregistrement a bien l’id 17 et que nous n’avons pas eu à nous en
occuper.
Il est possible de modifier des données déjà présentes dans la table.
conn = sqlite3.connect(’baseDonnees.db’)
cur = conn.cursor()
cur.close()
conn.close()
✝ ✆
Ouvrez le fichier "baseDonnees.db" à l’aide du logiciel "DB Browser for SQLite" et vérifiez que les données
ont bien été modifiées dans la table LIVRES.
Comme vous pouvez le constater, il est possible d’utiliser la clause WHERE avec un ?
Il est aussi possible de supprimer une donnée :
conn = sqlite3.connect(’baseDonnees.db’)
cur = conn.cursor()
suppr = (’Hypérion’,)
cur.execute(’DELETE FROM LIVRES WHERE titre = ?’, suppr)
conn.commit()
cur.close()
conn.close()
✝ ✆
Ouvrez le fichier "baseDonnees.db" à l’aide du logiciel "DB Browser for SQLite" et vérifiez que l’entrée "Hy-
périon" a été supprimée de la table LIVRES.
Attention, le deuxième paramètre de la méthode "execute" doit être un tuple, si on écrit seulement suppr =
(’Hypérion’), suppr est une chaine de caractère et pas un tuple. Pour avoir un tuple avec un seul élément il est
nécessaire d’ajouter une virgule (d’où le suppr = (’Hypérion’,))
Enfin, pour terminer cette introduction sur l’utilisation de sqlite en Python, nous devons nous intéresser aux
requêtes de type "SELECT" :
conn = sqlite3.connect(’baseDonnees.db’)
cur = conn.cursor()
liste = cur.fetchall()
cur.close()
conn.close()
✝ ✆
À l’aide de la console, déterminez la valeur référencée par la variable liste
Comme vous pouvez le constater, la variable liste est un tableau qui contient des tuples. Chaque tuple est
un enregistrement de la table LIVRES. La méthode "fetchall" d’un objet de type "cursor" renvoie un tableau
contenant des tuples
Il est possible d’avoir des requêtes plus sélectives :
conn = sqlite3.connect(’baseDonnees.db’)
cur = conn.cursor()
liste = cur.fetchall()
cur.close()
conn.close()
✝ ✆
À l’aide de la console, déterminez la valeur référencée par la variable liste
Vous pouvez constater que l’on obtient bien un tableau contenant des tuples (nous avons bien des tuples même
si seuls les titres ont été sélectionnés)
Il est possible d’utiliser les points d’interrogation dans une requête de type SELECT :
conn = sqlite3.connect(’baseDonnees.db’)
cur = conn.cursor()
recherche = (1960, 8)
cur.execute(’SELECT titre FROM LIVRES WHERE ann_publi < ? AND note > ?’,
recherche)
conn.commit()
liste = cur.fetchall()
cur.close()
conn.close()
✝ ✆
À l’aide de la console, déterminez la valeur référencée par la variable liste
3.16 Projets
3.16.1 Installer flask s’il n’est pas présent
sudo apt-get update
sudo apt-get install python3-pip python3-dev nginx
sudo pip3 install virtualenv
mkdir /flaskproject
cd /flaskproject
virtualenv flaskprojectenv
This will install a local copy of Python and pip into a directory called flaskprojectenv within your project
directory. Before we install applications within the virtual environment, we need to activate it. You can do so
by typing :
source flaskprojectenv/bin/activate
Your prompt will change to indicate that you are now operating within the virtual environment. It will look
something like this (flaskprojectenv)user@host : /flaskproject$.
mouspad flaskproject.py
✞
from flask import Flask
app = Flask(__name__)
@app.route("/")
def greeting():
return "<h1 style=’color:green’>Hello World!</h1>"
if __name__ == "__main__":
app.run(host=’0.0.0.0’)
✝ ✆
sudo ufw allow 5000
python3 flaskproject.py
3.16.2 Projet
A l’aide de tout ces outils, créer une page web qui interagisse avec votre base de données.
En 1975, Gordon E. Moore (cofondateur de la société Intel) énonça la conjecture suivante sur l’évolution des
capacités des circuits intégrés, appelés familièrement puces électroniques :
Dans les microprocesseurs, le nombres de transistors sur une puce va doubler tout les 2 ans.
Bien que fondée sur un constat empirique de l’industrie des fabricants de circuits entre les années 1965 et
1975, cette prédiction, qu’on appelle aussi loi de Moore, s’est révélée incroyablement juste. On est ainsi passé
de 2250 transistors en 1971 sur un microprocesseur Intel 4004 à plusieurs dizaines de milliards aujourd’hui
sur les derniers microprocesseurs, où la taille des transistors n’est que de 7 nanomètres, soit à peine plus que
l’épaisseur de quelques dizaines d’atomes de silicium.
Cette miniaturisation des tous les composants a permis une augmentation de la puissance de calcul des ordina-
teurs, une baisse des coûts, une diminution de leur tension de fonctionnement et donc de leur consommation
énergétique à puissance de calcul équivalente. On peut aujourd’hui rassembler sur une même puce tous les
systèmes embarqués (microprocesseur, mémoire, interfaces d’entrées-sorties, etc...) sur un microcontrôleur.
64
CHAPITRE 4. ARCHITECTURES MATÉRIELLES, SYSTÈMES D’EXPLOITATION ET RÉSEAUX
On peut considérer Alan Turing (1912-1954) comme le père de l’informatique, bien qu’il ait été secondé par
d’autres. Il s’est penché en particulier sur le problème de la calculabilité et a fait le lien avec celui de la
décidabilité en arithmétique, problème posé initialement par le mathématicien David Hilbert en 1928.
En 1936, il présente la machine de Turing, une expérience de pensée qui permet de préciser la notion de
procédé calculable, et à partir de cette notion de définir clairement ce qu’il appelle un programme.
Il démontre l’indécidabilité du problème de l’arrêt en prouvant qu’on ne peut pas répondre à cette question
avec un algorithme. Sa démonstration est liée à la notion de calculabilité qu’il définit par ce qui peut se calculer
mécaniquement avec un algorithme.
Plus tard, Alan Turing participe à un débat sur l’intelligence artificielle et présente ce qu’on appelle le test de
Turing. L’objet central de l’informatique est le calcul. Un algorithme ne traite pas que des nombres, il est aussi
appliqué à toutes sortes d’objets.
Dans tous les cas, un algorithme, et en particulier un calcul sur des nombres, est une succession
de tâches simples appelées opérations, à exécuter dans un ordre précis et qui va produire un
résultat après un nombre fini d’étapes.
Un programme utilisable sur une machine est alors une description de l’algorithme dans un langage (de pro-
grammation) compréhensible par la machine.
Un programme est écrit dans un fichier ce fichier peut être édité et lu. Le contenu peut être du texte,
plus ou moins compréhensible, le code source, qui est une suite d’instructions écrites dans un langage de
programmation. En Python ces instructions sont interprétées, elles sont traduites en des instructions en binaires,
en langage machine. En C, par exemple, le programme est d’abord compilé, un fichier exécutable est créé.
Un programme peut être exécuté seul, ou utilisé par un autre programme Il est alors considéré comme
une donnée par cet autre programme, qui le reçoit en argument.
Définition 12 : Une multiplication peut se décomposer en une série d’additions. Une opération plus complexe
peut se décomposer en une série d’opérations moins complexes, et ainsi de suite jusqu’à obtenir des additions.
Or les additions sont calculables par une machine de Turing.
Définition 13 : fonctions calculables : une fonction f est calculable si on peut obtenir f (u) en un nombre fini
d’étapes pour tout u donné, donc avec une succession d’opérations simples, et que le programme termine.
On peut se demander si un problème peut être résolu par un algorithme. On ramène ce problème à une fonction,
la question est alors de savoir si cette fonction est calculable ou pas.
67
CHAPITRE 5. LANGAGES ET PROGRAMMATION
5.1.2 Décidabilité
La décidabilité est une notion utilisée en logique.
Définition 14 : Soit une proposition, il s’agit de démontrer qu’elle est vraie ou fausse. Si cette démonstration
est possible, on dit que la proposition est décidable. Sinon, on dit que la proposition est indécidable.
Exercice n°1 :
1. Ecrire un programme qui détermine pour n’importe quel entier si cet entier est premier ou pas.
2. Répondre à la question : est-ce qu’il existe un programme qui prend en argument un entier naturel n
quelconque et détermine si cet entier naturel est premier ou pas ?
3. Ce problème est-il décidable ?
5.2 Récursivité
En classe de première, de nombreux programmes ont été écrits avec des boucles et des affectations.
La notion de fonction permet d’éviter de réécrire un jeu d’instructions qui revient plusieurs fois dans le pro-
gramme.
Le style des programmes est alors :
❧ impératif, les séquences d’instructions sont exécutées l’une après l’autre.
❧ itératif, des instructions sont exécutées dans des boucles while ou for.
Une fonction qui s’appelle elle-même va permettre de supprimer par exemple une boucle for. Que fait le
programme suivant :
✞
for i in range(10):
print(i)
✝ ✆
Que vaut i à la sortie du programme ?
Considérons maintenant :
✞
def affiche(k):
if k <10:
print(k)
affiche(k+1)
affiche(0)
✝ ✆
Décrire les étapes de ce programme :
Nous souhaitons effectuer un compte à rebours, par exemple de 5, 4, 3, 2, 1, 0 ; soit le programme suivant :
✞
def rebours(n):
""" n est un entier naturel,
affiche les entiers de n à 0
"""
while n >= 0:
print(n)
n=n- 1
✝ ✆
Regardons maintenant celui-ci :
✞
def rebours2(n):
if n >= 0:
print(n)
rebours2(n - 1) # récursivité terminale
✝ ✆
Décrire les étapes de ce programme, combien y a-t-il eu d’appel à la fonction rebours2 ? Combien y a-t-il
eu de print ?
Et celui là :
✞
def rebours3(n):
if n >= 0:
rebours3(n - 1) # récursivité non terminale
print(n)
✝ ✆
Décrire les étapes de ce programme, combien y a-t-il eu d’appel à la fonction rebours3 ? Combien y a-t-il
eu de print et dans quel ordre ?
En règle générale on fait appel à une fonction récursive quand on sait résoudre le problème à petite échelle, et
qu’on veut le résoudre à grande échelle.
Exemple, on veut calculer la fonction factorielle(n) n!, qui est définie ainsi :
n
f ( n) = n ∗ ( n − 1) ∗ . . . ∗ 2 ∗ 1 = ∏ x
x= 1
et f (0) = 1.
Avec une boucle for on a :
✞
def factorielle(n):
f=1
for i in range(2, n+1):
f=f*i
return f
✝ ✆
Avec une structure récursive :
✞
def factorielle(n):
""" n est de type int positif
renvoie n!
"""
if n > 0:
return n * factorielle(n - 1) # récursivité profonde
else:
return 1
✝ ✆
Décrire les étapes de ce programme pour n=5, combien y a-t-il eu d’appel à la fonction factorielle ? A
quel moment est réalisé le calcul ?
principe généraux :
❧ Une fonction récursive doit contenir une ou des conditions d’arrêt. Sinon le programme
boucle indéfiniment.
❧ Les valeurs passées en paramètres dans les appels récursifs doivent être différentes. Si-
non le programme boucle indéfiniment.
❧ Après un nombre fini d’appels, la ou les valeurs passées en paramètres doivent permettre
de satisfaire la condition d’arrêt.
Exercice n°2 :
1. Ecrire le programme qui calcule la valeur de la suite de Fibonacci pour n = 10 de manière itérative.
2. Ecrire le programme qui calcule la valeur de la suite de Fibonacci pour n = 10 de manière récursive.
3. Déssinez sous forme d’un arbre les différents appels de la fonction, Combien de fois calcule-t-on f(2) ?
Commentez.
5.2.3 Exercices
(simples puis progressif)
Exercice n°3 :
Les seules opérations autorisées sur les nombres entiers sont soit ajouter 1, soit retrancher 1. La fonction somme
renvoie la somme des entiers positifs a et b.
✞
def somme(a,b):
while b > 0:
a=a+1
b=b- 1
return a
✝ ✆
Ecrire une version récursive de cette fonction.
Exercice n°4 :
En s’inspirant de l’exercice précédent, écrire une fonction qui renvoie la somme de deux entiers quelconques.
Les seules opérations autorisées sur les nombres entiers sont soit ajouter 1, soit retrancher 1.
1. Ecrire une fonction somme itérative avec une boucle while.
2. Ecrire une fonction somme_rec récursive non terminale.
3. Ecrire une fonction somme_rec_term récursive terminale.
Exercice n°5 :
On reprend la fonction du cours permettant de savoir si un entier naturel est pair ou non.
✞
def pair(n):
while n > 0:
n=n- 2
return n == 0
✝ ✆
Ecrire une version récursive de cette fonction.
Exercice n°6 :
1. Ecrire une fonction récursive puissance qui prend en paramètres un flottant x non nul et un entier
naturel n et qui renvoie xn . On se base sur la définition mathématique : x0 = 1 et xn = x × xn−1 pour
n > 0.
2. Dans la première question, on se contente de traduire la définition, et la version obtenue n’est pas ré-
cursive terminale. Modifier le code pour que la fonction soit récursive terminale. On peut s’inspirer des
exemples du cours concernant la factorielle.
Exercice n°7 :
Ecrire une fonction récursive puissance qui prend en paramètres un flottant x non nul et un entier naturel
n et qui renvoie xn . Cette fois-ci on note que x0 = 1 et on remarque que si n = 2k, alors xn = x2k = (x2 )k et si
n = 2k + 1 alors xn = x2k+1 = x(x2 )k .
1. Ecrire une première version récursive non terminale.
2. Ecrire une version récursive terminale.
3. Ecrire une version itérative.
Exercice n°8 :
1. Ecrire une fonction récursive somme qui prend en paramètre une liste de nombres et renvoie la somme
des termes de cette liste.
2. Expliquer avec l’exemple somme([4, 7, 2]) le déroulement de l’exécution de cette fonction.( Des-
siner un arbre d’appel.)
3. Que penser du coût en temps et en espace d’une telle fonction ?
Exercice n°9 :
1. Expliquer quel est le résultat renvoyé par la fonction mystere.
✞
def mystere(n):
if n < 2:
return str(n)
else:
return mystere(n//2) + str(n % 2)
✝ ✆
2. Ecrire une fonction récursive binaire qui prend en paramètres un entier relatif r et un entier n stricte-
ment positif, et qui renvoie la représentation en machine de r sur n bits.
3. Compléter le code avec un compteur passé en paramètre afin d’obtenir un résultat composé de n carac-
tères, donc avec éventuellement des 0 au début.
Exercice n°10 :
On programme le tri par insertion de manière récursive. (voir cours de première)
1. Ecrire une fonction récursive insertion qui prend trois paramètres : une liste, un élément de la liste
x et son indice n. Si n est strictement positif, on suppose que les éléments d’indice 0 à n-1 sont triés et
la fonction insère l’élément x à la bonne place.
2. Ecrire la fonction récursive tri_insertion qui prend en paramètres une liste et la longueur de la
liste et qui trie la liste.
Exercice n°11 :
La valeur du pgcd (plus grand commun diviseur) de deux entiers naturels a et b est calculable à l’aide de
l’algorithme d’Euclide : pgcd (a, b) = pgcd (b, r) ou r est le reste dans la division euclidienne de a par b. En
voici une version itérative :
✞
def pgcd(a, b):
if b == 0:
return a
while b != 0:
a, b = b, a % b
return a
✝ ✆
Ecrire une version récursive de cette fonction.
5.3 Corrigés
✞
def premier(n):
"""
un nombre est premier si il n’est divisible que par un ou par lui même
"""
i = n-1
while i > 1 and n % i !=0:
i=i- 1
if i > 1:
return False
else:
return True
✝ ✆
✞
# avec une boucle while:
def fibo1(n):
u, v, cpt = 0 , 1 , 0
while cpt < n:
u, v, cpt = v, u+v, cpt + 1
return u
def fibo3(n):
"""
un fonction récursive doit toujours contenir une condition de sortie
"""
#print(n)
if n == 0 or n== 1:
return n
else:
if (n-1) == 2 or (n-2) == 2:
print(n)
return fibo3(n-1) + fibo3(n-2)
def somme2(a,b):
if b == 0:
return a
else:
return 1 + somme2(a,b-1) # récursif terminal
def somme3(a,b):
if b == 0:
return a
else:
return somme3(a+1,b-1) # récursif non terminal
def pair(n):
while n > 0:
n=n- 2
return n == 0
def pair2(n):
if n <= 1:
return n == 0
else:
return pair2(n-2)
x=10
n=997
print(x, "puissance ",n,"= ", puissance(x,n))
✝ ✆
✞
def puissance(x,n):
if n==0:
return 1
else:
p = puissance(x,n//2)
if n%2 == 0:
return p*p
else:
return x*p*p
elif n % 2:
return a * puis(a * a, n // 2)
else:
return puis(a * a, n // 2)
def puis_it(a,n):
acc = 1
while n!=0:
if n%2:
acc = a*acc
a , n = a * a, n//2
return acc
print(somme([4, 7, 2]))
def somme2(L): # cout linéaire, la liste est modifiée en place, mais elle est d
étruite.
if L == []:
return 0
else:
x = L.pop()
return x + somme2(L)
print(somme2([4, 7, 2]))
✝ ✆
✞
N=8
R= 15
def mystere(n):
if n < 2:
return str(n)
else:
return mystere(n // 2) + str( n % 2 )
print(mystere(R))
def binaire(r,n):
if r < 0:
r = r + 2** n
if r < 2:
return str(r)
else:
return binaire(r//2, n) + str(r%2)
print(binaire(R,N))
print(binaire2(R,N))
✝ ✆
✞
def insertion(liste, n, x):
if n == 0 or x >= liste[n-1]:
liste[n] = x
else:
liste[n] = liste[n-1] # on inverse
insertion(liste, n-1, x)
L = [2, 5,1, 8, 6, 3]
print(L)
insertion(L, 4, 12)
print(L)
tri_insertion_rec(L, len(L))
print(L)
✝ ✆
✞
def pgcd(a,b):
if b == 0:
return(a)
else:
return pgcd(b, a%b)
print(pgcd(100, 90))
✝ ✆
5.4 Modularité
En général, quand on écrit un programme, on essaye de ne pas réécrire ce qui existe déjà.
Soit on utilise des programmes écrits par nous-même. On place des fonctions dans un module ou un pa-
ckage, et on importe ces fonctions avec la commande import monModule (voir cours de première). Soit
on utilise des bibliothèques existantes, par exemple : Pygame, Tkinter, math, time, Turtle,
Matplotlib, Numpy, PIL, Django....
def m1(taille):
forward(taille)
left(60)
forward(taille)
right(120)
forward(taille)
left(60)
forward(taille)
up()
goto (-500, 150)
down()
speed(0)
ht()
width(2)
color(’black’)
courbe(200, 4, m1)
n=int(input())
✝ ✆
En vous inspirant de ce programme, choisissez sur l’image suivante un motif, et réalisez le programme qui le
dessine en permettant de faire varier le niveau de fractale.
Livrez-le selon ce cahier des charges :
❧ expliquez le motif choisi, sa modélisation mathématique ; (un petit historique peut-être ?)
❧ présentez votre algorithme pour sa résolution en précisant si ce dernier est itératif ou récursif ;
❧ en généralisant à un ordre supérieur, quelle est la complexité de votre algorithme ?
❧ Utilisez des fonctions, des modules, mettez en évidence vos tests de fonction et traitement des erreurs.
B F
C D G H
E I J K
Nous pouvons écrire cet arbre sous la forme : a= [’A’, [’B’, [’C’, [], [’E’,[], []] ],[’D’,
[], []]], [’F’, [’G’, [’I’, [], []],[]],[’H’,[’J’, [], []],[’K’, [], []] ]]]
16
17
18 b=Arbre(’A’)
19 b.ajout_gauche(’B’)
20 b.ajout_droit(’F’)
21
22 b.gauche.ajout_gauche(’C’)
23 b.gauche.ajout_droit(’D’)
24 b.gauche.gauche.ajout_droit(’E’)
25
26 b.droit.ajout_gauche(’G’)
81
CHAPITRE 6. ALGORITHMIQUE
27 b.droit.ajout_droit(’H’)
28 b.droit.gauche.ajout_gauche(’I’)
29 b.droit.droit.ajout_gauche(’J’)
30 b.droit.droit.ajout_droit(’K’)
31 print(b)
32 print(b.gauche,b.droit )
33 print(b.gauche.gauche,b.gauche.droit, b.droit.gauche, b.droit.droit )
34 print(b.gauche.gauche.droit, b.droit.gauche.gauche, b.droit.droit.gauche, b.
droit.droit.droit)
✝ ✆
4 def gauche(arbre):
5 if isinstance(arbre, list): # teste si la variable arbre est une liste
6 return arbre[1]
7 else:
8 return arbre.gauche
9
10 def droit(arbre):
11 if isinstance(arbre, list): # teste si la variable arbre est une liste
12 return arbre[2]
13 else:
14 return arbre.droit
15
16
17 def taille(arbre):
18 if vide(arbre):
19 return 0
20 else:
21 return 1 + taille(gauche(arbre)) + taille(droit(arbre))
✝ ✆
Exercice n°12 :
Tester les deux implémentations et vérifier qu’on obtient bien le même résultat pour les deux.
Hauteur Il s’agit de déterminer le nombre maximal de nœuds se trouvant entre la racine et une feuille.
Principe de l’algorithme :
❧ si l’arbre est vide, il contient 0 nœud.
❧ sinon, nous comptons le nœud racine plus le maximum entre le nombre de nboeud du sous-arbre gauche
et le nombre de noeud du sous arbre droit.
Un programme Python peut s’écrire sous la forme :
✞
1 def hauteur(arbre):
2 if vide(arbre):
3 return 0
4 else:
5 return 1 + max(hauteur(gauche(arbre)), hauteur(droit(arbre))) # import
math?
✝ ✆
On peut aussi insérer ces fonction directement dans la classe Arbre, il suffit d’y ajouter :
✞
1 def taille(self):
2 tg = self.gauche.taille() if self.gauche else 0
3 td = self.droit.taille() if self.droit else 0
4 return 1 + td + tg
5
6 def hauteur(self):
7 hg = self.gauche.hauteur() if self.gauche else 0
8 hd = self.droit.hauteur() if self.droit else 0
9 return 1 + max(hg, hd)
✝ ✆
Exercice n°13 :
Tester la classe Arbre avec ces méthodes.
✞
1 def affiche_valeur(arbre):
2 if not vide(arbre):
3 if isinstance(arbre, list):
4 print(arbre[0])
5 else:
6 print(arbre.valeur)
✝ ✆
Nous disposons des fonctions vues plus haut : vide, gauche, droit et affiche_valeur.
Nous définissons alors les fonctions de parcours :
✞
1 def dfs_prefixe(arbre):
2 if not vide(arbre):
3 affiche_valeur(arbre)
4 dfs_prefixe(gauche(arbre))
5 dfs_prefixe(droit(arbre))
6
7 def dfs_infixe(arbre):
8 if not vide(arbre):
9 dfs_infixe(gauche(arbre))
10 affiche_valeur(arbre)
11 dfs_infixe(droit(arbre))
12
13 def dfs_postfixe(arbre):
14 if not vide(arbre):
15 dfs_postfixe(gauche(arbre))
16 dfs_postfixe(droit(arbre))
17 affiche_valeur(arbre)
✝ ✆
Exercice n°14 :
En utilisant les deux arbres a et b précédents :
1. Executer dfs_prefixe(a) et dfs_prefixe(b), notez le résultat.
2. Executer dfs_infixe(a) et dfs_infixe(b), notez le résultat.
3. Executer dfs_postfixe(a) et dfs_postfixe(b), notez le résultat.
4. Légendez les schémas ci-dessous, en indiquant en rouge le sens de parcourt de l’arbre.
B F
C D G H
E I J K
B F
C D G H
E I J K
B F
C D G H
E I J K
16 def taille(self):
17 tg = self.gauche.taille() if self.gauche else 0
18 td = self.droit.taille() if self.droit else 0
19 return 1 + td + tg
20
21 b=Arbre(’A’)
22 b.ajout_gauche(’B’)
23 b.ajout_droit(’F’)
24
25 b.gauche.ajout_gauche(’C’)
26 b.gauche.ajout_droit(’D’)
27 b.gauche.gauche.ajout_droit(’E’)
28
29 b.droit.ajout_gauche(’G’)
30 b.droit.ajout_droit(’H’)
31 b.droit.gauche.ajout_gauche(’I’)
32 b.droit.droit.ajout_gauche(’J’)
33 b.droit.droit.ajout_droit(’K’)
34 #-----
35 from queue import *
36
37 def parcours_largeur(arbre):
38 f = Queue(arbre.taille()) # taille de la file
39 f.put(arbre) # l’arbre est placé dans la file
40 k=0
41 while not f.empty():
42 a = f.get() # un élément est retiré de la file
43 print(a.valeur, end = " ")
44 if a.gauche is not None:
45 f.put(a.gauche)
46 if a.droit in note None:
47 f.put(a.droit)
✝ ✆
Exercice n°15 :
1. Tester le résultat de parcours_largeur(b)
2. Représenter les différentes étapes, avec le contenu de la file :
14 else:
15 self.gauche.ajoute(valeur)
16 elif valeur > self.valeur:
17 if self.droit is None:
18 self.droit = ABR(valeur)
19 else:
20 self.droit.ajoute(valeur)
21
22 monArbre = ABR(17)
23 monArbre.ajoute(10)
24 monArbre.ajoute(23)
25 monArbre.ajoute(19)
26 monArbre.ajoute(25)
27 monArbre.ajoute(2)
28 monArbre.ajoute(20)
29 monArbre.ajoute(8)
✝ ✆
Pour rechercher une valeur dans l’arbre, on cré une méthode recherche qui repose sur le même principe que
l’insertion d’une valeur dans l’arbre :
✞
1 class ABR:
2 ...
3
4 def recherche(self, val):
5 if val < self.valeur:
6 return self.gauche.recherche(val) if self.gauche else False
7 elif val > self.valeur:
8 return self.droit.recherche(val) if self.droit else False
9 return True # la méthode renvoie True si val == self.valeur
✝ ✆
Exercice n°16 :
1. Tester la méthode en recherchant les valeurs 19 et 18.
2. Est-ce que l’intégralité de l’arbre est parcouru pour trouver la valeur ? A quoi cela fait-il penser ?
3. Entrer un nouvel arbre avec les valeurs : 5, 8, 11, 13, 15, 18. Quelle sont la taille et la hauteur de cet
arbre ? (Réutiliser les fonctions vues précédemment)
4. Entrer un nouvel arbre avec les valeurs : 13, 8, 5, 11, 15, 18. Quelle sont la taille et la hauteur de cet
arbre ? Que constatez-vous ?
Exercice n°17 :
1. Ecrire vous-même la méthode qui trouve le maximum.
2. Tester ces deux méthodes.
Exercice n°18 :
1. Créer un arbre rempli de 100 nombres aléatoires distincts compris entre 1 et 100.
2. Combien d’étapes sont nécessaires pour trouver la valeur 25 ?
3. Modifier le programme pour faire en sorte que l’arbre soit équilibré.
4. Combien d’étapes sont nécessaires pour trouver la valeur 25 ? Conclure.
6.3 Applications
Un exemple d’utilisation des ABR est le codage de Huffmann.
Codage de Huffman est un algorithme de compression de données sans perte. Le codage de Huff-
man utilise un code à longueur variable pour représenter un symbole de la source (par exemple un
caractère dans un fichier). Le code est déterminé à partir d’une estimation des probabilités d’appa-
rition des symboles de source, un code court étant associé aux symboles de source les plus fréquents.
Un code de Huffman est optimal au sens de la plus courte longueur pour un codage par symbole,
et une distribution de probabilité connue. Des méthodes plus complexes réalisant une modélisation
probabiliste de la source permettent d’obtenir de meilleurs ratios de compression.
(wikipédia)
Prenons un texte quelconque. Si nous utilisons le codage ASCII étendu à 256 caractères pour avoir les caractères
accentués de la langue française, chaque caractère peut être codé sur un octet. Donc un texte de 1000 caractères
nécessite 1000 octets.
Le principe du codage de Huffmann est le suivant :
Plutôt que de coder chaque caractère sur 8 bits, on utilise moins de bits pour les caractères les plus fréquents et
plus de bits pour les caractères les moins fréquents.
Considérons un exemple simple. Un texte ne contient que les quatre caractères suivants : "A", "Z", "E", "R".
Le "E" est présent 500 fois, le "R" est présent 300 fois, le "A" est présent 150 fois, le "Z" est présent 50 fois.
Nous commençons par le caractère le plus fréquent et codons le "E" par 0. Nous ne pouvons pas coder le "R"
avec la valeur 1 ou 01, en effet une suite de bits débutant par 0 est alors ambigüe : est-ce "ER" ou est-ce un autre
caractère commençant par 1 ? L’astuce est de dire que "R" est codé 10, les autres caractères devront commencer
par 11.
Finalement le codage est le suivant :
"A" "Z" "E" "R"
110 111 0 10
Considérons une suite quelconque : 100110111010 et vérifions que le codage fonctionne.
Pour un texte de 1000 caractères, nous obtenons donc : 500 ∗ 1 + 300 ∗ 2 + (150 + 50) ∗ 3 = 1700 bits au lieu
des 8000 bits standard. On a donc compressé l’information sans perte.
Remarque : en utilisant 00, 01, 10, 11, on aurait obtenu : 1000∗2 = 2000 bits, ce qui aurait été moins performant
en terme de compression.
Remarque 2 : le rapport 8000 2000
1700 ou 1700 s’appelle le taux de compression. Dans le premier cas il est de 4,7 alors
que dans le second cas il est de 1,18.
Voici l’arbre correspondant à notre exemple :
1000
0 1
E : 500 500
0 1
R : 300 200
0 1
A : 150 Z :50
Exercice n°19 :
Dessiner un arbre binaire de recherche (sur le papier) obtenu dans les 2 cas qui suivent :
1. en insérant dans l’ordre les valeurs suivantes : 17, 25, 11, 13, 23, 5, 8, 28.
2. en insérant dans l’ordre les valeurs suivantes : 17, 5, 28, 11, 13,23, 8, 25.
Conclure.
Exercice n°20 :
On recherche le nombre 28 dans un arbre binaire de recherche.
1. La suite des nœuds parcourus est : 13, 22, 35, 31, 29, 23, 25, 28. Est-ce possible ?
2. La suite des nœuds parcourus est : 13, 23, 35, 31, 29, 22, 25, 28. Est-ce possible ?
Exercice n°21 :
On définit une méthode de recherche de la class ABR :
✞
1 class ARB:
2 ...
3 def recherche(self, val):
4 while self.valeur is not None:
5 if val < self.valeur:
6 if not self.gauche: return False
7 self = self.gauche
8 elif val > self.valeur:
9 if not self.droit : return False
10 self = self.droit
11 else: return True
✝ ✆
Prouver la correction de ce programme. (Rappel : pour prouver la correction d’un programme itératif, il faut
utiliser un invariant de boucle.
Exercice n°22 :
Successeur et prédécesseur.
1. On reprend la classe ABR en ajoutant l’attribut self.parent = None. Modifier les méthodes en
conséquence.
2. Un nœud est un enfant gauche s’il a un parent et qu’il est la racine du sous-arbre gauche de son parent.
Ecrire une méthode enfant_gauche qui renvoie True si le nœud concerné est un enfant gauche et
False sinon.
3. De même écrire enfant_droit.
Les valeurs des noeuds contenues dans l’arbre peuvent être rangées par ordre croissant. Une valeur non extrême
a une valeur qui lui succède et une qui lui précède. Les noeuds correspondants s’appellent respectivement
successeur et prédécesseur.
1. Ecrire une méthode successeur qui renvoie le successeur d’un nœud. Si le nœud a un fils droit, le
successeur est le nœud le plus à gauche du sous-arbre droit, sinon c’est le premier ascendant tel que le
nœud est dans son sous-arbre gauche.
2. Ecrire une méthode predecesseur qui renvoie le prédécesseur d’un nœud.
Exercice n°23 :
1. Implémenter le codage de Huffmann. (Attention, ne prenez pas un texte en UTF-8, ou alors tenez-en
compte)
2. Comment joindre l’arbre de compression ?
3. Tester le taux de compression sur quelques gros fichiers texte.
4. Pour aller plus loin : implémenter la décompression.
Nous montrons que la propriété « la valeur cherchée appartient à l’arbre courant » est un invariant de boucle.
L’arbre courant est représenté par self. Si la valeur recherchée appratient à cet arbre, celui-ci a une racine.
Soit la valeur cherchée est égale à cette racine et le programme s’arrête ; soit elle est strictement inférieure à
cette racine et appartient donc au sous-arbre gauche ; soit elle est supérieure à cette racine et appartient donc
au sous-arbre droit. Donc si il y a un nouveau passage dans la boucle, la valeur cherchée appartient encore à
l’arbre courant.
Si la valeur cherchée n’appartient pas à l’arbre courant, elle n’est pas égale à la racine et n’appartient ni au sous-
arbre gauche, ni au sous-arbre droit. Donc elle n’appartient pas à l’arbre courant s’il y a un nouveau passage
dans la boucle, le programme s’arrête lorsque l’arbre courant n’a pas de sous-arbre gauche alors qu’on cherche
une valeur inférieure, ou pas de sous-arbre droit alors qu’on cherche une valeur supérieure à la racine courante.
Exercice n°24 :
1. Décrivez ce que fait le programme.
7 if __name__ == "__main__":
8 mot = "ressasser"
9 palindrome2( mot, 0, len(mot)-1)
✝ ✆
Exercice n°25 :
1. Décrivez ce que fait le programme, en mettant en avant les différences avec le précédent.
1. Etude de la terminaison de l’algorithme : est-ce que la fonction renvoie effectivement une valeur ?
Au départ, l’intervalle d’étude est l’ensemble {0, 1, ..., N-1} de longueur N. Après chaque itération
dans la boucle while la longueur de l’intervalle est divisé par 2. Supposons donc que N = 2n , c’est un
multiple de 2.
1
Notons li cette longueur à chaque itération. La suite est géométrique de raison et de premier terme N,
2
soit :
N 2n
li = i = i = 2n−i
2 2
n
(Un = U0 × q = Un0 × q 0 ) n−n
❧ la liste à triée est partagée en deux parties égale à une unité près ;
❧ Un appel récursif est alors réalisé sur chacune des deux parties ;
❧ lorsque les deux parties sont triées, elles sont fusionnées en une liste triée.
Exercice n°26 :
Soit une liste d’entiers : [5, 6, 8, 9, 3, 4, 8]
1. Implémenter un tri par fusion récursif ;
2. Implémenter un tri par fusion itératif ;
3. Un tri en place se fait en modifiant les valeurs de la liste directement, un tri qui n’est pas en place trie une
liste secondaire et la renvoie. Comparez vos solutions avec celles de vos voisins, reconnaissez quel sont
les algorithmes qui sont en place et ceux qui ne le sont pas.
4. Quel est l’intérêt de réaliser un tri en place ?
Exos :
97
Chapitre 8 Programme officiel
8.1 Préambule
♣
L’enseignement de spécialité de numérique et sciences informatiques du cycle terminal de la voie générale
vise l’appropriation des fondements de l’informatique pour préparer les élèves à une poursuite d’études en
les formant à la pratique d’une démarche scientifique et en développant leur appétence pour des activités de
recherche.
L’objectif de cet enseignement général est l’appropriation des concepts et des méthodes qui fondent l’informa-
tique, dans ses dimensions scientifiques et techniques. Il s’appuie sur l’universalité de quatre concepts fonda-
mentaux et la variété de leurs interactions :
les données , qui représentent sous une forme numérique unifiée des informations très diverses : textes, images,
sons, mesures physiques, sommes d’argent, etc. ;
les algorithmes , qui spécifient de façon abstraite et précise des traitements à effectuer sur les
les langages , qui permettent de traduire les algorithmes abstraits en programmes textuels ou graphiques de
façon à ce qu’ils soient exécutables par les machines ;
les machines , et leurs systèmes d’exploitation, qui permettent d’exécuter des programmes en enchaînant un
grand nombre d’instructions simples, assurent la persistance des données par leur stockage et gèrent les
communications. Y sont inclus les objets connectés et les réseaux.
À ces concepts s’ajoute un élément transversal : les interfaces qui permettent la communication, la collecte des
données et la commande des systèmes.
Cet enseignement prolonge les enseignements d’informatique dispensés à l’école primaire, au collège en ma-
thématiques et en technologie et, en classe de seconde, l’enseignement commun Sciences numériques et tech-
nologie. Il s’appuie aussi sur l’algorithmique pratiquée en mathématiques en classe de seconde. Il approfondit
les notions étudiées et les compétences travaillées en classe de première dans l’enseignement de spécialité. Il
autorise tous les choix de couplage avec les autres spécialités.
L’enseignement de spécialité de numérique et sciences informatiques permet de développer les compétences
suivantes, constitutives de la pensée informatique :
❧ analyser et modéliser un problème en termes de flux et de traitement d’informations ;
❧ décomposer un problème en sous-problèmes, reconnaître des situations déjà analysées et réutiliser des
solutions ;
❧ concevoir des solutions algorithmiques ;
❧ traduire un algorithme dans un langage de programmation, en spécifier les interfaces et les interactions,
comprendre et réutiliser des codes sources existants, développer des processus de mise au point et de
validation de programmes ;
❧ mobiliser les concepts et les technologies utiles pour assurer les fonctions d’acquisition, de mémorisation,
de traitement et de diffusion des informations ;
❧ développer des capacités d’abstraction et de généralisation.
Cet enseignement se déploie en mettant en activité les élèves, sous des formes variées qui permettent de déve-
lopper des compétences transversales :
❧ faire preuve d’autonomie, d’initiative et de créativité ;
❧ présenter un problème ou sa solution, développer une argumentation dans le cadre d’un débat ;
❧ coopérer au sein d’une équipe dans le cadre d’un projet ;
❧ rechercher de l’information, partager des ressources ;
❧ faire un usage responsable et critique de l’informatique.
98
CHAPITRE 8. PROGRAMME OFFICIEL
La progression peut suivre un rythme annuel construit autour de périodes spécifiques favorisant une alternance
entre divers types d’activités.
L’enseignement de numériques et sciences informatiques permet l’acquisition des compétences numériques qui
font l’objet d’une certification en fin de cycle terminal. Comme tous les enseignements de spécialité, il contribue
au développement des compétences orales à travers notamment la pratique de l’argumentation. Celle-ci conduit
à préciser sa pensée et à expliciter son raisonnement de manière à convaincre. Elle permet à chacun de faire
évoluer sa pensée, jusqu’à la remettre en cause si nécessaire, pour accéder progressivement à la vérité par la
preuve.
Comme tous les concepts scientifiques et techniques, ceux de l’informatique ont une histoire et ont été forgés
par des personnes. Les algorithmes sont présents dès l’Antiquité, les machines à calculer apparaissent pro-
gressivement au XVIIe siècle, les sciences de l’information sont fondées au XIXe siècle, mais c’est en 1936
qu’apparaît le concept de machine universelle, capable d’exécuter tous les algorithmes, et que les notions de
machine, algorithme, langage et information sont pensées comme un tout cohérent. Les premiers ordinateurs
ont été construits en 1948 et leur puissance a ensuite évolué exponentiellement. Parallèlement, les ordinateurs
se sont diversifiés dans leurs tailles, leurs formes et leurs emplois : téléphones, tablettes, montres connectées, or-
dinateurs personnels, serveurs, fermes de calcul, méga-ordinateurs. Le réseau internet, développé depuis 1969,
relie aujourd’hui ordinateurs et objets connectés.
Contenus Capacités attendues Commentaires
Événements clés de l’histoire de Situer dans le temps les principaux Ces repères viennent
l’informatique. événements de l’histoire de compléter ceux qui ont été
l’informatique et leurs introduits en première.
protagonistes. Ces repères historiques sont
construits au fur et à mesure
Identifier l’évolution des rôles de la présentation des
relatifs des logiciels et des concepts et techniques.
matériels.
8.2.6 Algorithmique
Le travail de compréhension et de conception d’algorithmes se poursuit en terminale notamment via l’intro-
duction des structures d’arbres et de graphes montrant tout l’intérêt d’une approche récursive dans la résolution
algorithmique de problèmes. On continue l’étude de la notion de coût d’exécution, en temps ou en mémoire et
on montre l’intérêt du passage d’un coût quadratique en n2 à n log 2 n ou de n à log 2 n. Le logarithme en base
2 est ici manipulé comme simple outil de comptage (taille en bits d’un nombre entier).