Une fonction récursive peut-elle avoir des itérations / boucles?

12

J'ai étudié les fonctions récursives, et apparemment, ce sont des fonctions qui s'appellent elles-mêmes et n'utilisent pas d'itérations / boucles (sinon ce ne serait pas une fonction récursive).

Cependant, en surfant sur le Web pour des exemples (le problème récursif des 8 reines), j'ai trouvé cette fonction:

private boolean placeQueen(int rows, int queens, int n) {
    boolean result = false;
    if (row < n) {
        while ((queens[row] < n - 1) && !result) {
            queens[row]++;
            if (verify(row,queens,n)) {
                ok = placeQueen(row + 1,queens,n);
            }
        }
        if (!result) {
            queens[row] = -1;
        }
    }else{
        result = true;
    }
    return result;
}

Il y a une whileboucle impliquée.

... donc je suis un peu perdu maintenant. Puis-je utiliser des boucles ou non?

Oméga
la source
5
Compile-t-il. Oui. Alors pourquoi demander?
Thomas Eding
6
La définition entière de la récursivité est qu'à un moment donné, la fonction peut être rappelée dans le cadre de sa propre exécution avant son retour (qu'elle soit rappelée par elle-même ou par une autre fonction qu'elle appelle). Rien dans cette définition n'exclut la possibilité de boucler.
cHao
En complément du commentaire de cHao, une fonction récursive sera rappelée sur une version plus simple d'elle-même (sinon, elle serait en boucle pour toujours). Pour citer l'orbling (de Dans un anglais simple, qu'est-ce que la récursivité? ): "La programmation récursive est le processus de réduction progressive d'un problème afin de résoudre plus facilement les versions de lui-même." Dans ce cas, la version la plus difficile de placeQueen"place 8 queens" et la version la plus simple de placeQueen"place 7 queens" (puis place 6, etc.)
Brian
Vous pouvez utiliser tout ce qui fonctionne Omega. Il est très rare que les spécifications du logiciel spécifient le style de programmation à utiliser - sauf si vous êtes à l'école et que votre devoir le dit.
Apoorv Khurasia
@ThomasEding: Oui, bien sûr, il compile et fonctionne. Mais je suis en train d'étudier l'ingénierie en ce moment - Ce qui compte pour moi, à ce stade, c'est le concept / définition strict et non la façon dont les programmeurs l'utilisent de nos jours. Je demande donc si le concept que j'ai est correct (ce qui n'est pas le cas, semble-t-il).
Omega

Réponses:

41

Vous avez mal compris la récursivité: bien qu'elle puisse être utilisée pour remplacer l'itération, il n'y a absolument aucune obligation pour la fonction récursive de ne pas avoir d'itérations internes à elle-même.

La seule condition pour qu'une fonction soit considérée comme récursive est l'existence d'un chemin de code par lequel elle s'appelle, directement ou indirectement. Toutes les fonctions récursives correctes ont également un conditionnel quelconque, les empêchant de "se reproduire" pour toujours.

Votre fonction récursive est idéale pour illustrer la structure de la recherche récursive avec retour arrière. Elle commence par la vérification de la condition de sortie row < net procède à des décisions de recherche sur son niveau de récursivité (c'est-à-dire la sélection d'une position possible pour le nombre de reines row). Après chaque itération, un appel récursif est effectué pour s'appuyer sur la configuration que la fonction a trouvée jusqu'à présent; finalement, il " rowtouche nle fond" lorsqu'il atteint l'appel récursif qui est nprofond.

dasblinkenlight
la source
1
+1 pour les fonctions récursives "correctes" ont un conditionnel, beaucoup de incorrectes là-bas qui ne le font pas
Jimmy Hoffa
6
+1 "récurrent" pour toujours `Turtle () {Turtle ();}
Mr.Mindor
1
@ Mr.Mindor J'adore la citation «C'est des tortues tout le long du chemin» :)
dasblinkenlight
Cela m'a fait sourire :-)
Martijn Verburg
2
"Toutes les fonctions récursives correctes ont également un conditionnel quelconque, les empêchant de" se reproduire "pour toujours." n'est pas vrai avec une évaluation non stricte.
Pubby
12

La structure générale d'une fonction récursive ressemble à ceci:

myRecursiveFunction(inputValue)
begin
   if evaluateBaseCaseCondition(inputValue)=true then
       return baseCaseValue;
   else
       /*
       Recursive processing
       */
       recursiveResult = myRecursiveFunction(nextRecursiveValue); //nextRecursiveValue could be as simple as inputValue-1
       return recursiveResult;
   end if
end

Le texte que j'ai marqué comme /*recursive processing*/pourrait être n'importe quoi. Il peut inclure une boucle, si le problème résolu le nécessite, et peut également inclure des appels récursifs à myRecursiveFunction.

FrustratedWithFormsDesigner
la source
1
C'est trompeur, car cela implique qu'il n'y a qu'un seul appel récursif, et exclut à peu près les cas où l'appel récursif est lui-même à l'intérieur d'une boucle (par exemple, traversée de l'arbre B).
Peter Taylor
@PeterTaylor: Oui, j'essayais de rester simple.
FrustratedWithFormsDesigner
Ou même plusieurs appels sans boucle, comme traverser un arbre binaire simple, où vous auriez 2 appels car chaque nœud a 2 enfants.
Izkata
6

Vous pouvez sûrement utiliser des boucles dans une fonction récursive. Ce qui rend une fonction récursive, c'est seulement le fait que la fonction s'appelle à un moment donné de son chemin d'exécution. Cependant, vous devriez avoir une condition pour empêcher les appels de récursion infinis à partir desquels votre fonction ne peut pas retourner.

marco-fiset
la source
1

Les appels et boucles récursifs ne sont que deux manières / constructions pour implémenter un calcul itératif.

Une whileboucle correspond à un appel récursif de queue (voir par exemple ici ), c'est-à-dire une itération dans laquelle vous n'avez pas besoin de sauvegarder les résultats intermédiaires entre deux itérations (tous les résultats d'un cycle sont prêts lorsque vous entrez dans le cycle suivant). Si vous avez besoin de stocker des résultats intermédiaires que vous pourrez réutiliser plus tard, vous pouvez soit utiliser une whileboucle avec une pile (voir ici ), soit un appel récursif non récursif (c'est-à-dire arbitraire).

De nombreuses langues vous permettent d'utiliser les deux mécanismes et vous pouvez choisir celui qui vous convient le mieux et même les mélanger dans votre code. Dans les langages impératifs comme C, C ++, Java, etc., vous utilisez normalement une boucle whileou forlorsque vous n'avez pas besoin d'une pile, et vous utilisez des appels récursifs lorsque vous avez besoin d'une pile (vous utilisez implicitement la pile d'exécution). Haskell (un langage fonctionnel) n'offre pas de structure de contrôle d'itération, vous ne pouvez donc utiliser que des appels récursifs pour effectuer l'itération.

Dans votre exemple (voir mes commentaires):

// queens should have type int [] , not int.
private boolean placeQueen(int row, int [] queens, int n)
{
    boolean result = false;
    if (row < n)
    {
        // Iterate with queens[row] = 1 to n - 1.
        // After each iteration, you either have a result
        // in queens, or you have to try the next column for
        // the current row: no intermediate result.
        while ((queens[row] < n - 1) && !result)
        {
            queens[row]++;
            if (verify(row,queens,n))
            {
                // I think you have 'result' here, not 'ok'.
                // This is another loop (iterate on row).
                // The loop is implemented as a recursive call
                // and the previous values of row are stored on
                // the stack so that we can resume with the previous
                // value if the current attempt finds no solution.
                result = placeQueen(row + 1,queens,n);
            }
        }
        if (!result) {
            queens[row] = -1;
        }
    }else{
        result = true;
    }
    return result;
}
Giorgio
la source
1

Vous avez raison de penser qu'il existe une relation entre la récursivité et l'itération ou la boucle. Les algorithmes récursifs sont souvent convertis manuellement ou même automatiquement en solutions itératives à l'aide de l'optimisation des appels de queue.

Dans huit reines, la partie récursive est liée au stockage des données nécessaires au suivi arrière. Lorsque vous pensez à la récursivité, il est utile de penser à ce qui est poussé sur la pile. La pile peut contenir des paramètres de passage par valeur et des variables locales qui jouent un rôle clé dans l'algorithme, ou parfois des éléments qui ne sont pas aussi apparemment pertinents comme l'adresse de retour ou dans ce cas, une valeur transmise avec le nombre de reines utilisées mais pas changé par l'algorithme.

L'action qui se produit dans huit reines est qu'essentiellement, on nous donne une solution partielle pour un certain nombre de reines dans les premières colonnes à partir desquelles nous déterminons de manière itérative des choix valides jusqu'à présent dans la colonne actuelle que nous passons récursivement pour être évalués pour la colonnes restantes. Localement, huit reines gardent une trace de la ligne qu'il essaie et si le suivi arrière se produit, il est prêt à parcourir les lignes restantes ou à revenir en arrière en retournant simplement s'il ne trouve aucune autre ligne qui pourrait fonctionner.

DeveloperDon
la source
0

La partie "créer une version plus petite du problème" peut avoir des boucles. Tant que la méthode s'appelle en passant comme paramètre la version plus petite du problème, la méthode est récursive. Bien sûr, une condition de sortie, lorsque la plus petite version possible du problème est résolue et que la méthode renvoie une valeur, doit être fournie pour éviter une condition de dépassement de pile.

La méthode de votre question est récursive.

Tulains Córdova
la source
0

La récursivité appelle à nouveau votre fonction et le principal avantage de la récursivité est d'économiser de la mémoire. La récursivité peut contenir des boucles, elles sont utilisées pour effectuer une autre opération.

Akshay
la source
Beg to differ. De nombreux algorithmes peuvent être récursifs ou itératifs et la solution récursive est souvent beaucoup plus gourmande en mémoire si vous comptez les adresses de retour, les paramètres et les variables locales qui doivent être poussés sur la pile. Certaines langues détectent et aident à optimiser la récursivité de queue ou l'optimisation des appels de queue, mais cela est parfois spécifique à la langue ou au code.
DeveloperDon