Cominciamo con alcuni fatti di matematica:
- Per un n positiva, aⁿ = a⨯a⨯ ... ⨯an volte
- Per un n negativo, aⁿ = ⅟a⁻ⁿ = ⅟ (a⨯a⨯ ... ⨯a). Ciò significa che a non può essere zero.
- Per n = 0, aⁿ = 1, anche se a è zero o negativo.
Quindi partiamo dal caso positivo n e lavoriamo da lì.
Dal momento che vogliamo che la nostra soluzione sia ricorsiva, dobbiamo trovare un modo per definire aⁿ basato su un n più piccolo, e lavorare da lì. Il modo abituale in cui le persone pensano alla ricorsione è cercare di trovare una soluzione per n-1 e lavorare da lì.
E in effetti, dal momento che è matematicamente vero che aⁿ = a⨯ (aⁿ⁻¹), l'approccio ingenuo sarebbe molto simile a quello che si è creato:
public static int pow(int a, int n) {
if (n == 0) {
return 1;
}
return (a * pow(a,n-1));
}
Tuttavia, la complessità di questo è O (n). Perché? Perché per n = 0 non fa alcuna moltiplicazione. Per n = 1, fa una moltiplicazione. Per n = 2, chiama pow (a, 1) che sappiamo essere una moltiplicazione, e moltiplica una volta, quindi abbiamo due moltiplicazioni. C'è una moltiplicazione in ogni passo di ricorsione, e ci sono n passi. Quindi è O (n).
Per rendere questo O (log n), è necessario applicare ogni passaggio a una frazione di n anziché solo a n-1. Anche in questo caso, esiste un fatto matematico che può aiutarci: a n₁ + n₂ = a n₁ ⨯a n₂.
Questo significa che siamo in grado di calcolare aⁿ come n/2 ⨯a n/2.
Ma cosa succede se n è dispari? qualcosa come a⁹ sarà un 4.5 ⨯a 4.5. Ma stiamo parlando di poteri interi qui. Gestire le frazioni è una cosa completamente diversa. Fortunatamente, possiamo solo formularlo come a⨯a⁴⨯a⁴.
Così, per un numero pari utilizzare un n/2 ⨯a n/2, e per un numero dispari, utilizzare un a⨯ n/2 ⨯a n/2 (divisione intera , dandoci 9/2 = 4).
public static int pow(int a, int n) {
if (n == 0) {
return 1;
}
if (n % 2 == 1) {
// Odd n
return a * pow(a, n/2) * pow(a, n/2);
} else {
// Even n
return pow(a, n/2) * pow(a, n/2);
}
}
Questo effettivamente ci dà i risultati giusti (per un positivo n, cioè). Ma in effetti, la complessità qui è, di nuovo, O (n) piuttosto che O (log n). Perché? Perché stiamo calcolando i poteri due volte.Significa che in realtà lo chiamiamo 4 volte al livello successivo, 8 volte al livello successivo e così via. Il numero di passaggi di ricorsione è esponenziale, quindi questo si annulla con il presunto risparmio che abbiamo fatto dividendo n per due.
Ma in realtà, è necessario solo una piccola correzione:
public static int pow(int a, int n) {
if (n == 0) {
return 1;
}
int powerOfHalfN = pow(a, n/2);
if (n % 2 == 1) {
// Odd n
return a * powerOfHalfN * powerOfHalfN;
} else {
// Even n
return powerOfHalfN * powerOfHalfN;
}
}
In questa versione, ci chiedono il ricorsione solo una volta. Quindi otteniamo, diciamo, una potenza di 64, molto rapidamente attraverso 32, 16, 8, 4, 2, 1 e fatto. Solo una o due moltiplicazioni per ogni passaggio e ci sono solo sei passaggi. Questo è O (log n).
La conclusione di tutto questo è:
- per ottenere un O (log n), abbiamo bisogno di ricorsione che funziona su una frazione di n ad ogni passo, piuttosto che solo n - 1 o n - qualsiasi cosa.
- Ma la frazione è solo una parte della storia. Dobbiamo stare attenti a non chiamare la ricorsione più di una volta, perché l'utilizzo di più chiamate ricorsive in una fase crea complessità esponenziale che si annulla con l'utilizzo di una frazione di n.
Infine, siamo pronti a occuparci dei numeri negativi. Dobbiamo semplicemente ottenere il reciproco ⅟a⁻ⁿ. Ci sono due cose importanti da notare:
- Non consentire la divisione per zero. Cioè, se hai ottenuto un = 0, non dovresti eseguire il calcolo. In Java, gettiamo un'eccezione in questo caso. L'eccezione ready-made più appropriata è IllegalArgumentException. È una RuntimeException, quindi non è necessario aggiungere una clausola
throws
al metodo. Sarebbe positivo se tu lo prendessi o impedissi che si verificasse una situazione del genere, nel tuo metodo main
quando leggi gli argomenti.
- Non è più possibile restituire un numero intero (in realtà, avremmo dovuto utilizzare
long
perché eseguiamo un overflow di numeri interi per potenze piuttosto basse con int
), perché il risultato potrebbe essere frazionario.
Quindi definiamo il metodo in modo che ritorni doppio. Il che significa che dobbiamo anche correggere il tipo di powerOfHalfN
. Ed ecco il risultato:
public static double pow(int a, int n) {
if (n == 0) {
return 1.0;
}
if (n < 0) {
// Negative power.
if (a == 0) {
throw new IllegalArgumentException(
"It's impossible to raise 0 to the power of a negative number");
}
return 1/pow(a, -n);
} else {
// Positive power
double powerOfHalfN = pow(a, n/2);
if (n % 2 == 1) {
// Odd n
return a * powerOfHalfN * powerOfHalfN;
} else {
// Even n
return powerOfHalfN * powerOfHalfN;
}
}
}
nota che la parte che gestisce a n negativo viene utilizzato solo nel livello superiore della ricorsione. Una volta che chiamiamo pow()
in modo ricorsivo, è sempre con numeri positivi e il segno non cambia finché non raggiunge 0.
Questa dovrebbe essere una soluzione adeguata al tuo esercizio. Tuttavia, personalmente non mi piace il if
lì alla fine, quindi ecco un'altra versione. Puoi dire perché questo sta facendo lo stesso?
public static double pow(int a, int n) {
if (n == 0) {
return 1.0;
}
if (n < 0) {
// Negative power.
if (a == 0) {
throw new IllegalArgumentException(
"It's impossible to raise 0 to the power of a negative number");
}
return 1/pow(a, -n);
} else {
// Positive power
double powerOfHalfN = pow(a, n/2);
double[] factor = { 1, a };
return factor[n % 2] * powerOfHalfN * powerOfHalfN;
}
}
ogni volta che avete solo bisogno di aggiungere quarantina inizializzare somma = 0, ma quando si vuole moltiplicare e aggiungere inizializzare somma = 1 – Vihar
Perché stai usando 'float' quando v'è stato detto di usare' int'? Ancora più importante - non solo il tuo uso di 'risultato' è sbagliato (a causa dello zero), la complessità della tua funzione è O (n), non O (log n).Devi ridurre il problema ad almeno la metà della sua dimensione nella ricorsione per ottenere O (log n). – RealSkeptic
Questa è la complessità di 'O (n)' anche se è ricorsiva. Un O (log n) '' algoritmo sarebbe sempre dividere la potenza ('n' in questo caso) per 2, non diminuire di 1. – Daniel