Una funzione ricorsiva può avere iterazioni / loop?


12

Ho studiato funzioni ricorsive e, apparentemente, sono funzioni che si chiamano da sole e non usano iterazioni / cicli (altrimenti non sarebbe una funzione ricorsiva).

Tuttavia, mentre navigavo sul web per esempio (il problema ricorsivo delle 8 regine), ho trovato questa funzione:

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;
}

C'è un whileciclo coinvolto.

... quindi sono un po 'perso ora. Posso usare i loop o no?


5
Si compila. Sì. Allora perché chiedere?
Thomas Eding,

6
L' intera definizione di ricorsione è che ad un certo punto, la funzione può essere richiamata come parte della propria esecuzione prima che ritorni (sia che venga richiamata da sola o da qualche altra funzione che chiama). Nulla di tale definizione esclude la possibilità di loop.
cHao,

Come addendum al commento di cHao, una funzione ricorsiva verrà richiamata su una versione più semplice di se stessa (altrimenti, sarebbe in loop per sempre). Per citare orbling (da In parole povere , che cos'è la ricorsione? ): "La programmazione ricorsiva è il processo di riduzione progressiva di un problema a versioni di se stesso più facili da risolvere". In questo caso, la versione più difficile di placeQueenè "place 8 queens" e la versione più semplice di placeQueenè "place 7 queens" (quindi posto 6, ecc.)
Brian

Puoi usare tutto ciò che funziona Omega. Molto raramente le specifiche del software specificano quale stile di programmazione usare, a meno che tu non sia a scuola e il tuo compito lo dica.
Apoorv Khurasia,

@ThomasEding: Sì, ovviamente si compila e funziona. Ma sto solo studiando ingegneria in questo momento - Ciò che conta per me, a questo punto, è il concetto / definizione rigorosa e non il modo in cui i programmatori la utilizzano al giorno d'oggi. Quindi sto chiedendo se il concetto che ho sia corretto (che non lo è, a quanto pare).
Omega,

Risposte:


41

Hai frainteso la ricorsione: sebbene possa essere usata per sostituire l'iterazione, non è assolutamente necessario che la funzione ricorsiva non abbia iterazioni interne a se stessa.

L'unico requisito per considerare una funzione ricorsiva è l'esistenza di un percorso di codice attraverso il quale si chiama, direttamente o indirettamente. Tutte le funzioni ricorsive corrette hanno anche una condizione di qualche tipo, impedendo loro di "ricorrere verso il basso" per sempre.

La funzione ricorsiva è ideale per illustrare la struttura della ricerca ricorsiva con il backtracking. Inizia con il controllo della condizione di uscita row < ne procede alle decisioni di ricerca sul suo livello di ricorsione (cioè selezionando una possibile posizione per il numero di regina row). Dopo ogni iterazione, viene effettuata una chiamata ricorsiva per basarsi sulla configurazione che la funzione ha trovato finora; alla fine, "tocca il fondo" quando rowraggiunge nla chiamata ricorsiva che è nprofonda.


1
+1 per le funzioni ricorsive "corrette" hanno una condizione condizionata, molte errate là fuori che non lo fanno
Jimmy Hoffa,

6
+1 "recurring down" forever `Turtle () {Turtle ();}
Mr.Mindor

1
@ Mr.Mindor Adoro la citazione "Sono le tartarughe fino in fondo" :)
dasblinkenlight,

Mi ha fatto sorridere :-)
Martijn Verburg,

2
"Tutte le corrette funzioni ricorsive hanno anche una condizione di qualche tipo, impedendo loro di" ricorrere verso il basso "per sempre." non è vero con una valutazione non rigorosa.
Pubblicazione del

12

La struttura generale di una funzione ricorsiva è qualcosa del genere:

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

Il testo che ho contrassegnato come /*recursive processing*/potrebbe essere qualsiasi cosa. Si potrebbe includere un ciclo, se il problema essendo risolto lo richiede, e potrebbe anche includere le chiamate ricorsive a myRecursiveFunction.


1
Questo è fuorviante, perché implica che esiste solo una chiamata ricorsiva e praticamente esclude i casi in cui la chiamata ricorsiva è essa stessa all'interno di un ciclo (ad esempio, traversata B-tree).
Peter Taylor,

@PeterTaylor: Sì, stavo cercando di mantenerlo semplice.
FrustratedWithFormsDesigner,

O anche più chiamate senza loop, come attraversare un semplice albero binario, dove avresti 2 chiamate a causa del fatto che ogni nodo ha 2 figli.
Izkata,

6

Puoi sicuramente usare i loop in una funzione ricorsiva. Ciò che rende ricorsiva una funzione è solo il fatto che la funzione si chiama ad un certo punto del suo percorso di esecuzione. Tuttavia, è necessario disporre di alcune condizioni per impedire infinite chiamate di ricorsione da cui la funzione non può tornare.


1

Le chiamate e i loop ricorsivi sono solo due modi / costrutti per implementare un calcolo iterativo.

Un whileciclo corrisponde a una chiamata ricorsiva di coda (vedere ad esempio qui ), ovvero un'iterazione in cui non è necessario salvare risultati intermedi tra due iterazioni (tutti i risultati di un ciclo sono pronti quando si accede al ciclo successivo). Se è necessario memorizzare risultati intermedi che è possibile riutilizzare in seguito, è possibile utilizzare un whileciclo insieme a uno stack (vedere qui ) oppure una chiamata ricorsiva non ricorsiva alla coda (ovvero arbitraria).

Molte lingue ti consentono di utilizzare entrambi i meccanismi e puoi scegliere quello più adatto a te e persino mescolarli insieme nel tuo codice. In linguaggi imperativi come C, C ++, Java, ecc. Normalmente si utilizza un ciclo whileo forquando non è necessario uno stack e si utilizzano chiamate ricorsive quando è necessario uno stack (si utilizza implicitamente lo stack di runtime). Haskell (un linguaggio funzionale) non offre una struttura di controllo dell'iterazione, pertanto è possibile utilizzare solo chiamate ricorsive per eseguire l'iterazione.

Nel tuo esempio (vedi i miei commenti):

// 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;
}

1

Hai ragione a pensare che esista una relazione tra ricorsione e iterazione o loop. Gli algoritmi ricorsivi vengono spesso convertiti manualmente o addirittura automaticamente in soluzioni iterative utilizzando l'ottimizzazione delle chiamate di coda.

In otto regine, la parte ricorsiva è correlata alla memorizzazione dei dati necessari per il back tracking. Quando pensi alla ricorsione, è utile pensare a ciò che viene spinto in pila. Lo stack può contenere parametri di passaggio per valore e variabili locali che svolgono un ruolo chiave nell'algoritmo, o talvolta cose che non sono così apparentemente rilevanti come l'indirizzo di ritorno o, in questo caso, un valore passato con il numero di regine utilizzate ma non modificato dall'algoritmo.

L'azione che si verifica in otto regine è che essenzialmente ci viene data una soluzione parziale per un certo numero di regine nelle prime colonne da cui determiniamo iterativamente scelte valide finora nella colonna corrente che passiamo ricorsivamente per essere valutate per il colonne rimanenti. A livello locale, otto regine tengono traccia di quale riga sta provando e, se si verifica il back tracking, è pronto per scorrere le restanti righe o per tornare indietro semplicemente semplicemente ritornando se non trova altre righe che potrebbero funzionare.


0

La parte "crea una versione più piccola del problema" può avere loop. Finché il metodo si chiama passando come parametro la versione più piccola del problema, il metodo è ricorsivo. Naturalmente una condizione di uscita, quando viene risolta la versione più piccola possibile del problema e il metodo restituisce un valore, deve essere fornita per evitare una condizione di overflow dello stack.

Il metodo nella tua domanda è ricorsivo.


0

La ricorsione in pratica richiama nuovamente la tua funzione e il vantaggio principale della ricorsione è il risparmio di memoria. La ricorsione può avere dei loop in essa utilizzati per eseguire altre operazioni.


Iniziamo a differire. Molti algoritmi possono essere ricorsivi o iterativi e la soluzione ricorsiva spesso richiede molta più memoria se si contano indirizzi di ritorno, parametri e variabili locali che devono essere inseriti nello stack. Alcune lingue rilevano e aiutano a ottimizzare la ricorsione della coda o l'ottimizzazione delle chiamate di coda, ma a volte questo è specifico della lingua o del codice.
Sviluppatore:
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.