Se connecterS'abonner

Chapitre 11

Designing Modular Algorithms

Lecture : 17 minQuiz de 12 questions

# Designing Modular Algorithms ## Objectifs et prérequis Ce chapitre vise à développer la capacité des élèves à concevoir, analyser et implémenter des algorithmes modulaires en utilisant les sous-programmes (subroutines) et la récursion, tout en comprenant leurs limites en termes de complexité et de ressources. Les élèves apprendront à décomposer un problème en sous-tâches réutilisables, à modéliser des processus récursifs, à évaluer leur efficacité et à intégrer ces concepts dans une activité de synthèse. **Prérequis :** - Maîtrise des concepts de base de la programmation (variables, boucles, conditions, tableaux). - Compréhension des fonctions/procédures dans un langage de programmation (ex. : Python, Pascal ou pseudocode). - Notions élémentaires de complexité en temps (O-notation) et d’espace. - Familiarité avec la lecture et l’écriture de pseudocode structuré. --- ## I. Processing Subroutine Calls ### Définition et propriétés Un **sous-programme** (ou subroutine) est une séquence d’instructions nommée qui effectue une tâche spécifique et peut être appelée depuis différentes parties d’un programme. Il permet la modularité, la réutilisation du code et une meilleure lisibilité. > **À retenir :** Un sous-programme encapsule une logique indépendante, réduit la redondance et facilite le débogage et la maintenance. Lorsqu’un sous-programme est appelé, le processus suivant se produit : 1. **Sauvegarde de l’état** : l’adresse de retour (où reprendre après l’appel) est enregistrée (souvent sur la pile d’appels). 2. **Passage des paramètres** : les valeurs réelles sont copiées dans les paramètres formels (par valeur ou par référence, selon le langage). 3. **Exécution du corps** : les instructions du sous-programme sont exécutées. 4. **Retour** : la valeur de retour (si fonction) est passée, puis l’exécution reprend à l’adresse de retour sauvegardée. ### Exemple résolu : Calcul du factoriel avec sous-programme Considérons un sous-programme `factorial(n)` qui retourne $n!$ : ```pseudocode FUNCTION factorial(n : INTEGER) : INTEGER IF n <= 1 THEN RETURN 1 ELSE RETURN n * factorial(n - 1) END IF END FUNCTION ``` **Appel :** `factorial(4)` **Étapes d’exécution :** 1. Appel `factorial(4)` → sauvegarde adresse retour, passe `n=4` 2. `4 > 1` → appelle `factorial(3)` 3. Appel `factorial(3)` → sauvegarde adresse retour, passe `n=3` 4. `3 > 1` → appelle `factorial(2)` 5. Appel `factorial(2)` → sauvegarde adresse retour, passe `n=2` 6. `2 > 1` → appelle `factorial(1)` 7. Appel `factorial(1)` → sauvegarde adresse retour, passe `n=1` 8. `1 <= 1` → retourne `1` 9. Retour à `factorial(2)` : retourne `2 * 1 = 2` 10. Retour à `factorial(3)` : retourne `3 * 2 = 6` 11. Retour à `factorial(4)` : retourne `4 * 6 = 24` 12. Résultat final : `24` ### Exercices d’application corrigés **Exercice 1 :** Écrire un sous-programme `gcd(a, b)` qui retourne le plus grand commun diviseur de deux entiers positifs par l’algorithme d’Euclide. Tracer l’exécution pour `gcd(48, 18)`. **Correction :** ```pseudocode FUNCTION gcd(a, b : INTEGER) : INTEGER WHILE b ≠ 0 DO temp ← b b ← a MOD b a ← temp END WHILE RETURN a END FUNCTION ``` **Trace pour `gcd(48, 18)` :** | Étape | a | b | a MOD b | Nouvelle a | Nouvelle b | |-------|-----|-----|---------|------------|------------| | 1 | 48 | 18 | 48 mod 18 = 12 | 18 | 12 | | 2 | 18 | 12 | 18 mod 12 = 6 | 12 | 6 | | 3 | 12 | 6 | 12 mod 6 = 0 | 6 | 0 | | 4 | 6 | 0 | b=0 → arrêt | | | Résultat : `6` **Exercice 2 :** Quel est l’effet d’un appel de sous-programme sur la pile d’appels ? Illustrer avec l’appel `factorial(3)` dans le langage pseudocode ci-dessus. **Correction :** À chaque appel, l’adresse de retour et la valeur locale de `n` sont empilées. Pour `factorial(3)` : - Appel initial : pile vide → empile (retour à main, n=3) - Appel `factorial(2)` → empile (retour à factorial(3), n=2) - Appel `factorial(1)` → empile (retour à factorial(2), n=1) - Retour `factorial(1)` : dépile → reprend à factorial(2) avec n=1 - Retour `factorial(2)` : dépile → reprend à factorial(3) avec n=2 - Retour `factorial(3)` : dépile → reprend à main La pile atteint une profondeur de 3 appels imbriqués. --- ## II. Recursion ### Définition et propriétés La **récursion** est une technique de programmation où un sous-programme s’appelle lui-même pour résoudre un problème en le décomposant en sous-instances plus petites du même problème. Elle repose sur deux éléments essentiels : - Un **cas de base** (ou cas trivial) qui arrête la récursion. - Un **cas récursif** qui réduit le problème vers le cas de base. > **À retenir :** Toute définition récursive doit avoir un cas de base bien défini et une progression garantie vers ce cas pour éviter l’infini. ### Exemple résolu : Somme des éléments d’un tableau Calculer la somme des éléments d’un tableau `A[0..n-1]` de manière récursive. ```pseudocode FUNCTION sumArray(A : ARRAY OF INTEGER, n : INTEGER) : INTEGER IF n == 0 THEN RETURN 0 ELSE RETURN A[n-1] + sumArray(A, n-1) END IF END FUNCTION ``` **Appel :** `sumArray([2, 4, 6], 3)` **Étapes :** 1. `sumArray([2,4,6], 3)` → `6 + sumArray([2,4,6], 2)` 2. `sumArray([2,4,6], 2)` → `4 + sumArray([2,4,6], 1)` 3. `sumArray([2,4,6], 1)` → `2 + sumArray([2,4,6], 0)` 4. `sumArray([2,4,6], 0)` → `0` (cas de base) 5. Retour : `2 + 0 = 2` 6. Retour : `4 + 2 = 6` 7. Retour : `6 + 6 = 12` Résultat : `12` ### Exemple résolu : Parcours en profondeur d’un arbre binaire (affichage préfixe) ```pseudocode PROCEDURE preorder(node : TreeNode) IF node ≠ NULL THEN PRINT node.value preorder(node.left) preorder(node.right) END IF END PROCEDURE ``` Pour un arbre : ``` 1 / \ 2 3 / \ 4 5 ``` Appel : `preorder(racine)` → affichage : `1, 2, 4, 5, 3` ### Exercices d’application corrigés **Exercice 1 :** Écrire une fonction récursive `power(x, n)` qui calcule $x^n$ pour un entier $n \geq 0$. Tracer l’exécution pour `power(2, 5)`. **Correction :** ```pseudocode FUNCTION power(x : REAL, n : INTEGER) : REAL IF n == 0 THEN RETURN 1 ELSE RETURN x * power(x, n-1) END IF END FUNCTION ``` **Trace pour `power(2,5)` :** - `power(2,5)` → `2 * power(2,4)` - `power(2,4)` → `2 * power(2,3)` - `power(2,3)` → `2 * power(2,2)` - `power(2,2)` → `2 * power(2,1)` - `power(2,1)` → `2 * power(2,0)` - `power(2,0)` → `1` - Retour : `2 * 1 = 2` - Retour : `2 * 2 = 4` - Retour : `2 * 4 = 8` - Retour : `2 * 8 = 16` - Retour : `2 * 16 = 32` Résultat : $2^5 = 32$ **Exercice 2 :** Pourquoi la fonction suivante ne termine-t-elle pas ? ```pseudocode FUNCTION badRecurse(n : INTEGER) : INTEGER RETURN badRecurse(n + 1) END FUNCTION ``` **Correction :** Elle manque un cas de base. Chaque appel augmente `n`, s’éloignant davantage de toute condition d’arrêt. Il n’y a pas de progression vers un cas terminal → récursion infinie → débordement de pile. --- ## III. Recursion Limitations and Complexity ### Limitations de la récursion Malgré son élégance, la récursion présente des inconvénients pratiques : - **Consommation de mémoire** : chaque appel ajoute un cadre sur la pile d’appels (espace mémoire). Une récursion profonde peut provoquer un **débordement de pile** (stack overflow). - **Overhead de fonction** : chaque appel implique un coût en temps (sauvegarde/restoration de l’état, passage de paramètres). - **Difficulté de débogage** : suivre l’exécution récursive peut être complexe en raison de l’imbrication des appels. > **À retenir :** La récursion est élégante mais coûteuse en espace et en temps ; elle doit être utilisée avec discernement, surtout pour des profondeurs importantes. ### Analyse de complexité en temps et en espace Nous analysons la complexité en utilisant la notation asymptotique $O()$. #### Exemple 1 : Factoriel récursif ```pseudocode FUNCTION factorial(n) IF n <= 1 THEN RETURN 1 ELSE RETURN n * factorial(n-1) ``` - **Appels récursifs** : $n$ appels (de `n` down to `1`) - **Travail par appel** : $O(1)$ (multiplication et comparaison) - **Complexité en temps** : $T(n) = T(n-1) + O(1)$ → $T(n) = O(n)$ - **Complexité en espace** : chaque appel laisse un cadre sur la pile → $O(n)$ espace #### Exemple 2 : Recherche dichotomique récursive ```pseudocode FUNCTION binarySearch(A, low, high, key) IF low > high THEN RETURN -1 mid ← (low + high) // 2 IF A[mid] == key THEN RETURN mid ELSE IF A[mid] > key THEN RETURN binarySearch(A, low, mid-1, key) ELSE RETURN binarySearch(A, mid+1, high, key) ``` - **Appels récursifs** : chaque appel réduit l’intervalle de moitié → profondeur $O(\log n)$ - **Travail par appel** : $O(1)$ - **Complexité en temps** : $T(n) = T(n/2) + O(1)$ → $T(n) = O(\log n)$ - **Complexité en espace** : $O(\log n)$ (profondeur de la pile) #### Exemple 3 : Suite de Fibonacci naïve ```pseudocode FUNCTION fib(n) IF n <= 1 THEN RETURN n ELSE RETURN fib(n-1) + fib(n-2) ``` - **Arbre d’appels** : chaque appel `fib(n)` génère deux sous-appels → arbre binaire de hauteur $n$ - **Nombre d’appels** : approximativement $2^n$ (en fait, proportionnel au $n$-ième nombre de Fibonacci lui-même) - **Complexité en temps** : $O(2^n)$ (exponentielle) - **Complexité en espace** : $O(n)$ (profondeur maximale de la pile, pas le nombre total d’appels) > **À retenir :** La récursion naïve de Fibonacci est exponentielle en temps car elle recompute les mêmes valeurs plusieurs fois. Cela illustre l’importance de la programmation dynamique ou de la mémoisation. ### Tableau comparatif : Complexité des algorithmes récursifs | Algorithme | Cas de base | Appels récursifs | Travail par appel | Complexité en temps | Complexité en espace | |-------------------------|----------------------|------------------------|-------------------|---------------------|----------------------| | Factoriel | $n \leq 1$ | 1 appel : $n-1$ | $O(1)$ | $O(n)$ | $O(n)$ | | Somme de tableau | $n = 0$ | 1 appel : $n-1$ | $O(1)$ | $O(n)$ | $O(n)$ | | Recherche dichotomique | $low > high$ | 1 appel : moitié | $O(1)$ | $O(\log n)$ | $O(\log n)$ | | Fibonacci naïve | $n \leq 1$ | 2 appels : $n-1, n-2$ | $O(1)$ | $O(2^n)$ | $O(n)$ | | Hanoï (tours) | $n = 0$ | 2 appels : $n-1$ | $O(1)$ | $O(2^n)$ | $O(n)$ | > **À retenir :** La complexité en espace d’un algorithme récursif correspond généralement à la profondeur maximale de la pile d’appels, pas au nombre total d’appels. ### Exercices d’application corrigés **Exercice 1 :** Déterminer la complexité en temps et en espace de l’algorithme récursif suivant qui calcule la somme des premiers $n$ entiers : ```pseudocode FUNCTION sumN(n) IF n == 0 THEN RETURN 0 ELSE RETURN n + sumN(n-1) ``` **Correction :** - Appels : $n+1$ (de `n` à `0`) - Travail par appel : $O(1)$ - Temps : $T(n) = T(n-1) + O(1)$ → $O(n)$ - Espace : pile de profondeur $n+1$ → $O(n)$ **Exercice 2 :** Pourquoi l’algorithme de recherche dichotomique récursive est-il préférable en espace à une version qui copie des sous-tableaux à chaque appel ? **Correction :** Si on passait des sous-tableaux copiés (ex. : `A[low..mid-1]`), chaque appel nécessiterait $O(n)$ espace pour la copie, et avec $O(\log n)$ appels, l’espace total serait $O(n \log n)$. En passant uniquement les indices `low` et `high`, on évite la copie : chaque cadre de pile utilise $O(1)$ espace, donc espace total $O(\log n)$. **Exercice 3 :** Un élève propose une fonction récursive pour trouver le maximum d’un tableau : ```pseudocode FUNCTION maxArray(A, n) IF n == 1 THEN RETURN A[0] ELSE RETURN max(A[n-1], maxArray(A, n-1)) ``` Quelle est sa complexité en temps et en espace ? Est-elle optimale ? **Correction :** - Appels : $n$ appels (de `n` à `1`) - Travail par appel : $O(1)$ (comparaison et appel) - Temps : $O(n)$ - Espace : $O(n)$ (pile de profondeur $n$) Elle est asymptotiquement optimale en temps ($\Omega(n)$ nécessaire pour lire tous les éléments), mais pas en espace : une version itérative utiliserait $O(1)$ espace. La récursion ici apporte de la lisibilité mais pas d’efficacité spatiale. --- ## IV. Integration Activity ### Contexte de l’activité Les élèves doivent concevoir un programme qui résout un problème réel en combinant sous-programmes modulaires et récursion, puis analyser sa complexité. L’activité vise à intégrer les notions vues : décomposition en sous-tâches, appel de sous-programmes, conception récursive, évaluation des limites. ### Énoncé du problème Écrire un programme qui, donné un arbre binaire de recherche (BST), : 1. Vérifie s’il est bien formé (pour tout nœud, toutes les valeurs dans le sous-arbre gauche sont $<$ nœud, et toutes celles dans le sous-arbre droit sont $>$ nœud). 2. Retourne la hauteur de l’arbre. 3. Compte le nombre de feuilles (nœuds sans enfants). Le programme doit utiliser : - Au moins deux sous-programmes distincts (en dehors du `main`). - La récursion pour au moins une des tâches. - Une analyse de complexité en temps et en espace pour chaque sous-programme. ### Solution proposée (pseudocode) ```pseudocode TYPE TreeNode value : INTEGER left, right : TreeNode END TYPE FUNCTION isBST(node : TreeNode, min : INTEGER, max : INTEGER) : BOOLEAN IF node == NULL THEN RETURN TRUE END IF IF node.value < min OR node.value > max THEN RETURN FALSE END IF RETURN isBST(node.left, min, node.value - 1) AND isBST(node.right, node.value + 1, max) END FUNCTION FUNCTION height(node : TreeNode) : INTEGER IF node == NULL THEN RETURN -1 // hauteur d’un arbre vide = -1 (ou 0 si on compte les nœuds) END IF RETURN 1 + MAX(height(node.left), height(node.right)) END FUNCTION FUNCTION countLeaves(node : TreeNode) : INTEGER IF node == NULL THEN RETURN 0 END IF IF node.left == NULL AND node.right == NULL THEN RETURN 1 END IF RETURN countLeaves(node.left) + countLeaves(node.right) END FUNCTION PROCEDURE main() // Construction d’un arbre exemple : 4 / \ 2 6 / \ / \ 1 3 5 7 root ← new TreeNode(4) root.left ← new TreeNode(2) root.right ← new TreeNode(6) root.left.left ← new TreeNode(1) root.left.right ← new TreeNode(3) root.right.left ← new TreeNode(5) root.right.right ← new TreeNode(7) IF isBST(root, -∞, +∞) THEN PRINT "L'arbre est un BST valide." ELSE PRINT "L'arbre n'est pas un BST." END IF PRINT "Hauteur : ", height(root) PRINT "Nombre de feuilles : ", countLeaves(root) END PROCEDURE ``` ### Analyse de complexité #### 1. `isBST(node, min, max)` - Chaque nœud est visité exactement une fois. - Travail par nœud : $O(1)$ (comparaisons et appels récursifs) - **Temps** : $O(n)$ où $n$ est le nombre de nœuds - **Espace** : profondeur de la pile = hauteur de l’arbre - Dans le pire cas (arbre dégénéré) : $O(n)$ - Dans le meilleur cas (arbre équilibré) : $O(\log n)$ - Donc : $O(h)$ où $h$ est la hauteur #### 2. `height(node)` - Chaque nœud visité une fois. - Travail par nœud : $O(1)$ - **Temps** : $O(n)$ - **Espace** : $O(h)$ (profondeur de la pile) #### 3. `countLeaves(node)` - Chaque nœud visité une fois. - Travail par nœud : $O(1)$ - **Temps** : $O(n)$ - **Espace** : $O(h)$ ### Exercices d’application corrigés (activité intégrée) **Exercice 1 :** Pour l’arbre suivant : ``` 5 / \ 3 8 / \ \ 2 4 9 ``` Tracer l’exécution de `isBST(root, -∞, +∞)` et confirmer qu’il retourne `TRUE`. **Correction :** - Appel `isBST(5, -∞, +∞)` : $5 \in [-∞, +∞]$ → OK - Appel gauche : `isBST(3, -∞, 4)` → $3 \in [-∞, 4]$ → OK - Gauche-gauche : `isBST(2, -∞, 2)` → $2 \in [-∞, 2]$ → OK - Les deux enfants NULL → retourne `TRUE` - Gauche-droite : `isBST(4, 3, 4)` → $4 \in [3, 4]$ → OK - Retourne `TRUE` - Retour gauche : `TRUE AND TRUE = TRUE` - Appel droit : `isBST(8, 6, +∞)` → $8 \in [6, +∞]$ → OK - Droit-gauche : `isBST(NULL, 6, 7)` → `TRUE` - Droit-droite : `isBST(9, 9, +∞)` → $9 \in [9, +∞]$ → OK - Retourne `TRUE` - Retour droit : `TRUE AND TRUE = TRUE` - Retour racine : `TRUE AND TRUE = TRUE` **Exercice 2 :** Quelle est la hauteur de l’arbre ci-dessus ? Combien de feuilles contient-il ? **Correction :** - Chemins depuis la racine : - 5 → 3 → 2 : longueur 2 - 5 → 3 → 4 : longueur 2 - 5 → 8 → 9 : longueur 2 - Hauteur = nombre d’arêtes sur le plus long chemin = `2` - Feuilles : nœuds sans enfants → `2`, `4`, `9` → `3` feuilles **Exercice 3 :** Si l’arbre était dégénéré (ex. : 1 → 2 → 3 → 4 → 5, tous à droite), quelle serait la complexité en espace de `isBST` ? **Correction :** Dans un arbre dégénéré vers la droite, chaque appel récursif va à droite uniquement. La pile d’appels contiendra un cadre pour chaque nœud : 5 appels imbriqués pour 5 nœuds → profondeur $n$. → Complexité en espace : $O(n)$ **Exercice 4 :** Proposer une version itérative de `height` qui utilise $O(1)$ espace. Pourquoi n’est-ce pas possible pour `isBST` sans pile ou file explicite ? **Correction :** Une version itérative de `height` peut utiliser un parcours en niveau (BFS) avec une file, mais cela utilise $O(n)$ espace en pire cas. Pour une vraie $O(1)$ espace, il faudrait un arbre avec des pointeurs vers le parent (threaded tree) ou utiliser l’algorithme de Morris (modification temporaire), ce qui dépasse le cadre. Pour `isBST`, une vérification itérative nécessite de suivre les contraintes de min/max pour chaque nœud, ce qui implique naturellement une pile ou une file pour revenir en arrière → on ne peut pas le faire en $O(1)$ espace sans modifier l’arbre ou utiliser une structure auxiliaire. > **À retenir :** Certaines propriétés intrinsèquement récursives (comme la validation de BST) nécessitent une mémoire auxiliaire proportionnelle à la hauteur, même en itératif, sauf si l’arbre est modifié. --- ## Synthèse du chapitre - Les sous-programmes permettent la modularité, la réutilisation et une meilleure organisation du code via l’encapsulation de tâches spécifiques. - L’appel d’un sous-programme implique la sauvegarde de l’état (adresse de retour, paramètres) sur la pile d’appels, suivie de l’exécution et du retour. - La récursion est une technique où une fonction s’appelle elle-même pour résoudre un problème en le réduisant à des instances plus petites, nécessitant un cas de base et un pas récursif. - Toute définition récursive doit garantir une progression vers le cas de base pour éviter l’infini. - La récursion peut entraîner un débordement de pile et un overhead en temps et en espace ; sa complexité en espace correspond généralement à la profondeur maximale de la pile d’appels. - L’analyse de complexité des algorithmes récursifs se fait souvent par récurrence (ex. : $T(n) = T(n-1) + O(1)$ → $O(n)$). - Certains algorithmes récursifs naïfs (comme Fibonacci) ont une complexité exponentielle en temps dû à des recalculs répétés, illustrant le besoin d’optimisation (mémoisation, programmation dynamique). - L’activité d’intégration permet de combiner sous-programmes et récursion pour résoudre un problème concret (validation de BST, hauteur, comptage de feuilles), tout en analysant leur efficacité. - Le choix entre récursion et itération doit tenir compte de la lisibilité, de l’efficacité en espace et en temps, et des contraintes du problème. --- ## Glossaire - **Sous-programme (Subroutine)** : Une séquence d’instructions nommée qui effectue une tâche spécifique et peut être appelée depuis différentes parties d’un programme. - **Pile d’appels (Call Stack)** : Une structure de données LIFO qui stocke les informations d’état (adresse de retour, variables locales) lors des appels de sous-programmes. - **Cas de base (Base Case)** : La condition dans une définition récursive qui arrête la récursion et retourne une valeur directement. - **Cas récursif (Recursive Case)** : La partie d’une définition récursive qui réduit le problème vers le cas de base par un ou plusieurs appels récursifs. - **Débordement de pile (Stack Overflow)** : Une erreur qui se produit lorsque la pile d’appels dépasse sa capacité mémoire, généralement due à une récursion trop profonde ou infinie. - **Complexité en temps** : La quantité de temps qu’un algorithme prend en fonction de la taille de l’entrée, exprimée en notation asymptotique (ex. : $O(n)$). - **Complexité en espace** : La quantité de mémoire qu’un algorithme utilise en fonction de la taille de l’entrée, incluant la pile d’appels pour les algorithmes récursifs. - **Récursion linéaire (Linear Recursion)** : Une récursion où chaque appel génère au maximum un appel récursif (ex. : factoriel, somme de tableau). - **Récursion binaire (Binary Recursion)** : Une récursion où chaque appel génère deux appels récursifs (ex. : Fibonacci naïve, recherche dans un arbre binaire). - **Mémoisation (Memoization)** : Une technique d’optimisation qui stocke les résultats des appels de fonction coûteux pour éviter des recalculs répétés. - **Arbre binaire de recherche (BST)** : Un arbre binaire où pour chaque nœud, toutes les valeurs dans le sous-arbre gauche sont strictement inférieures et celles dans le sous-arbre droit sont strictement supérieures à la valeur du nœud. - **Hauteur d’un arbre** : Le nombre d’arêtes sur le plus long chemin depuis la racine jusqu’à une feuille. Par convention, la hauteur d’un arbre vide est $-1$.

Quiz du chapitre

12 questions — vérifie chaque réponse pour voir l'explication.

  1. 1. What is the primary goal of designing modular algorithms?
  2. 2. Which of the following best describes a 'module' in the context of modular algorithm design?
  3. 3. In modular design, what is the main advantage of having low coupling between modules?
  4. 4. Which principle ensures that each module has a single, well-defined responsibility?
  5. 5. A student is designing an algorithm to calculate the average grade of a class. They break it into: (1) input grades, (2) sum grades, (3) count students, (4) divide sum by count. Which step best exemplifies a reusable module?
  6. 6. In a modular algorithm for sorting a list using merge sort, the 'merge' function is called recursively. What property of modular design does this best illustrate?
  7. 7. A programmer writes a module to validate user input that checks if a string is a valid email address. Later, they reuse this module in a login form, a registration form, and a newsletter signup. What benefit of modular design is being demonstrated?
  8. 8. Which of the following is a potential disadvantage of over-modularizing an algorithm?
  9. 9. In a modular algorithm for computing the factorial of a number using recursion, the base case (n=0) is handled in a separate module called 'factorial_base'. What is the most likely issue with this design?
  10. 10. A team is designing a modular algorithm to process sensor data: Module A reads data, Module B filters noise, Module C detects anomalies, and Module D logs results. If Module B is changed to use a new filtering technique, which modules are most likely to require modification?
  11. 11. Consider the following pseudocode for a modular algorithm: FUNCTION processData(list): cleaned = cleanData(list) sorted = sortData(cleaned) result = analyzeData(sorted) RETURN result END FUNCTION What is the primary benefit of this structure?
  12. 12. In a modular algorithm for pathfinding in a grid, the 'heuristic' function is passed as a parameter to the main search function. What design principle does this best exemplify?