1. Exemple de recherche d'un élément dans une liste non triée⚓︎
Le problème est de rechercher la présence d’un élément dans une liste non triée.
En adoptant le paradigme "diviser pour régner", l’idée pour résoudre cette question est de rechercher récursivement l’élément dans la première moitié de la liste et dans la seconde, puis de combiner les résultats via l’opérateur logique or.
En effet, l’élément recherché sera dans la liste s’il est dans la première moitié ou dans la seconde. La condition d’arrêt à la récursivité sera l’obtention d’une liste à un seul élément,car il est alors immédiat de conclure si l’élément recherché appartient à une telle liste ou non.
Étapes
Voici donc les trois étapes de la résolution de ce problème via la méthode "diviser pour régner":
• Diviser la liste en deux sous-listes en la “coupant” par la moitié.
• Rechercher la présence de l’élément dans chacune de ces sous-listes. Arrêter la récursion lorsque les listes n’ont plus qu’un seul élément.
• Combiner avec l’opérateur logique or les résultats obtenus.
Algorithme proposé
👉 Voici l'algorithme proposé :
📋 Texte
fonction recherche(ma_liste, x, d, f):
"""
Précondition : ma_liste est une liste à priori non triée, x est un élément cherché dans la liste
d est le rang du début de la recherche, et f le rang de la fin de la recherche dans ma_liste
postcondition : la fonction renvoie du booléen :
`True` si x est dans la liste, et `False` sinon.
"""
Si d = f:
renvoyer ma_liste[d] == x
m ← (d + f) // 2
renvoyer recherche(ma_liste, x, d, m) ou recherche(ma_liste, x, m + 1, f)
À vous de jouer 1 :
Compléter le script ci-dessous :
###(Dés-)Active le code après la ligne # Tests (insensible à la casse) (Ctrl+I)
Entrer ou sortir du mode "deux colonnes" (Alt+: ; Ctrl pour inverser les colonnes)
Entrer ou sortir du mode "plein écran" (Esc)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier
Comparaison avec la méthode de parcours séquentiel
On donne ici une méthode "naïve" par parcours de liste séquentiel.
Nous allons comparer ces deux méthodes dans le pire des cas (nous cherchons un élément x qui ne se trouve pas dans la liste):
###(Dés-)Active le code après la ligne # Tests (insensible à la casse) (Ctrl+I)
Entrer ou sortir du mode "deux colonnes" (Alt+: ; Ctrl pour inverser les colonnes)
Entrer ou sortir du mode "plein écran" (Esc)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier
Votre figure
Votre tracé sera ici
À lire
Ici, le parcours séquentiel est plus efficace. Une explication est que les appels de fonction (récursivement) ont un coût.
Utiliser la méthode diviser pour régner dans ce cas-là s'apparente à "écraser une mouche avec un marteau-pilon."
2. Recherche du maximum dans une liste non triée⚓︎
Avec le paradigme diviser pour régner
Le problème est de rechercher le maximum d’une liste de nombres.
En adoptant le paradigme "diviser pour régner", l’idée pour résoudre cette question est de rechercher récursivement le maximum de la première moitié de la liste et celui de la seconde, puis de les comparer. Le plus grand des deux sera le maximum de toute la liste.
La condition d’arrêt à la récursivité sera l’obtention d’une liste à un seul élément, son maximum étant bien sûr la valeur de cet élément.
Voici donc les trois étapes de la résolution de ce problème via la méthode "diviser pourrégner":
• Diviser la liste en deux sous-listes en la “coupant” par la moitié.
• Rechercher récursivement le maximum de chacune de ces sous-listes. Arrêter la récursion lorsque les listes n’ont plus qu’un seul élément.
• Combiner : Renvoyer le plus grand des deux maximums précédents.
Observons le schéma suivant :
À vous de jouer 2 :
Compléter le script ci-dessous :
###(Dés-)Active le code après la ligne # Tests (insensible à la casse) (Ctrl+I)
Entrer ou sortir du mode "deux colonnes" (Alt+: ; Ctrl pour inverser les colonnes)
Entrer ou sortir du mode "plein écran" (Esc)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier
Comparaison avec la méthode de parcours séquentiel
On donne ici une méthode "naïve" par parcours de liste séquentiel.
Nous allons comparer ces deux méthodes :
###(Dés-)Active le code après la ligne # Tests (insensible à la casse) (Ctrl+I)
Entrer ou sortir du mode "deux colonnes" (Alt+: ; Ctrl pour inverser les colonnes)
Entrer ou sortir du mode "plein écran" (Esc)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier
Votre figure
Votre tracé sera ici
À lire
Ici, le parcours séquentiel est plus efficace. Une explication est que les appels de fonction (récursivement) ont un coût.
Utiliser la méthode diviser pour régner dans ce cas-là s'apparente à "écraser une mouche avec un marteau-pilon."
2. Utilisons la fonction précédente pour calculer \(2^{1000}\)
Que se passe-t-il?
Solution
Nous avons un message d'erreur expliquant qu'il y a eu un trop grand nombre d'appels de fonctions récursifs. Nous verrons plus tard que nous pouvons modifier cette limite dans Python.
😥 Il faudra faire 100 multiplications pour calculer \(x^{100}\) .
On dit que la complexité (en regardant le nombre de multiplications) de cet algorithme est en \(O(n)\),
Temps de calcul avec l'exponentiation récursive classique
###(Dés-)Active le code après la ligne # Tests (insensible à la casse) (Ctrl+I)
Entrer ou sortir du mode "deux colonnes" (Alt+: ; Ctrl pour inverser les colonnes)
Entrer ou sortir du mode "plein écran" (Esc)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier
Votre figure
Votre tracé sera ici
Solution
La complexité semble encore linéaire.
Quelques exemples d'utilisation de "diviser pour régner" :
defmystere(l1,l2):n1=len(l1)n2=len(l2)lst=[]# initialisation de la fusion de l1 et l2 i1=0# indice qui sert à parcourir l1i2=0# indice qui sert à parcourir l2whilei1<n1andi2<n2:ifl1[i1]<l2[i2]:lst.append(l1[i1])i1=i1+1else:lst.append(l2[i2])i2=i2+1returnlstmystere([2,3,5,8],[1,4])
Recopier sur votre cahier le tableau suivant qui décrit le déroulement de l'exécution de : mystere([2, 3, 5, 8],[1, 4]) et le compléter.
Il y a une ligne par tour de boucle.
Pour vous aider, nous avons rajouté une colonne pour l1 et une pour l2. Vous pourrez entourer à chaque étape, dans une de ces colonnes, l'élément qui sera ajouté à lst (en gras ici)
i1
i2
l1
l2
lst
0
0
[2, 3, 5, 8]
[1, 4]
[1]
0
1
[2, 3, 5, 8]
[1, 4]
[1, 2]
...
...
...
...
...
...
Solution
i1
i2
l1
l2
lst
0
0
[2, 3, 5, 8]
[1, 4]
[1]
0
1
[2, 3, 5, 8]
[1, 4]
[1, 2]
1
1
[2, 3, 5, 8]
[1, 4]
[1, 2, 3]
2
1
[2, 3, 5, 8]
[1, 4]
[1, 2, 3, 4]
2
2
😥 Nous observons que les deux listes n'ont pas été complètement fusionnées, car nous avons "épuisé" tous les éléments de l2.
👉 Par contre, il est certain que les éléments restants de l1 qui n'ont pas été fusionnés, sont triés, et plus grands que tous les éléments déjà présents dans lst.
🤗 Pour obtenir la liste complètement fusionnée, il suffit donc d'exécuter : lst + [5, 8] c'est à dire lst + l1[i1:]
💻 A vous de jouer 1
Compléter le script suivant :
###(Dés-)Active le code après la ligne # Tests (insensible à la casse) (Ctrl+I)
Entrer ou sortir du mode "deux colonnes" (Alt+: ; Ctrl pour inverser les colonnes)
Entrer ou sortir du mode "plein écran" (Esc)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier
😥 Le problème, c'est que les "slices" ne sont pas vraiment au programme de NSI, et que leur utilisation n'est pas toujours efficace. Essayons une version qui n'utilise pas de "slices".
💻 A vous de jouer 2
Compléter le script suivant :
###(Dés-)Active le code après la ligne # Tests (insensible à la casse) (Ctrl+I)
Entrer ou sortir du mode "deux colonnes" (Alt+: ; Ctrl pour inverser les colonnes)
Entrer ou sortir du mode "plein écran" (Esc)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier
Nous disposons d'un tableau (type list de Python) de taille n.
Son premier rang est donc 0 et son dernier rang n-1.
On notera t[a -> b] la liste constituée des éléments de rang compris entre a et b (compris) de la liste t.
La fonction tri_fusion fait appel à la fonction fusion qui permet de fusionner deux listes triées en une liste triée.
fonction tri_fusion(t)
"""
entrée ː un tableau t
sortie ː renvoie un autre tableau qui correspond au tableau t trié
"""
n = longueur(t)
si n ≤ 1
renvoyer t
sinon
m = n//2
renvoyer fusion(tri_fusion(t[0 -> m-1]), tri_fusion(t[m -> n-1]))
La terminaison est justifiée par la décroissance stricte de n à chaque appel récursif.
💻 A vous de jouer 3
Compléter le script suivant :
La fonction fusion est écrite dans le code caché.
###(Dés-)Active le code après la ligne # Tests (insensible à la casse) (Ctrl+I)
Entrer ou sortir du mode "deux colonnes" (Alt+: ; Ctrl pour inverser les colonnes)
Entrer ou sortir du mode "plein écran" (Esc)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier
deffusion(l1,l2):""" Précondition : l1 et l2 sont deux listes triées Postcondition : la fonction renvoie une liste triée constituée de la fusion de l1 et l2 Exemple : fusion([2, 3, 5, 8],[1, 4]) renvoie [1, 2, 3, 4, 5, 8] """n1=len(l1)n2=len(l2)lst=[]# initialisation de la fusion de l1 et l2 i1=0# indice qui sert à parcourir l1i2=0# indice qui sert à parcourir l2whilei1<n1andi2<n2:ifl1[i1]<l2[i2]:lst.append(l1[i1])i1=i1+1else:lst.append(l2[i2])i2=i2+1ifi1==n1:returnlst+l2[i2:]ifi2==n2:returnlst+l1[i1:]deftri_fusion(lst):""" Précondition : lst est une liste Postcondition : la fonction renvoie une liste qui est la liste triée """n=len(lst)ifn<=1:returnlstelse:m=n//2returnfusion(tri_fusion(lst[:m]),tri_fusion(lst[m:]))
# Tests
(insensible à la casse)(Ctrl+I)
(Alt+: ; Ctrl pour inverser les colonnes)
(Esc)