web-dev-qa-db-fra.com

Fonction d'alimentation utilisant la récursivité

Je dois écrire une méthode de puissance en Java. Il reçoit deux ints et peu importe qu’ils soient des nombres positifs ou négatifs. Il devrait avoir la complexité de O(logN). Il doit également utiliser la récursivité. Mon code actuel a deux nombres, mais le résultat que je continue à produire est zéro et je ne comprends pas pourquoi.

import Java.util.Scanner;

public class Powers {

    public static void main(String[] args) {
        float a;
        float n;
        float res;

        Scanner in = new Scanner(System.in);
        System.out.print("Enter int a ");
        a = in.nextFloat();

        System.out.print("Enter int n ");
        n = in.nextFloat();

        res = powers.pow(a, n);

        System.out.print(res);
    }

    public static float pow(float a, float n) {
        float result = 0;

        if (n == 0) {
            return 1;
        } else if (n < 0) {
            result = result * pow(a, n + 1);
        } else if (n > 0) {
            result = result * pow(a, n - 1);
        }

        return result;
    }
}
7
Yaboy93

Commençons par quelques faits mathématiques:

  • Pour un n positif, aⁿ = a⨯a⨯… a n fois
  • Pour un n négatif, aⁿ = ⅟a⁻ⁿ = ⅟ (aa⨯… ⨯a). Cela signifie que a ne peut être nul.
  • Pour n = 0, aⁿ = 1, même si a est nul ou négatif.

Commençons donc par le cas positif n et travaillons à partir de là.

Puisque nous voulons que notre solution soit récursive, nous devons trouver un moyen de définir une méthode basée sur un n plus petit et de travailler à partir de là. La façon habituelle de penser à la récursivité est d'essayer de trouver une solution pour n-1 et de travailler à partir de là.

Et en effet, puisqu'il est mathématiquement vrai que aⁿ = a⨯ (aⁿ⁻¹), l'approche naïve serait très similaire à ce que vous avez créé:

public static int pow( int a, int n) {
    if ( n == 0 ) {
        return 1;
    }
    return ( a * pow(a,n-1));
}

Cependant, la complexité de ceci est O (n). Pourquoi? Parce que pour n = 0, il ne fait aucune multiplication. Pour n = 1, cela fait une multiplication. Pour n = 2, il appelle pow (a, 1) que nous savons être une multiplication, et le multiplie une fois, nous avons donc deux multiplications. Il y a une multiplication dans chaque étape de récursion et il y a n étapes. Donc c'est O (n).

Pour faire cela O (log n), il faut que chaque étape soit appliquée à une fraction de n plutôt qu'à n-1. Là encore, il existe un fait mathématique qui peut nous aider:n₁ + n₂ = unn⨯an₂.

Cela signifie que nous pouvons calculer aⁿ en tant quen/2⨯an/2.

Mais que se passe-t-il si n est impair? quelque chose comme a⁹ sera un4,5⨯a4,5. Mais nous parlons ici de puissances entières. La manipulation des fractions est une chose complètement différente. Heureusement, nous pouvons simplement formuler cela comme aa⁴⨯a⁴.

Donc, pour un nombre pair, utilisez unn/2⨯an/2et pour un nombre impair, utilisez a⨯ an/2⨯an/2 (division entière, nous donnant 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 );
    }
}

Cela nous donne les bons résultats (pour un n positif, c’est-à-dire). Mais en réalité, la complexité ici est encore une fois O(n) plutôt que O (log n). Pourquoi? Parce que nous calculons les pouvoirs deux fois. Cela signifie que nous l’appelons 4 fois au niveau suivant, 8 fois au niveau suivant, et ainsi de suite. Le nombre d'étapes de récursivité est exponentiel, donc cela annule les économies supposées que nous avons effectuées en divisant n par deux.

Mais en réalité, seule une petite correction est nécessaire:

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

Dans cette version, nous appelons la récursion une seule fois. Nous obtenons donc, disons, une puissance de 64, très rapidement, passant à 32, 16, 8, 4, 2, 1 et ainsi de suite. Seulement une ou deux multiplications à chaque étape, et il n'y a que six étapes. C'est O (log n).

La conclusion de tout cela est:

  1. Pour obtenir un O (log n), nous avons besoin d'une récursion qui fonctionne sur une fraction de n à chaque étape plutôt que sur n-1 ou n-n'importe quoi.
  2. Mais la fraction n'est qu'une partie de l'histoire. Nous devons veiller à ne pas appeler la récursion plus d'une fois, car l'utilisation de plusieurs appels récursifs en une étape crée une complexité exponentielle qui s'annule avec une fraction de n.

Enfin, nous sommes prêts à nous occuper des nombres négatifs. Nous devons simplement obtenir la réciproque ⅟a. Il y a deux choses importantes à noter:

  • Ne pas permettre la division par zéro. Autrement dit, si vous obtenez un = 0, vous ne devriez pas effectuer le calcul. En Java, nous lançons une exception dans un tel cas. L'exception prête à l'emploi la plus appropriée est IllegalArgumentException. C'est une exception RuntimeException, vous n'avez donc pas besoin d'ajouter une clause throws à votre méthode. Ce serait bien si vous le détectiez ou si vous empêchiez qu'une telle situation se produise, dans votre méthode main, lors de la lecture des arguments.
  • Vous ne pouvez plus retourner un entier (en fait, nous aurions dû utiliser long car nous rencontrons un dépassement d'entier pour des puissances assez faibles avec int), car le résultat peut être fractionnaire.

Nous définissons donc la méthode pour qu'elle retourne double. Ce qui signifie que nous devons également corriger le type de powerOfHalfN. Et voici le résultat:

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

Notez que la partie qui traite un n négatif n'est utilisée que dans le niveau supérieur de la récursivité. Une fois que nous appelons pow() récursivement, les nombres sont toujours positifs et le signe ne change pas tant qu'il n'a pas atteint 0.

Cela devrait être une solution adéquate à votre exercice. Cependant, personnellement, je n'aime pas la if à la fin, voici donc une autre version. Pouvez-vous dire pourquoi cela fait la même chose?

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

faire attention à :

float result = 0;

et

result = result * pow( a, n+1);

C'est pourquoi vous avez obtenu un résultat égal à 0. Il est suggéré de travailler comme suit:

result = a * pow( a, n+1);
1
Chiron

Outre l'erreur d'initialisation de result à 0, il existe d'autres problèmes:

  1. Votre calcul pour n négatif est faux. Rappelez-vous que a^n == 1/(a^(-n)).
  2. Si n n'est pas un entier, le calcul est beaucoup plus compliqué et vous ne le supportez pas. Je ne serai pas surpris si vous n'êtes pas obligé de le supporter.
  3. Pour atteindre les performances de O(log n), vous devez utiliser une stratégie de division et de conquête. c'est-à-dire a^n == a^(n/2)*a^(n/2).
1
Eran

Voici une façon de le faire beaucoup moins déroutante, du moins si vous n'êtes pas préoccupé par les multiplications supplémentaires. : 

public static double pow(int base,int exponent) {
        if (exponent == 0) {
            return 1;
        }
        if (exponent < 0) {
            return 1 / pow(base, -exponent);
        }
        else {
            double results = base * pow(base, exponent - 1);
            return results;
        }
    }
0
Jameson
import Java.io.*;
import Java.util.*;
public class CandidateCode {
    public static void main(String args[] ) throws Exception {

    Scanner sc = new Scanner(System.in);
    int m = sc.nextInt();
    int n = sc. nextInt();
     int result = power(m,n);
     System.out.println(result);
    }
     public static int power(int m, int n){
         if(n!=0)
            return (m*power(m,n-1));
        else 
            return 1;
     }

}
0
malik arman

essaye ça: 

    public int powerN(int base, int n) {return n == 0 ? 1 : (n == 1 ? base : base*(powerN(base,n-1)));
0
Milze

Une bonne règle est de s’éloigner du clavier jusqu’à ce que l’algorithme soit prêt. Ce que vous avez fait est évidemment O (n).

Comme Eran l'a suggéré, pour obtenir une complexité O(log(n)), vous devez diviser n par 2 à chaque itération.

Conditions finales: 

  • n == 0 => 1
  • n == 1 => a

Cas particulier :

  • n <0 => 1./pow (a, -n) // notez le 1. pour obtenir un double ...

Cas normal:

  • m = n/2
  • résultat = pow (a, n)
  • result = resul * resul // éviter de calculer deux fois
  • si n est impair (n% 2! = 0) => resul * = a

Cet algorithme est dans O(log(n)) - C’est à vous d’en écrire le code Java correct

Mais comme on vous l’avait dit: n must be integer (négatif de ok positif, mais integer )

0
Serge Ballesta