Skip to content
Snippets Groups Projects
Commit c57a877e authored by Robin VAN DE MERGHEL's avatar Robin VAN DE MERGHEL :computer:
Browse files

New courses of Algorithmic and MRP

parent 04ee12c4
No related branches found
No related tags found
No related merge requests found
# Exercice 4.1
> **On a les codes suivants :**
> **Quelle est la complexité de chacun de ces codes ?**
## Algorithme 1
```js
s = 0;
for (i=0;i<=n;i++) {
s = s + i;
}
```
On a $n+1$ itérations, dont chaque itération prend $3$ unités de temps (incrémentation, addition, et comparaison). De plus il y a une affectation à l'initialisation qui coûte $3$ unités de temps. Donc la complexité est $O(3(n + 1) + 3) = O(n)$.
## Algorithme 2
```js
s = 0;
for (i=0;i<=n;i++) {
for (j=i;j<=n;j++) {
s = s + i;
}
}
```
On a $n+1$ itérations, dont chaque itération prend $3$ unités de temps (incrémentation, addition, et comparaison). De plus, la boucle `for` interne a $n - i + 1$ itérations, donc $n(n+1)/2$ itérations au total. Donc la complexité est $O(3(n + 1) + 3(n(n+1)/2)) = O(n^2)$.
## Algorithme 3
```js
for (k=2; k<=n;k++) {
x = a[k];
j = k - 1;
while (j!=0) {
if (x < a[j]) {
j = j - 1;
} else {
j = 0;
}
}
}
```
On a une boucle `for` qui fait $n-2$ itérations. La boucle while elle a un nombre d'itérations qui dépend de la valeur de $j$.
Dans le pire cas, on a $j=0$ à la fin de la boucle `while` grâce à la condition `j!=0`. Donc on a $n-2$ itérations dans la boucle `for` et $k$ itérations dans la boucle `while` pour $k=2,\dots,n$. Donc la complexité est $\sum_{k=2}^n O(k) = O(n^2)$.
## Algorithme 4
```js
c = n;
while (c > 1) {
c = c / 5;
}
```
On peut traduire cet algorithme par une suite récurrente :
$$\begin{cases} c_0 = n \\ c_{k+1} = \frac{c_k}{5} \end{cases}$$
C'est à dire :
$$c_k = \frac{n}{5^k}$$
On a donc $c_k \leq 1$ quand $n \geq \log_5(n)$. Donc la complexité est au pire $O(\log_5(n)) = O(\log(n))$.
# Exercice 4.2
> **Notations $O$**
## Proposition 1
> **Montrez que $(n+a)^b = O(n^b)$ pour tout $a$ et $b$**
On a :
$$\begin{align}
(n+a)^b &= \sum_{k=0}^b \binom{b}{k} a^{b-k} n^k \\
&= n^k + \alpha_1 n^{k-1} + \alpha_2 n^{k-2} + \dots + \alpha_{k-1} n + \alpha_k \\
\end{align}$$
Avec $\alpha_i = \binom{b}{i}$.
On a donc :
$$\begin{align}
O((n+a)^b) &= O(n^b + \alpha_1 n^{b-1} + \alpha_2 n^{b-2} + \dots + \alpha_{b-1} n + \alpha_b) \\
&= O(n^b) + O(\alpha_1 n^{b-1}) + O(\alpha_2 n^{b-2}) + \dots + O(\alpha_{b-1} n) + O(\alpha_b) \\
&= O(n^b) + O(n^{b-1}) + O(n^{b-2}) + \dots + O(n) + O(1) \\
\end{align}$$
On a $O(1)$ négligeable par rapport à $O(n)$, puis $O(n)$ négligeable par rapport à $O(n^2)$, etc. Jusqu'à $O(n^{b-1})$ qui est négligeable par rapport à $O(n^b)$. Donc $O((n+a)^b) = O(n^b)$.
## Proposition 2
> **Est-ce que la phrase suivante a un sens : "Le temps d'exécution de l'algorithme est $O(n^2)$" ? Et pourquoi ?**
File added
...@@ -251,19 +251,16 @@ def all_assignments(agencies_choices, candidates_choices): ...@@ -251,19 +251,16 @@ def all_assignments(agencies_choices, candidates_choices):
agencies = list(agencies_choices.keys()) agencies = list(agencies_choices.keys())
candidates = list(candidates_choices.keys()) candidates = list(candidates_choices.keys())
n = len(agencies)
def h(i): def h(i):
if i == n: if i == n:
yield resultat yield resultat
else: else:
resultat[agencies[i]] = candidates[0] for j in range(n):
yield from h(i+1) if j not in resultat.values():
resultat[agencies[i]] = candidates[1] resultat[agencies[i]] = candidates[j]
yield from h(i+1) yield from h(i+1)
resultat[agencies[i]] = candidates[2]
yield from h(i+1)
resultat[agencies[i]] = candidates[3]
yield from h(i+1)
resultat = {agency: 0 for agency in agencies} resultat = {agency: 0 for agency in agencies}
yield from h(0) yield from h(0)
......
import random
""" Un element est soit une agence, soit un candidat : les classes Agence et
Candidat héritent de la classe Element.
La classe Element a deux attributs :
+ nom : chaine de caractères.
+ tuple_choix : tuple (pas une liste) des objets constituant ses choix.
La classe Element a deux méthodes :
+ __init__ pour initialiser.
+ choix() qui retourne le tuple de ses choix.
Les deux classes filles, Agence et Candidat ont une méthode spéciale __repr__
pour un affichage confortable de l'élément.
"""
# Ecrivez ces trois classes ici
class Element:
def __init__(self, nom, tuple_choix):
self.nom = nom
self.tuple_choix = tuple_choix
def choix(self):
return self.tuple_choix
class Agence(Element):
def __repr__(self):
return f"Agence {self.nom}"
class Candidat(Element):
def __repr__(self):
return f"Candidat {self.nom}"
# ------------------------------------------------------------------------------
"""La classe Instance a deux attributs :
+ ens_agences : l'ensemble des agences de l'instance
+ ens_candidats : l'ensemble des candidats de l'instance.
Elle contient les méthodes :
+ __init__ pour initialiser.
+ ajouter_agence qui prend en entrée une agence et l'ajoute dans l'ensemble
des agences de l'instance.
+ ajouter_candidat qui prend en entrée un candidat et l'ajoute dans l'ensemble
des candidats de l'instance.
+ __repr__ pour un affichage confortable de l'instance.
+ __len__ pour retourner le nombre d'agences (ou de candidats) de l'instance.
+ affiche_choix qui affiche proprement les choix de chaque agence et de chaque
candidat."""
# Ecrivez cette classe ici
class Instance:
def __init__(self, ens_agences, ens_candidats):
self.ens_agences = ens_agences
self.ens_candidats = ens_candidats
def ajouter_agence(self, agence):
self.ens_agences.append(agence)
def ajouter_candidat(self, candidat):
self.ens_candidats.append(candidat)
def __repr__(self):
return f"Instance avec {len(self.ens_agences)} agences et {len(self.ens_candidats)} candidats"
def __len__(self):
return len(self.ens_agences)
def affiche_choix(self):
for agence in self.ens_agences:
print(f"{agence} a pour choix : {agence.choix()}")
for candidat in self.ens_candidats:
print(f"{candidat} a pour choix : {candidat.choix()}")
def __contains__(self, element):
return element in self.ens_agences or element in self.ens_candidats
def agences(self):
return self.ens_agences
def candidats(self):
return self.ens_candidats
# ------------------------------------------------------------------------------
"""La classe Affectation a un attribut :
+ dico_agence_candidat : chaque clé est une agence et la valeur correspondante
est le candidat auquel il est affecté.
Cette classe contient les méthodes :
+ __init__ pour initialiser.
+ candidat_affecté_à : prend en entrée une agence et retourne le candidat qui
lui est affecté (None s'il n'y en a pas).
+ agence_affectée_à : prend en entrée un candidat et retourne l'agence qui
lui est affectée (None s'il n'y en a pas).
+ affecter : prend en entrée une agence, un candidat et affecte l'un à l'autre.
+ changer_affectation_candidat : prend en entrée agence, candidat,
nouvelle_agence et affecte candidat à nouvelle_agence et "dé-affecte" agence.
"""
# Ecrivez cette classe ici
class Affectation:
def __init__(self, dico_agence_candidat):
self.dico_agence_candidat = dico_agence_candidat
def candidat_affecté_à(self, agence):
return self.dico_agence_candidat[agence]
def agence_affectée_à(self, candidat):
for agence, candidat_affecté in self.dico_agence_candidat.items():
if candidat_affecté == candidat:
return agence
return None
def agence_est_affectée(self, agence):
return agence in self.dico_agence_candidat
def candidat_est_affecté(self, candidat):
for agence, candidat_affecté in self.dico_agence_candidat.items():
if candidat_affecté == candidat:
return True
return False
def affecter(self, agence, candidat):
self.dico_agence_candidat[agence] = candidat
def changer_affectation_candidat(self, agence, candidat, nouvelle_agence):
# On affecte le candidat à la nouvelle agence
self.dico_agence_candidat[nouvelle_agence] = None
# On dé-affecte l'ancienne agence
del self.dico_agence_candidat[agence]
def __repr__(self) -> str:
return f"Affectation : {self.dico_agence_candidat}"
def __contains__(self, element):
return element in self.dico_agence_candidat or element in self.dico_agence_candidat.values()
def __len__(self):
return len({candidat for candidat in self.dico_agence_candidat.values() if candidat is not None})
# ------------------------------------------------------------------------------
def extraire_instance_du_fichier(file):
"""Prend en entrée un nom de fichier (chaine de caractères) et retourne
l'instance stockée dans ce fichier. Le format du fichier est :
n (seul sur la première ligne)
suivi de n lignes, chacune étant sous le format :
X:Y1:Y2:...:Yn ou X est le nom d'une agence et les n Yi suivants sont les
noms des candidats, dans l'ordre des choix de X.
suivi de n lignes, chacune étant sous le format :
X:Y1:Y2:...:Yn ou X est le nom d'un candidat et les n Yi suivants sont les
noms des agences, dans l'ordre des choix de X.
Le séparateur est ':' sans espace entre les éléments.
Les noms des agences et candidats ne doivent pas contenir le caractère ":"
"""
with open(file, "r") as buffer:
# Différence avec le code précédent : on utilise les classes
n = int(buffer.readline())
# On va stocker les agences et les candidats dans des dictionnaires ("nom":"objet en classe")
dico_agences = {}
dico_candidats = {}
choix_agences = {}
choix_candidats = {}
for _ in range(n):
line = buffer.readline()
line = line.split(":")
agence = line[0]
candidats = line[1:]
candidats = [candidat.strip() for candidat in candidats]
choix_agences[agence] = candidats
dico_agences[agence] = Agence(agence, None)
for _ in range(n):
line = buffer.readline()
line = line.split(":")
candidat = line[0]
agences = line[1:]
agences = [agence.strip() for agence in agences]
choix_candidats[candidat] = agences
dico_candidats[candidat] = Candidat(candidat, None)
# Les agences ont comme liste de candidats des strings, on va les remplacer par des objets Candidat
for agence in dico_agences.values():
agence.tuple_choix = tuple(
[dico_candidats[candidat] for candidat in choix_agences[agence.nom]])
# Les candidats ont comme liste d'agences des strings, on va les remplacer par des objets Agence
for candidat in dico_candidats.values():
candidat.tuple_choix = tuple(
[dico_agences[agence] for agence in choix_candidats[candidat.nom]])
return Instance(dico_agences.values(), dico_candidats.values())
# ------------------------------------------------------------------------------
def génère_instance_aléatoire(n, version_number=1):
"""Generate a random instance with n agencies, n candidates and put the
result in a file that is named GSEntries_Rand_{n}_{version_number}
(for example GSEntries_Rand_10_3) to distinguish different random files."""
pass
# ------------------------------------------------------------------------------
def gale_shapley_algorithm2(instance):
"""Exécute l'algorithme de Gale-Shapley sur l'instance et retourne
l'affectation construite."""
agenciesAssign = Affectation({})
candidatesAssign = Affectation({})
agenciesIndex = {agency: 0 for agency in instance.agences()}
n = len(instance.agences())
while not len(agenciesAssign) == n:
for agency in instance.agences():
if agency in agenciesAssign:
continue
candidate = agency.tuple_choix[agenciesIndex[agency]]
if candidate not in candidatesAssign:
agenciesAssign.affecter(agency, candidate)
candidatesAssign.affecter(candidate, agency)
else:
currentAgency = candidatesAssign.dico_agence_candidat[candidate]
candidateAgencyRank = candidate.tuple_choix.index(agency)
candidateAgencyIndex = candidate.tuple_choix.index(
currentAgency)
if candidateAgencyRank < candidateAgencyIndex:
agenciesAssign.changer_affectation_candidat(
currentAgency, candidate, agency)
candidatesAssign.changer_affectation_candidat(
candidate, currentAgency, agency)
agenciesIndex[agency] += 1
return agenciesAssign
def gale_shapley_algorithm(instance):
affectation = Affectation({})
agences_iter = {agence: iter(agence.choix())
for agence in instance.ens_agences}
liste_agences_non_affectees = list(instance.ens_agences)
while len(liste_agences_non_affectees) > 0:
a = random.choice(liste_agences_non_affectees)
c = next(agences_iter[a])
if affectation.agence_affectée_à(c) is None:
affectation.affecter(a, c)
liste_agences_non_affectees.remove(a)
else:
k = affectation.agence_affectée_à(c)
if c.choix().index(a) < c.choix().index(k):
affectation.changer_affectation_candidat(k, c, a)
liste_agences_non_affectees.remove(a)
liste_agences_non_affectees.append(k)
return affectation
# ------------------------------------------------------------------------------
def nombre_de_couples_non_stables(instance, affectation):
"""Retourne le nombre de couples non stables de l'affectation liée à
l'instance."""
nombre_de_couples_non_stables = 0
for a in instance.ens_agences:
c = affectation.candidat_affecté_à(a)
rang_de_c = a.choix().index(c)
if rang_de_c > 0:
liste_cand_potentiels = a.choix()[:rang_de_c]
for cand in liste_cand_potentiels:
agence_de_cand = affectation.agence_affectée_à(cand)
if cand.choix().index(a) < cand.choix().index(agence_de_cand):
nombre_de_couples_non_stables += 1
return nombre_de_couples_non_stables
# ------------------------------------------------------------------------------
def génère_affectation_aléatoire(instance):
"""Retourne une affectation aléatoire de l'instance."""
number = len(instance.agences)
import random
# On créer deux listes de candidats et d'agences
candidats = list(instance.candidats)
agences = list(instance.agences)
# On mélange les listes
random.shuffle(candidats)
random.shuffle(agences)
# On créer un dictionnaire qui va contenir les affectations
return Affectation({agence: candidat for agence, candidat in zip(agences, candidats)})
# ------------------------------------------------------------------------------
def iter_toutes_les_affectations(instance):
"""Un itérateur de toutes les affectations possibles (stables ou non
stables). Utilise la bibliothèque itertools. """
import itertools
it = itertools.permutations(instance.ens_candidats)
tuple_des_agences = tuple(instance.ens_agences)
for perm in it:
yield Affectation({agence: candidat for agence, candidat in zip(tuple_des_agences, perm)})
# ------------------------------------------------------------------------------
# Tests
# ------------------------------------------------------------------------------
fichier = "couple.txt"
inst = extraire_instance_du_fichier(fichier)
# inst.affiche_choix()
# affect = gale_shapley_algorithm(inst)
# print("L'affectation produite par l'algorithme de Gayle-Shapley est :")
# print(affect)
# if nombre_de_couples_non_stables(inst, affect) != 0:
# raise Exception("Erreur : le nbre de couples non stables doit être 0 !!")
# print(nombre_de_couples_non_stables(inst, affect))
# affect = génère_affectation_aléatoire(inst)
# print(f"Le nbre de couples non stables dans l'affectation aléatoire")
# print(affect)
# print(f"est de {nombre_de_couples_non_stables(inst, affect)}")
it = iter_toutes_les_affectations(inst)
print([el for el in it])
# liste = [nombre_de_couples_non_stables(inst, affect) for affect in it]
# liste.sort()
# print(liste)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment