diff --git a/Algorithmique/TD/TD4.md b/Algorithmique/TD/TD4.md
new file mode 100644
index 0000000000000000000000000000000000000000..d4dc80af168af5b3daa9083ee54eacdb9828f37f
--- /dev/null
+++ b/Algorithmique/TD/TD4.md
@@ -0,0 +1,107 @@
+# 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 ?**
+
diff --git a/Algorithmique/TD/td-complexite.pdf b/Algorithmique/TD/td-complexite.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..557c69b0c5849779539d9d3d429ec3b7d4d4f408
Binary files /dev/null and b/Algorithmique/TD/td-complexite.pdf differ
diff --git a/Modeliser Resolution/Exo_Assignment.py b/Modeliser Resolution/Exo_Assignment.py
index 1783150597f7cf30732fd0cce61dfd681c3a988a..f78ee8fd0e8b2ca19cb9b9be668429f32fba5ee7 100644
--- a/Modeliser Resolution/Exo_Assignment.py	
+++ b/Modeliser Resolution/Exo_Assignment.py	
@@ -251,19 +251,16 @@ def all_assignments(agencies_choices, candidates_choices):
 
     agencies = list(agencies_choices.keys())
     candidates = list(candidates_choices.keys())
+    n = len(agencies)
 
     def h(i):
         if i == n:
             yield resultat
         else:
-            resultat[agencies[i]] = candidates[0]
-            yield from h(i+1)
-            resultat[agencies[i]] = candidates[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)
+            for j in range(n):
+                if j not in resultat.values():
+                    resultat[agencies[i]] = candidates[j]
+                    yield from h(i+1)
 
     resultat = {agency: 0 for agency in agencies}
     yield from h(0)
diff --git a/Modeliser Resolution/Exo_GaleShapleyAlgorithm-OO-Objets.py b/Modeliser Resolution/Exo_GaleShapleyAlgorithm-OO-Objets.py
new file mode 100644
index 0000000000000000000000000000000000000000..dd81d707e0770632a759353b90167d9cca192992
--- /dev/null
+++ b/Modeliser Resolution/Exo_GaleShapleyAlgorithm-OO-Objets.py	
@@ -0,0 +1,368 @@
+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)