Vous êtes sur la page 1sur 12

Cours Algorithmique et Programmation

Cours-pc4

September 28, 2010

1 Structures de données : exercices


Pour tous les codes python, on privilégiera la programmation récursive des
fonctions.

Recherche de motifs et automates


Dans cet exercice, on va effectuer la recherche exacte d’un motif en utilisant
un automate. L’intérêt est d’éviter la relecture plusieurs fois d’une même
lettre de la séquence de référence. L’idée directrice est, au fur et à mesure de
la lecture des lettres de la séquence, de se souvenir des lettres lues en fonction
des lettres du motif et stocker l’information au moyen d’un automate. Dans
cet exercice, on va se placer dans le cadre restreint où les mots représentant le
motif et la séquence dans laquelle nous recherchons le motif, sont construits
sur l’alphabet {A, T, G, C}. La recherche d’un motif correspond alors à la
recherche de la présence d’un gène (suite particulière de nucléotides) dans un
brin d’ADN, vu comme la séquence de nucléotides. Un point de vue intuitif
et imagé d’un automate :

• Labyrinthe ou jeu de piste avec un point de départ, des points de rendez-


vous (points intermédiaires) et une arrivée gagnante (unique dans notre
cas).

• Les points de rendez-vous sont appelés des états.

1
• Dans chaque état, selon la nature de la prochaine lettre lue dans la
séquence A, C , G ou T (vue comme un ticket), il y a un changement
d’état.

• L’état suivant est uniquement déterminé par les trois informations (état
courant, ticket lu, motif)

• Il y a (k+1) états si le motif est de longueur k (pour l’automate mini-


mal).

Ces états peuvent être notés, pour le motif m(1)m(2) . . . m(k) par

• E0 ou état initial (de départ)

• Em(1) ou 1er état (la première lettre du motif a été reconnue)

• Em(1)m(2) ou second état (les deux premières lettres du motif ont été
lues dans l’ordre, ... et on ne fait pas plus)

• ...

• Em(1)m(2)...m(k) ou état final (gagnant) : le motif a été complètement lu

Les états de l’automate sont liés entre eux par des arcs étiquetés par les
tickets lus. Un arc est donc modélisé par un triplet (état courant, ticket, état
suivant). Pour tous les états (hors l’état gagnant) e et pour tous les tickets
t, il existe un arc (e, t, e′ ) dans l’automate indiquant le changement d’état.
L’algorithme permettant de construire l’automate à partir d’un motif est
alors le suivant:

• de l’état initial E0 , si on lit m(1) alors on passe à l’état Em(1) , sinon on


reste à l’état E0 .

• de l’état Em(1)m(2)...m(i) (i < k), si on lit la lettre m(i + 1), alors on


passe à l’état Em(1)m(2)...m(i+1) .

• de l’état Em(1)m(2)...m(i) (i < k), si on si lit une lettre l différente de


m(i + 1), alors on passe à l’état Em(1)...m(j) tel que le mot m(1) . . . m(j)
est le plus long préfixe (éventuellement réduit au mot vide) dans le
motif m suffixe de m(1)m(2) . . . m(i)l.

2
Pour représenter les automates, nous utiliserons des listes à double entrée ou
des dictionnaires python.
À titre d’exemple, donnons ci-dessous l’automate correspondant au motif
AAT AAC :

E0 EA EAA EAAT EAAT A EAAT AA EAAT AAC


A EA EAA EAA EAAT A EAAT AA EAA X
T E0 E0 EAAT E0 E0 EAAT X
C E0 E0 E0 E0 E0 EAAT AAC X
G E0 E0 E0 E0 E0 E0 X

(le cas X est un cas exceptionnel : peu importe l’état suivant de l’état gag-
nant: il y a arrêt de la recherche dans l’état gagnant. Il y a en fait deux
options classiques, soit rester dans l’état final (et alors X vaut ici EAAT AAC )
ou bien réamorcer la lecture du motif.)

• Question 1 : Écrire la fonction python qui à partir d’un motif con-


struit l’automate selon l’algorithme ci-dessus. Pour cela, écrire les fonc-
tions suivantes :

– prefixe(m1 , m2 ) qui teste si la chaı̂ne m1 est préfixe de m2 .


– suf longpref(m1 , m2 ) qui calcule le plus grand suffixe de m1 appa-
raissant en début de m2 .
– automate(m1 , m2 ) qui calcule l’automate associé au motif m2 , sachant
que le motif m1 , préfixe de m2 a été reconnu (algorithme ci-dessus)

• Question 2 : Écrire la fonction python qui à partir d’une séquence s


et d’un automate construit à partir d’un motif m, dit si m est un motif
de s. L’idée directrice de l’algorithme est de partir de la séquence s
et de l’état initial de l’automate et lettre après lettre de la séquence s
parcourir l’automate. Si on atteint l’état final de l’automate avant la
fin de la séquence s, alors le motif m a été reconnu dans la séquence s.

Approfondissement : Pour plus de détails concernant la recherche de


motif dans une chaı̂ne par automates, nous vous invitons à étudier
le chapitre 1 du polycopié de Matthieu Raffinot disponible sur
Claroline. Différents algorithmes ainsi que l’étude de leur com-
plexité y sont décrits.

3
Exercice sur la structure de données Pile
Les piles définissent une structure de données de stockage qui suit une poli-
tique LIFO (Last In, First Out) d’ajout et de retrait d’élément. Un moyen
simple d’implanter la structure de données des piles est d’utiliser les listes
python

1. En supposant que les piles ont été implantées au moyen des listes
python, écrire les fonctions usuelles d’ajout (empiler(e,P)) et de retrait
(depiler(P)) ainsi que la fonction sommet(P) qui rend le sommet de la
pile et la fonction estVide(P) qui teste si la pile est vide.
2. Evaluation des expressions arithmétiques postfixées.
On suppose manipuler des expressions arithmétiques sans variable définies
à partir des 4 opérations arithmétiques +, −, ×, ÷ et des entiers. La
notation postfixée consiste à mettre les opérations arithmétiques après
leurs 2 arguments. Ainsi, une expression arithmétique de la forme n•m
ou • ∈ {+, −, ×, ÷} a pour notation postfixée l’expression n m •. On
suppose que les expressions arithmétiques postfixées sont stockées dans
une liste de chaı̂nes de caractères où chacune des chaı̂nes de caractères
représente soit un entier soit une opération arithmétique. En utilisant
une pile, écrire une fonction qui à partir d’une expression arithmétique
en notation postfixée, évalue cette expression et retourne le résultat.

Test de bon parenthésage


On considère une expression arithmétique dont les éléments appartiennent à
l’alphabet suivant : A = {0, ..., 9, +, −, ∗, /, (, ), [, ]}
• Ecrire un algorithme qui permet de vérifier la validité des parenthèses
et des crochets contenus dans une expression arithmétique.
• Justifiez le choix de la structure de données utilisée.

Exercice sur la structure de données File


Les files définissent une structure de données de stockage qui suit une poli-
tique FIFO (First In, First Out) d’ajout et de retrait d’élément. Un moyen
simple d’implanter la structure de données des files est d’utiliser les listes
python.

4
1. En supposant que les files ont été implantées au moyen des listes
python, écrire les fonctions usuelles d’ajout et de retrait ainsi que les
deux fonctions qui respectivement rendent le premier élément de la file
et testent si la file est vide.

2. On se propose maintenant d’utiliser la structure de données des files


pour gérer un cabinet médical,i.e. les files d’attente des médecins d’un
cabinet au cours d’une journée.

• On commence par définir une file de Rendez-vous pour un médecin


donné. On doit pouvoir considérer une file vide en début de
journée du médecin (opération vide), ajouter un patient dans la
file d’attente (opération ajoute) et supprimer le premier patient
de la file lorque son cas a été traité (opération traite). Ecrire les
fonctions python implantant les trois opérations ci-dessus. On
supposera qu’un patient est défini par un tuple composé de trois
arguments (n, s, t) où n est une chaı̂ne de caractères désignant le
nom du patient, s est une booléen à True si la patient souffre et à
False sinon, et t est un entier positif qui dénote la température du
patient.
• On se place maintenant du point de vue de la réception du cabi-
net médical qui doit insérer les patients dans la file de rendez-vous
d’un médecin au fur et à mesure qu’ils arrivent. On suppose que
la liste des files d’attente des médecins du cabinet est modélisée
par un dictionnaire dont les indexes sont les noms des médecins.
La reception dispose d’une opération insere qui insère un patient
dans une file de rendez-vous d’un médecin en respectant les partic-
ularités suivantes : les patients prioritaires doivent être placés de
façon à être traités avant tout autre patient non prioritaire dans
la file. De plus, les patients prioritaires doivent être traités les
uns par rapport aux autres en suivant leur ordre d’arrivée et il en
est de même des patients non prioritaires. Un patient sera dit dit
prioritaire s’il souffre ou si sa température est inférieure à 36 ou
si elle est supérieure à 39. Ecrire la fonction python qui implante
insere
• On ajoute la fontion nbTotal qui compte le nombre de patients
dans une file de rendez-vous d’un médecin donné.

5
• On ajoute la fonction nbUrgences qui compte le nombre de patients
prioritaires dans une file de rendez-vous d’un médecin.
• On ajoute une nouvelle opération inserePatient qui insère un nou-
veau patient dans les files de rendez-vous selon les critères suivants
:
– L’ordre relatif des patients dans la file de rendez-vous de
chaque médecin doit vérifier les mêmes conditions que précedemment.
– Si un patient prioritaire arrive, il doit être placé dans la file
de rendez-vous du médecin qui a le moins de cas prioritaires
en attente.
– Si un patient non prioritaire arrive, il doit être placé dans la
file de rendez-vous du medecin qui a le moins de patients en
attente.

Immatriculation
Considérons une application de préfecture consistant à maintenir une base
de données des véhicules immatriculés et de leur propriétaire. Les plaques
d’immatriculation sont de la forme (n1 , c1 c2 c3 , n2 ) avec :
• n1 un chiffre compris entre 0001 et 9999

• c1 , c2 , c3 sont des caractères compris entre A et Z. c3 peut être 0

• n2 est un chiffre compris entre 01 et 95


La base de données se base sur une table de hachage dont la fonction de
hachage est :
′ ′ ′
h(n1 , c1 , c2 , c3 , n2 ) = n1 + (h (c1 ) + h (c2 ) + h (c3 )) + n2 et

h est la fonction qui associe 1 au caractère A, 2 au caractère B, ... 26 au
caractère Z et 0 à 0.

1. Quel est la valeur de hachage de :

• 1846 RC 69
• 8626 BD 69
• 2536 ARC 30
• 1789 KTZ 90

6
Quel problème cela pose ?

2. Trouvez une façon naı̈ve de résoudre ce problème et donnez la com-


plexité pour rechercher un élément dans le pire des cas.

2 Structures de données : problèmes


Réseaux sans fil (d’après Christoph Dürr )
L’objectif de ce problème est d’étudier les tables de hachage ou tables d’association.
Pour cela nous considérons une application des réseaux mobiles. Soient N
personnes réparties un peu n’importe comment sur un terrain, et équipées
d’un ordinateur portable avec wifi. Deux personnes sont voisines, c’est-à-
dire peuvent communiquer si et seulement si leur distance est inférieure ou
égale à une distance R, qui représente la portée du wifi d’un ordinateur. Le
but est maintenant de trouver qui est voisin avec qui. Formellement on reçoit
un tableau de N sites de coordonnées non-négatives (x, y), un rayon R, et
on doit fournir la liste de toutes les paires de sites voisins.

Approche naı̈ve
L’approche naı̂ne consiste à tester les O(N 2 ) paires.

1. Ecrire une fonction loadData(nomfich) qui permet d’ouvrir un fichier


contenant les coordonnées des sites ainsi que le rayon et qui retourne
une liste des sites du problème ainsi que le rayon. Des fichiers de
données sont disponibles sur Claroline. La première ligne du fichier
donne les dimensions du terrain considéré, le rayon R ainsi que le nom-
bre de sites.

2. Ecrire une fonction distanceTo(site1, site2) qui calcule la distance eucli-


dienne entre deux sites.

3. Ecrire une fonction findCloseSites(sites : Liste) qui trouve les paires de


sites voisins et les affiche. L’approche naı̈ve consiste à tester toutes les
N (N −1)
2
paires de sites.

7
Approche efficace : avec une table de hachage
Pour trouver les sites voisins, une approche efficace consiste à découper la
zone suivant une grille dont chaque cellule est de dimension R×R. Deux sites
voisins sont alors soit dans la même cellule soit dans deux cellules adjacentes.
On va alors créer une table qui associe à chaque cellule la liste des sites
qu’elle contient. L’algorithme va alors consister à tester pour chaque site s
la distance avec tous les sites contenus dans la même cellule que s ainsi que
dans chacune des huit cellules adjacentes. La complexité de cet algorithme
est aussi de O(N 2 ) dans le pire des cas, i.e. tous les sites localisés dans
la même cellule. On peut raisonnablement supposer qu’ils sont distribués
de telle manière que le nombre de points par cellule est borné par une con-
stante. Dans ce cas, la complexité de l’algorithme est de O(N ), ce qui est une
amélioration considérable. Certaines tailles d’instances peuvent être traités
dans l’ordre d’une seconde, là où l’algorithme en O(N 2 ) mettrait plus d’une
semaine.

1. On va utiliser la fonction de hachage suivante pour répartir les sites


dans une cellule donnée : h(x, y) = d(d+1)
2
+ Rx avec d = Rx + Ry entier
(attention : divisions entières). Cette fonction de hachage découpe le
plan en des cellules de surface RxR numérotées comme sur la figure ci-
dessus. Ecrire une fonction hachage (x,y,R) qui étant donné un site et
un rayon retourne le numéro de la cellule dans lequel ce site se trouve.

2. Une cellule pouvant contenir plusieurs sites, les valeurs de notre table
d’associations seront donc des listes de sites. Pour commencer il nous
faut une structure de données qui associe à chaque cellule la liste des
sites qu’elle contient. Ecrire une méthode cellules(liste,R) qui place les
sites dans les cellules selon la fonction de hachage et qui retourne ces
informations dans une structure adaptée.

8
3. Ecrire une fonction qui permet de trouver tous les voisins d’un site s
donné avec l’approche efficace puis une fonction qui calcule l’ensemble
du voisinage des sites entre eux. Le principe est pour chaque site s
de chercher dans les neufs cellules environnantes (celle de s inclus) des
voisins. (Astuce : pour cela créez des sites temporaires aux coordonnées
(s.x − R, s.y − R), (s.x, s.y − R), (s.x, s.y + R), (s.x − R, s.y), etc. et
parcourez la liste associée à leurs cellules)

2.1 Velib (d’après Christoph Dürr )


L’objectif de ce problème est de simuler une journée d’activités du système
des Velibs.

• Un vélo est représenté par un identifiant entier unique strictement posi-


tif.

• Une station est identifiée par son nom. Elle contient un certain nombre
d’emplacements; chaque emplacement peut être vide ou contenir un
vélo.

• Un déplacement est décrit par un vélo, une heure de départ, une station
de départ, une station d’arrivée, et une heure d’arrivée qui sera par
défaut l’heure de départ plus 30 minutes.

• Une heure – dans le sens moment dans la journée – est représentée en


interne par le nombre de minutes écoulées depuis minuit. L’affichage
par contre se fera sous la forme 9h27.

Pour simuler l’activité des vélos, deux listes sont utilisées. La première con-
tient les stations. La deuxième contient les demandes de déplacements. Votre
travail consiste alors à afficher dans l’ordre chronologique les événements de
départ et d’arrivée des vélos. Pour cela on a besoin d’une file contenant les
vélos en cours de déplacement. Dans une première approche quand la station
de départ est vide, on ignore tout simplement l’événement, c’est à dire que la
demande de déplacement n’est pas prise en compte et que le cycliste parisien
prendra le métro. De même, si la station d’arrivée est pleine, l’événement
est ignoré, c’est à dire qu’il ne termine pas. Dans une deuxième approche on
se servira des coordonnées pour trouver la station disponible la plus proche
de celle demandée.

9
2.1.1 Gestion des heures
• Ecrire la fonctions AfficherHeure qui renvoie l’heure formatée de la
manière suivante : 12h05.

• Ecrire la fonction EstAvant(h1,h2) qui teste la chronologie entre deux


heures données

2.1.2 Gestion des stations


• Ecrire des fonctions permettant de tester si une station est pleine ou
vide

• Ecrire une méthode garer(velo) qui gare un vélo à la station dans le


premier emplacement encore libre et génère une exception erreur si
aucun n’est libre

• Ecrire une fonction retirer() qui retire un vélo du premier emplacement


occupé et génère une exception erreur si aucun n’est occupé

• Ecrire une fonction afficher qui permet d’afficher l’état courant d’une
station

2.1.3 Gestion des déplacements


• Ecrire une fonction qui étant donné un déplacement produit une chaı̂ne
sous la forme [Velo 7 de Opera a 10h02 pour Nation a 10h32]

• On va utiliser une file pour la gestion des déplacements. Ecrire une


fonction ajouter(d) qui ajoute un déplacement en fin de file. Par un
mécanisme d’exceptions, on vérifiera que pour aucun déplacement e de
la file d.heurearrivée est strictement avant e.heurearrivée.

• Ecrire une fonction qui enlève un déplacement de la file et une fonction


prochain qui donne le prochain déplacement.

2.1.4 Gestion des velibs


• On utilisera une table de hachage qui associe un nom de stations à une
station.

10
• Une liste de déplacements plan permet de représenter les déplacements
planifiés

• Une liste de déplacements Encours permet de représenter les déplacements


en cours.

• La fonction arrivéeVelo(deplacement) permet de gérer l’arrivée du vélo


du déplacement à la station définie dans le déplacement. Si la station
n’est pas pleine, le velo est garé et un message est affiché. Sinon, seul
un message est affiché

• La fonction departVelo(deplacement) fait partir un velo de la station


de départ donnée par le déplacement. Si la station n’est pas vide,
alors on utilise un vélo de la station pour le déplacement et on ajoute
le déplacement à la liste Encours. Dans tous les cas, un message est
affiché.

• Une fonction afficher() permet l’affichage de l’état des stations

• Enfin, on va simuler une activité de Velibs par l’algorithme suivant :

– Tant qu’il y a des déplacements dans plan ou Encours


∗ Si le prochain événement est un départ alors
· Enlever le déplacement de plan et appeler departVelo(deplacement)
∗ Sinon (le prochain événement est une arrivée)
· Enlever ce déplacement de Encours et appeler arrivéeVelo(deplacement)

2.1.5 Gestion des stations vides ou pleines


Dans la méthode ajouter(d), on relache la contrainte que l’heure d’arrivée de
d est plus tard que toutes les dates d’arrivées dans la file. Par contre, la tête
de file est le déplacement qui a une heure d’arrivée minimale. On a ce qu’on
appelle une file de priorité

• Ecrire une méthode inserer(deplacement,liste) qui insère le déplacement


à sa place dans la liste et qui renvoie la liste

• Modifier la fonction ajouter(d) pour que d soit ajouté correctement dans


la liste

11
• On ajoute à la représentation d’une station, ses coordonnées (x,y) dans
la ville

• Ecrire une fonction ProchaineNonVide et ProchaineNonPleine qui re-


tourne la station la plus proche respectivement non vide ou non pleine.
On pourra s’inspirer pour cela des TDs des dernières séances.

• Dans le cas où une station est pleine, on dispose alors de 15 min
supplémentaires pour finir son déplaement. Modifier la fonction ar-
rivéeVelo(deplacement) en conséquence

12

Vous aimerez peut-être aussi