Algorithmique Et Programmation
Algorithmique Et Programmation
Algorithmique Et Programmation
FACULTE POLYTECHNIQUE
2023-2024
Les fondements mathématiques de l’algorithmique et l’évaluation des algorithmes
Un ordinogramme, également appelé diagramme de flux, est une représentation graphique d'un
algorithme. Il utilise des symboles graphiques standardisés pour représenter les étapes et les décisions
de l'algorithme, facilitant ainsi la visualisation de l'enchaînement des actions.
Diagrammes d'activité pour des processus complexes et des interactions entre entités.
Mise en évidence des structures de données utilisées et modifiées avec des diagrammes de structure de
données.
Utilisation de diagrammes de classes pour représenter les classes, attributs et relations des objets dans
une approche orientée objet.
Communication et collaboration : Le pseudo-code et les organigrammes sont des langages universels qui
transcendent les barrières linguistiques et les préférences de langage de programmation. Ils facilitent la
communication entre les membres de l'équipe en permettant l'expression d'idées et de solutions
compréhensibles pour tous.
Analyse théorique : Évaluez les algorithmes en termes de complexité temporelle et spatiale pour
déterminer leur efficacité en termes de ressources.
Étude des cas : Testez les algorithmes avec des données représentatives et mesurez leur temps
d'exécution pour évaluer leurs performances pratiques.
Expérimentation comparative : Comparez les résultats des algorithmes en termes de temps d'exécution,
d'utilisation de la mémoire et de précision des résultats en les exécutant sur des ensembles de données
de différentes tailles.
Considérations du contexte : Tenez compte du contexte spécifique du problème et des contraintes. Par
exemple, privilégiez la simplicité et la facilité de maintenance si elles sont prioritaires, même si cela
implique une légère baisse de performance.
Consultation d'experts : Demandez l'avis d'experts tels que des chercheurs ou des développeurs
expérimentés pour bénéficier de leur expertise dans l'évaluation des algorithmes pour le problème
spécifique.
La complexité spatiale, qui évalue la quantité de mémoire utilisée en fonction de la taille de l'entrée.
Une complexité spatiale plus faible est souhaitable, surtout pour les problèmes avec des contraintes de
mémoire.
L'analyse des cas d'entrée typiques et extrêmes, qui permet de comprendre le comportement de
l'algorithme dans différentes situations. Certains algorithmes peuvent être performants avec de petites
entrées mais ralentir considérablement avec des entrées plus grandes.
L'analyse expérimentale, qui consiste à implémenter l'algorithme et à le tester sur des ensembles de
données réels. Cette approche mesure les performances réelles de l'algorithme et permet de le
comparer avec d'autres solutions. Les tests expérimentaux fournissent des informations précieuses sur
le comportement de l'algorithme dans des conditions réelles.
Limitations de l'échantillonnage
15. Quelles sont les fonctions qui apparaissent le plus lors de l'analyse théorique des
algorithmes?
Les fonctions les plus courantes lors de l'analyse théorique des algorithmes sont :
Constante (1)
Logarithme (log n)
Linéaire (n)
Quadratique (n²)
Cubique (n³)
Exponentielle (2^n)
16. Quel est l'algorithme le plus efficace parmi un ensemble d'algorithmes permettant de
résoudre un problème
Pour déterminer l'algorithme le plus efficace parmi un ensemble donné, vous pouvez prendre en
compte plusieurs facteurs clés :
Complexité temporelle : Comparez les asymptotes de complexité des algorithmes pour évaluer leur
efficacité en termes de temps d'exécution. Recherchez les algorithmes avec une complexité inférieure,
telle que O(log n) ou O(n), plutôt que des complexités plus élevées comme O(n²) ou O(2^n).
Complexité spatiale : Évaluez l'utilisation de la mémoire par les algorithmes. Choisissez ceux qui
nécessitent moins de mémoire et qui peuvent gérer des ensembles de données plus importants de
manière efficace.
Performances expérimentales : Examinez les résultats des tests et des expériences réelles pour les
algorithmes. Considérez les performances observées dans des scénarios réels ou des ensembles de
données représentatifs du problème. Vérifiez si les algorithmes se comportent de manière optimale
dans ces situations.
Caractéristiques du problème : Tenez compte des spécificités du problème que vous souhaitez résoudre.
Certains algorithmes peuvent mieux s'adapter aux particularités du problème, exploitant ses propriétés
et sa structure spécifiques.
Le cas le plus favorable représente la situation idéale où l'algorithme nécessite le moins de temps ou de
ressources. Cependant, il est rarement représentatif de la réalité, car il suppose des conditions
optimales qui sont souvent peu probables dans la pratique.
Le cas moyen (probabiliste) tient compte de la distribution probabiliste des entrées et représente la
performance moyenne de l'algorithme dans des scénarios typiques. Il est plus représentatif de la réalité
que le cas le plus favorable, car il prend en compte la variabilité des entrées.
Le cas le plus défavorable est important car il garantit les performances de l'algorithme,
indépendamment de la taille ou de la nature des entrées. En se concentrant sur le pire des scénarios, on
peut s'assurer que l'algorithme fonctionnera de manière prévisible et acceptable, même dans des
conditions difficiles. Cela permet de prendre des décisions éclairées sur l'utilisation de l'algorithme dans
des situations réelles et assure une certaine robustesse.
La récursivité binaire est un type de récursivité où une fonction s'appelle elle-même deux fois à
chaque étape. Cela divise le problème en deux sous-problèmes, généralement de taille égale, et
combine ensuite les résultats pour obtenir la solution finale. La récursivité binaire est souvent utilisée
dans des algorithmes tels que la recherche binaire ou le tri fusion.
La récursivité multiple, également connue sous le nom de récursivité à plusieurs voies, est un
type de récursivité où une fonction s'appelle elle-même plusieurs fois à chaque étape. Cela permet de
diviser le problème initial en plusieurs sous-problèmes indépendants, qui sont ensuite résolus
séparément. La récursivité multiple est couramment utilisée dans des structures de données telles que
les arbres et les graphes, où chaque nœud peut avoir plusieurs enfants ou voisins.
21. De quelle façon un problème récursif doit-il pouvoir se définir? Donnez un exemple.
Un problème récursif doit pouvoir se définir en termes de sous-problèmes plus simples et de cas de
base. Chaque sous-problème doit être une version réduite ou similaire du problème initial, et la
résolution du problème global doit reposer sur la résolution de ces sous-problèmes.
Cas récursif : Si n est supérieur à 1, alors n! est égal à n multiplié par (n-1) !.
En utilisant cette définition récursive, nous pouvons calculer la factorielle d'un nombre en appelant
récursivement la fonction factorielle sur n-1 jusqu'à atteindre le cas de base.
= 5 * (4 * factorielle(3))
= 5 * (4 * (3 * factorielle(2)))
= 5 * (4 * (3 * (2 * factorielle(1))))
= 5 * (4 * (3 * (2 * 1)))
= 5 * (4 * (3 * 2))
= 5 * (4 * 6)
= 5 * 24
= 120
Quelques exercices
1. Le nombre d'opérations primitives exécutées par les algorithmes A et B est (50 n log n)
et (45 n2), respectivement. Déterminez n0 tel que A soit meilleur que B, pour tout n ≥
n0.
Soit l’algorithme A donné par (50 nlog (n)) et l’algorithme B donné par (45n²), pour que A soit meilleur
que B, il faut que son temps d’exécution soit inférieur à celui de B
𝟓𝟎𝐧𝐥𝐨𝐠(𝐧) < 𝟒𝟓𝐧² càd 𝟓𝟎/𝟒𝟓 𝐥𝐨𝐠(𝒏) < 𝒏 ssi 𝟏𝟎/𝟗 𝐥𝐨𝐠(𝒏) < 𝒏
On constate que pour des valeurs siffusament grandes de n l’algorithme A sera meilleur que B.
2. Le nombre d'opérations primitives exécutées par les algorithmes A et B est (140 n2) et
(29 n3), respectivement. Déterminez n0 tel que A soit meilleur que B pour tout n ≥ n0. Utiliser
Matlab ou Excel pour montrer les évolutions des temps d'exécution des algorithmes A et B
dans un graphique.
Sachant que A est meilleur que B ssi son nombre d’opérations primitives exécutées est inferieur à celui
de B, C’est a dire 140n²< 29n en divisant les deux membres par n² D’où 𝑛 > 140/29
3. Montrer que les deux énoncés suivants sont équivalents :
On sait que si le temps d’exécution du cas favorable ou défavorable est O(f(n)), alors il devra exister une
constante k telle que k*f(n) est supérieur au cas pire pour n >no, car théoriquement et pratiquement
dans le pire de cas, on observe un temps d’exécution supérieur. On peut trivialement déduire que le pire
de cas de l’algorithme A est aussi O(f(n)) , ceci car la forme asymptotique ne sera juste qu’un facteur
multiplicatif de k du cas le plus favorable. Comme a >=b et b >=k , alors a >=k , ce qui signifie que k est
O(f(n))
4. Montrer que si d(n) vaut O(f(n)), alors (a x d(n)) vaut O(f(n)), pour toute constante
a > 0.
Si d(n) a comme expression asymptotique O(f(n)), alors il existe une constante k tel que d(n) <= k*f(n)
pour tout n>n0. En multipliant d(n) par a, on obtient (a*d(n)) <= a*k*O(f(n)) = k‘ *O(f(n)) On déduira
aussi trivialement, qu’on a obtenue une nouvelle constante, ce qui sera essentiellement k * a qui
maintiendra la condition originale de notation O qui sera vraie quand n>a*n0
5. Montrer que si d(n) vaut O(f(n)) et e(n) vaut O(g(n)), alors le produit d(n)e(n)
est O(f(n)g(n)).
En haut on a démontré que si d(n) a comme expression de temps d’exécution O(f(n)) et e(n) O(g(n)),
alors d(n)<= k*f(n) pour n_f>n_f0 et e(n)<= l*g(n) pour n_e>n_e0 Ceci veut dire que , d(n)*e(n) <=
(c*f(n))*(d*g(n)) et n_f*n_e > n_f0*n_e0 ce qui signifie en d’autres termes qu'il existe une nouvelle
constante n' = n_f*n_e et et n0' = n_f0*n_e0, et un k' = k*l tel que d(n)*e(n) <= k'(f(n)*g(n)) pour n'>n0',
On conclut aussi que d(n)*e(n) est O(f(n)*g(n))
6. Montrer que si d(n) vaut O(f(n)) et e(n) vaut O(g(n)), alors d(n)+e(n) vaut
O(f(n)+g(n)).
On y vas par raisonnement mathématique, si d(n) a comme expression asymptotique O(f(n)) et e(n)
O(g(n)), alors d(n)<= k*f(n) pour n_f>n_f0 et e(n)<= h*g(n) pour n_e>n_e0 Cela signifie que d(n) + e(n)
<= (k*f(n)) + (h*g(n)) et n > n_f0+n_e0 En se référant du même raisonnement que précédemment, on
peut dire qu’il existe un nouveau n' = n_f+n_e et et n0' = n_f0+n_e0, tel que d(n) + e(n) <= k*f(n) +
h*g(n) pour n>n0' ; En effet, on peut continuer en disant que d(n) + e(n) <= f(k*n)+ g(h*n), ce qui va
nous conduire à d(n) + e(n) <= O(f(n)+ g(n))
7. Montrer que si d(n) est O(f(n)) et e(n) est O(g(n)), alors d(n)−e(n) n'est pas
nécessairement O(f(n)−g(n)).
On se contente à dire que si d(n) a pour notation O(f(n)) , et e(n) notation O(g(n)), Avec comme exemple
d(n) = n et e(n) = n avec f(n) = n et g(n) = n, Alors on peut encore dire d(n) <= k*(f(n)) pour n>=0, et e(n)
<= 2*k*(g(n)) pour n>=0 f(n)- g(n) = 0 et d(n) - e(n) = n-n , pas de valeur pour n>0 telle que 0>=n, ce qui
signifie que d(n) -e(n) n'est pas O(f(n)-g(n))
8. Montrer que si d(n) est O(f(n)) et f (n) est O(g(n)), alors d(n) est O(g(n)).
Si d(n) vaut O(f(n)) et f(n) vaut O(g(n)), alors d(n)<= k*f(n) pour n_f>n_f0 et f(n)<= h*g(n) pour n_g>n_g0
Si c'est vrai, alors d(n)<=k*f(n)<=k*(d(g(n))) = k*d*g(n) = k'*g(n) par substitution, ce qui est vrai pour
n_f*n_g>n_f0*n_g0, ou n>n0
L'algorithme E s'exécute en un temps O(i) lorsqu'il est appelé sur l'élément S[i]. Quel est le pire temps
d'exécution de l'algorithme D? Il s’agit ici d’un algorithme constitué de deux blocs , D et E parcourant
une séquence S de n éléments. Le temps d’exécution de E est O(i), il correspondra au temps d’exécution
O(n) de l’algorithme E, car n est la taille de notre séquence, 6 Nous pouvons donc conclure trivialement,
que le pire temps d’exécution de D est O(n²) , car la séquence a n éléments sera appelé par l’algorithme
D qui appellera à son tour l’algorithme E n fois de suite.
10. Alphonse et Bob se disputent à propos de leurs algorithmes. Alphonse revendique le fait
que son algorithme de temps d'exécution O(n log n) est toujours plus rapide que
l'algorithme de temps d'exécution O(n2) de Bob. Pour régler la question, ils effectuent une
série d'expériences. À la consternation d'Alphonse, ils découvrent que si n < 100, l'algorithme
de temps O(n2) s'exécute plus rapidement, et que c'est uniquement lorsque n ≥ 100 est le
temps O(n log n) est meilleur. Expliquez comment cela est possible.
À la consternation d'Alphonse, ils découvrent que si n < 100, l'algorithme de temps O(n2) s'exécute plus
rapidement, et que c'est uniquement lorsque n ≥ 100 est le temps O(n*log(n)) est meilleur. Expliquez
comment cela est possible. D’après nos résultats en haut, il existe C telle que f(n) < C*g(n) Par
conséquent, si l’algorithme de Alphonse A(n*log(n)) fonctionne mieux que celui de Bob B(n²), On peut
s’amuser à résoudre l’expression n*log(n)=n² Le rapport entre n*log(n) / n² donne 100/ 100log(100) =
15.5 s Ce qui signifie que l’algorithme d’alphonse est 15 fois lents sur une itération, mais ceci puisqu’il
effectue moins d’opérations. Plus l’algorithme d’Alphonse exécute plus d’opérations, plus il est meilleur
que celui de BOB.
Si debut == fin
Retourner sequence[debut]
Fin Si
Retourner maxGauche
Sinon
Retourner maxDroite
Fin Si
Fin Fonction
12. Concevoir un algorithme récursif qui permet de trouver le minimum et le maximum d'une
séquence de nombres sans utiliser de boucle.
# Exemple d'utilisation :
sequence = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3]
min_element, max_element = trouver_min_max(sequence, 0, len(sequence) - 1)
print(f"Le minimum de la séquence est : {min_element}")
print(f"Le maximum de la séquence est : {max_element}")
# Exemple d'utilisation :
chaine = "Exemple de chaîne de caractères"
resultat = est_plus_voyelles_que_consonnes(chaine)
print(f"La chaîne contient-elle plus de voyelles que de consonnes ?
{resultat}")
REFFERENCES :