Có phương pháp nào tính giai thừa trong Java không?


105

Tôi vẫn chưa tìm thấy nó. Tôi đã bỏ lỡ điều gì đó? Tôi biết phương pháp giai thừa là một chương trình ví dụ phổ biến cho người mới bắt đầu. Nhưng sẽ không hữu ích nếu có một triển khai tiêu chuẩn để sử dụng lại cái này? Tôi có thể sử dụng phương pháp như vậy với các kiểu tiêu chuẩn (Ví dụ: int, long ...) và cả với BigInteger / BigDecimal.

Câu trả lời:


26

Tôi không nghĩ sẽ hữu ích nếu có một hàm thư viện cho giai thừa. Có rất nhiều nghiên cứu về triển khai giai thừa hiệu quả. Đây là một số cách triển khai.


188
Tại sao nó không hữu ích khi có một hàm thư viện cho giai thừa?
midnite vào

17
Không nhiều người thực sự cần giai thừa trong mã thực. Nếu bạn làm như vậy, thì có thể bạn đang thực hiện một số phép toán hoặc thống kê nâng cao, trong trường hợp đó, rất có thể bạn đã sử dụng thư viện toán học có triển khai giai thừa chuyên biệt.
mikera,

3
Hàm gamma rất hữu ích, đó là lý do tại sao nó được đưa vào thư viện chuẩn C ++.
Columbo

2
Nghe với tôi rằng @KarlthePagan có nghĩa là không hữu ích khi có một chức năng thư viện tiêu chuẩn cho giai thừa - điều đó có đúng không?
dantiston

vì vậy họ sẽ hỏi bạn tại cuộc phỏng vấn để xem liệu bạn có thể tự làm được không * trường hợp của tôi
moldovean

59

Apache Commons Math có một vài phương thức giai thừa trong lớp MathUtils .


1
Vâng. Đồ tốt. Có một triển khai giai thừa cho các số thực và số không phẳng (MathUtils.factorial (int) và MathUtils.factorialDouble (int)) và cũng hữu ích lôgarit tự nhiên của n! (MathUtils.factorialLog (int))
Tomasz Błachowicz

3
nó đang ở trong ArithmeticUtils vào lúc này.
MarianP

6
ArithmeticUtils.factorial hiện đã không còn được dùng nữa, hãy sử dụng CombinatoricsUtils.factorial
Victor Häggqvist

Vui lòng không sử dụng liên kết! Chúng có xu hướng biến mất (như TẤT CẢ trong số này đều có).
SMBiggs

1
@ScottBiggs Cả hai liên kết trong câu trả lời đều hoạt động tốt.
Bill the Lizard

40
public class UsefulMethods {
    public static long factorial(int number) {
        long result = 1;

        for (int factor = 2; factor <= number; factor++) {
            result *= factor;
        }

        return result;
    }
}

Phiên bản Big Numbers của HoldOffHunger :

public static BigInteger factorial(BigInteger number) {
    BigInteger result = BigInteger.valueOf(1);

    for (long factor = 2; factor <= number.longValue(); factor++) {
        result = result.multiply(BigInteger.valueOf(factor));
    }

    return result;
}

Ngoài ra, bạn đang giả định rằng bạn muốn giai thừa của một số nguyên
Andrew

Giải pháp này nên sử dụng lớp BigInteger.
Oleg Abrazhaev

2
Phiên bản Big Numbers: public static giai thừa BigInteger (BigInteger n) {BigInteger factorial = BigInteger.valueOf (1); for (int i = 1; i <= n.intValue (); i ++) {factorial = factorial.multiply (BigInteger.valueOf (i)); } trả về giai thừa; }
HoldOffHunger

23

Trong thực tế hiếm khi cần đến các thừa số trần trụi. Thông thường, bạn sẽ cần một trong những thứ sau:

1) chia một giai thừa này cho một giai thừa khác, hoặc

2) câu trả lời gần đúng dấu phẩy động.

Trong cả hai trường hợp, bạn sẽ tốt hơn với các giải pháp tùy chỉnh đơn giản.

Trong trường hợp (1), giả sử, nếu x = 90! / 85 !, sau đó bạn sẽ tính được kết quả là x = 86 * 87 * 88 * 89 * 90 mà không cần phải giữ 90! trong trí nhớ :)

Trong trường hợp (2), hãy google để tìm "Ước lượng của Stirling".


3
Counterexample: Tính số hoán vị với N phần tử yêu cầu giai thừa trần và cần thiết nếu bạn muốn cấp phát một cấu trúc để chứa các hoán vị.
Mark Jeronimus

Điểm tốt! Câu hỏi đầu tiên của tôi là nếu 90! / 85! đơn giản hóa vì mẫu số chung là 5, nhưng thực ra, nó là mẫu số chung của 85 !. 90! / 85! = 90 * 89 * 88 * 87 * 86 * 85! / 85 !. Bạn có thể thấy điều đó rõ ràng hơn trong sự bình đẳng đó.
HoldOffHunger


6

Mặc dù giai thừa thực sự là một bài tập tốt cho lập trình viên mới bắt đầu, nhưng chúng không hữu ích lắm trong hầu hết các trường hợp và mọi người đều biết cách viết một hàm giai thừa, vì vậy chúng thường không có trong thư viện trung bình.


6
Tôi đồng ý với bạn, có nhiều hàm toán học quan trọng hơn. Nhưng trong mắt tôi phương pháp này nên chuẩn để mọi người có thể sử dụng lại. Không cần thực hiện nhiều lần bởi nhiều người. Đối với mục đích giáo dục thay thế có thể làm. Nhưng đối với công việc hàng ngày thì nó đã lỗi thời. Đây là ý kiến ​​của tôi. Dù sao, cảm ơn vì đã trả lời. Tôi sẽ làm điều đó một mình - lúc khác.

Lợi ích của tiêu chuẩn hóa được đề xuất của bạn là gì? Việc thêm các phương thức vào thư viện chuẩn không phải là không có chi phí. Như những người khác đã chỉ ra, không có một giải pháp tốt nhất . Bạn đề xuất xây dựng ngôn ngữ nào? Có một phương pháp trong thư viện tiêu chuẩn sẽ không giúp bạn tiết kiệm thời gian để hiểu vấn đề của mình và khi bạn đã hoàn thành điều đó, bạn cũng có thể chọn cách triển khai phù hợp nhất cho công việc.
Matt G

2
"... và mọi người đều biết cách viết một hàm giai thừa" messinmotion.com/blog/?p=622
James P.

4
Không đồng ý. Nguyên tố là cần thiết cho Tổ hợp , cần thiết trong nhiều lĩnh vực thiết kế phần mềm. Đối số không bao gồm các giai thừa trong thư viện toán học cài sẵn cũng giống như đối số không có thư viện toán học tích hợp sẵn.
BênFractal

Thật là một phần logic xuất sắc. Hoàn toàn xuất sắc. Thật tệ là các nhà thiết kế lớp java.lang.Math đã không biết về nó khi họ đưa các phương thức abs () vào lib đó.
Igor Soudakevitch

6

tôi tin rằng đây sẽ là cách nhanh nhất, bằng một bảng tra cứu:

private static final long[] FACTORIAL_TABLE = initFactorialTable();
private static long[] initFactorialTable() {
    final long[] factorialTable = new long[21];
    factorialTable[0] = 1;
    for (int i=1; i<factorialTable.length; i++)
        factorialTable[i] = factorialTable[i-1] * i;
    return factorialTable;
}
/**
 * Actually, even for {@code long}, it works only until 20 inclusively.
 */
public static long factorial(final int n) {
    if ((n < 0) || (n > 20))
        throw new OutOfRangeException("n", 0, 20);
    return FACTORIAL_TABLE[n];
}

Đối với kiểu gốc long(8 byte), nó chỉ có thể chứa tối đa20!

20! = 2432902008176640000(10) = 0x 21C3 677C 82B4 0000

Chắc chắn, 21! sẽ gây tràn.

Do đó, đối với kiểu gốc long, chỉ tối đa 20!là được phép, có nghĩa và chính xác.


1
Ý tưởng khá hay. xem xét, 20 đầu tiên của giai thừa đó có lẽ là đủ, tôi sẽ thêm một hằng số tĩnh (không cần tính toán nó mỗi khi khởi chạy ứng dụng) vào lớp Math với những dữ liệu đó được cung cấp. Thực tế là không nhiều người cần giai thừa trong mã của họ là một lý do tồi tệ để không hỗ trợ nó trong lớp Toán.
TG

6

Vì giai thừa phát triển rất nhanh, nên việc tràn ngăn xếp không phải là vấn đề nếu bạn sử dụng đệ quy. Trên thực tế, giá trị của 20! là cái lớn nhất có thể đại diện trong một Java dài. Vì vậy, phương thức sau đây sẽ tính giai thừa (n) hoặc ném IllegalArgumentException nếu n quá lớn.

public long factorial(int n) {
    if (n > 20) throw new IllegalArgumentException(n + " is out of range");
    return (1 > n) ? 1 : n * factorial(n - 1);
}

Một cách khác (hay hơn) để làm điều tương tự là sử dụng thư viện luồng của Java 8 như sau:

public long factorial(int n) {
    if (n > 20) throw new IllegalArgumentException(n + " is out of range");        
    return LongStream.rangeClosed(1, n).reduce(1, (a, b) -> a * b);
}

Đọc thêm về Factorials sử dụng các luồng của Java 8



6

Câu trả lời ngắn gọn là: sử dụng đệ quy.

Bạn có thể tạo một phương thức và gọi phương thức đó ngay trong cùng một phương thức một cách đệ quy:

public class factorial {

    public static void main(String[] args) {
        System.out.println(calc(10));
    }

    public static long calc(long n) {
        if (n <= 1)
            return 1;
        else
            return n * calc(n - 1);
    }
}

5
chức năng đệ quy là tốt đẹp, nhưng nếu ai đó sẽ cố gắng đếm một fatiorial thực sự lớn, họ sẽ kết thúc với StackOverflowException;) + Im không chắc chắn, nhưng tôi nghĩ rằng đệ quy là chậm hơn so với phương pháp lặp cũ tốt;)
TG

Làm thế nào bạn có thể biết nó sẽ kết thúc với ngoại lệ stackoverflow? @TG
gumuruh

2
Đó là đơn giản. mỗi đệ quy đang đặt vị trí hiện tại trên ngăn xếp để chương trình sẽ có 'bộ nhớ' về vị trí để quay lại sau khi kết thúc cuộc gọi phương thức. Ngăn xếp có giới hạn của nó. để thử nó cho chính mình, hãy thử trong mã ở trên thay đổi System.out.println(calc(10));để System.out.println(calc(Long.MAX_VALUE));bạn sẽ nhận được stactrace khá dài :)
TG

@TG Để rõ ràng hơn, tôi đã thử phương pháp đệ quy đang hoạt động BigInteger. Tôi đã cố gắng tính giai thừa của số 8020cho tôi kết quả 613578884952214809325384...27831chữ số thập phân. Vì vậy, ngay cả khi làm việc với những con số khổng lồ không có Stackoverflowsẽ bị ném ra. Ofcourse đúng youre nhưng tôi nghi ngờ có những con số mà lớn với việc sử dụng dụng thiết thực :-)
Moritz Schmidt

3

Thử cái này

public static BigInteger factorial(int value){
    if(value < 0){
        throw new IllegalArgumentException("Value must be positive");
    }

    BigInteger result = BigInteger.ONE;
    for (int i = 2; i <= value; i++) {
        result = result.multiply(BigInteger.valueOf(i));
    }

    return result;
}

3
Tôi tin rằng có một lỗi trong vòng lặp for: nó phải như vậy i <= value. Vòng lặp for có thể được tối ưu hóa một chút thành (int i = 2; i <= value; i++).
Chris

2

Tôi đã tìm thấy một thủ thuật tuyệt vời để tìm thừa số chỉ bằng một nửa số phép nhân thực tế.

Hãy kiên nhẫn vì đây là một bài viết hơi dài.

Đối với số chẵn: Để giảm một nửa phép nhân với số chẵn, bạn sẽ có n / 2 thừa số. Thừa số đầu tiên sẽ là số bạn đang lấy giai thừa, sau đó thừa số tiếp theo sẽ là số đó cộng với số đó trừ đi hai. Số tiếp theo sẽ là số trước đó cộng với số được cộng cuối cùng trừ đi hai. Bạn đã hoàn tất khi số cuối cùng bạn thêm vào là hai (tức là 2) . Điều đó có lẽ không có nhiều ý nghĩa, vì vậy hãy để tôi cho bạn một ví dụ.

8! = 8 * (8 + 6 = 14) * (14 + 4 = 18) * (18 + 2 = 20)

8! = 8 * 14 * 18 * 20 which is **40320** 

Lưu ý rằng tôi bắt đầu với 8, sau đó số đầu tiên tôi thêm vào là 6, sau đó 4, sau đó là 2, mỗi số thêm vào là hai ít hơn sau đó số thêm vào trước nó. Phương pháp này tương đương với việc nhân số ít nhất với số lớn nhất, chỉ với phép nhân ít hơn, như sau:

8! = 1 * 2 * 3 * 4 * 5 * 6 * 7 * 
8! = (1 * 8) * (2 * 7) * (3 * 6) * (4 * 5)
8! = 8 * 14 * 18 * 20

Đơn giản phải không :)

Now For Odd Numbers: Nếu là số lẻ, phép cộng cũng giống như khi bạn trừ hai lần mỗi lần, nhưng bạn dừng lại ở ba. Tuy nhiên, số lượng các yếu tố thay đổi. Nếu bạn chia số cho hai, bạn sẽ có một số kết thúc bằng .5. Lý do là nếu chúng ta nhân các đầu với nhau, chúng ta sẽ còn lại với số ở giữa. Về cơ bản, điều này đều có thể được giải quyết bằng cách giải cho một số thừa số bằng số bị chia cho hai, làm tròn. Điều này có lẽ cũng không có ý nghĩa gì đối với những người không có nền tảng toán học, vì vậy hãy để tôi làm một ví dụ:

9! = 9 * (9 + 7 = 16) * (16 + 5 = 21) * (21 + 3 = 24) * (roundUp(9/2) = 5)

9! = 9 * 16 * 21 * 24 * 5 = **362880**

Lưu ý: Nếu bạn không thích phương pháp này, bạn cũng có thể lấy giai thừa của số chẵn trước số lẻ (trong trường hợp này là tám) và nhân nó với số lẻ (tức là 9! = 8! * 9).

Bây giờ hãy triển khai nó trong Java:

public static int getFactorial(int num)
{
    int factorial=1;
    int diffrennceFromActualNum=0;
    int previousSum=num;

    if(num==0) //Returning  1 as factorial if number is 0 
        return 1;
    if(num%2==0)//  Checking if Number is odd or even
    { 
        while(num-diffrennceFromActualNum>=2)
        {
            if(!isFirst)
            {
                previousSum=previousSum+(num-diffrennceFromActualNum);  
            }
            isFirst=false;
            factorial*=previousSum;
            diffrennceFromActualNum+=2;
        }
    }
    else // In Odd Case (Number * getFactorial(Number-1))
    {
        factorial=num*getFactorial(num-1);
    }
    return factorial;
}

isFirstlà một biến boolean được khai báo là static; nó được sử dụng cho trường hợp đầu tiên mà chúng ta không muốn thay đổi tổng trước đó.

Thử với số chẵn cũng như số lẻ.


2

Bạn có thể sử dụng đệ quy.

public static int factorial(int n){    
      if (n == 0)    
        return 1;    
      else    
        return(n * factorial(n-1));    
     }

và sau đó sau khi bạn tạo phương thức (hàm) ở trên:

System.out.println(factorial(number of your choice));  
    //direct example
    System.out.println(factorial(3));

1

Cách sử dụng kinh doanh duy nhất cho giai thừa mà tôi có thể nghĩ đến là công thức Erlang B và Erlang C, và không phải ai cũng làm việc trong trung tâm cuộc gọi hoặc công ty điện thoại. Tính hữu ích của một tính năng đối với doanh nghiệp dường như thường ra lệnh cho những gì hiển thị bằng một ngôn ngữ - hãy xem tất cả các chức năng xử lý dữ liệu, XML và web bằng các ngôn ngữ chính.

Thật dễ dàng để giữ một đoạn mã giai thừa hoặc hàm thư viện cho những thứ như thế này.


1

Một phương pháp rất đơn giản để tính thừa số:

private double FACT(double n) {
    double num = n;
    double total = 1;
    if(num != 0 | num != 1){
        total = num;
    }else if(num == 1 | num == 0){
        total = 1;
    }
    double num2;
    while(num > 1){
        num2 = num - 1;
        total = total * num2;
        num = num - 1;
    }
    return total;
}

Tôi đã sử dụng double vì chúng có thể chứa số lượng lớn, nhưng bạn có thể sử dụng bất kỳ loại nào khác như int, long, float, v.v.

Tái bút Đây có thể không phải là giải pháp tốt nhất nhưng tôi mới làm quen với lập trình và tôi đã mất nhiều thời gian để tìm một mã đơn giản có thể tính toán giai thừa vì vậy tôi phải tự viết phương pháp nhưng tôi đang đặt nó ở đây để nó giúp những người khác như tôi .


1

Bạn cũng có thể sử dụng phiên bản đệ quy.

static int myFactorial(int i) {
    if(i == 1)
        return;
    else
        System.out.prinln(i * (myFactorial(--i)));
}

Đệ quy thường kém hiệu quả hơn vì phải đẩy và bật các đệ quy, vì vậy việc lặp lại nhanh hơn. Mặt khác, các phiên bản đệ quy sử dụng ít hơn hoặc không có biến cục bộ, đó là lợi thế.


1

Giai thừa là một chức năng rời rạc ngày càng tăng cao, vì vậy tôi nghĩ rằng sử dụng BigInteger tốt hơn là sử dụng int. Tôi đã triển khai mã sau đây để tính giai thừa của các số nguyên không âm. Tôi đã sử dụng đệ quy thay vì sử dụng một vòng lặp.

public  BigInteger factorial(BigInteger x){     
    if(x.compareTo(new BigInteger("1"))==0||x.compareTo(new BigInteger("0"))==0)
        return new BigInteger("1");
    else return x.multiply(factorial(x.subtract(new BigInteger("1")))); 
}

Ở đây phạm vi của số nguyên lớn là

-2^Integer.MAX_VALUE (exclusive) to +2^Integer.MAX_VALUE,
where Integer.MAX_VALUE=2^31.

Tuy nhiên, phạm vi của phương pháp giai thừa được đưa ra ở trên có thể được mở rộng lên đến hai lần bằng cách sử dụng BigInteger không dấu.


1

Chúng tôi có một dòng duy nhất để tính toán nó:

Long factorialNumber = LongStream.rangeClosed(2, N).reduce(1, Math::multiplyExact);

1

Một phương pháp khá đơn giản

    for ( int i = 1; i < n ; i++ )
    {
            answer = answer * i;
    }

1
    /**
import java liberary class

*/
import java.util.Scanner;

/* class to find factorial of a number
*/

public class factorial
{
public static void main(String[] args)
{

// scanner method for read keayboard values

    Scanner factor= new Scanner(System.in);

    int n;
    double total = 1;
    double sum= 1;

    System.out.println("\nPlease enter an integer: ");
    n = factor.nextInt();

// evaluvate the integer is greater than zero and calculate factorial

if(n==0)

{
    System.out.println(" Factorial of 0 is 1");
}
else if (n>0)
{
    System.out.println("\nThe factorial of " + n + " is " );

    System.out.print(n);

    for(int i=1;i<n;i++)
    {
        do // do while loop for display each integer in the factorial
              {
                System.out.print("*"+(n-i) );
              }

        while ( n == 1);

      total = total * i;

    }

// calculate factorial
sum= total * n;


// display sum of factorial

    System.out.println("\n\nThe "+ n +" Factorial is : "+" "+ sum);
}

// display invalid entry, if enter a value less than zero

else

{
    System.out.println("\nInvalid entry!!");

}System.exit(0);
}
}

0
public static int fact(int i){
    if(i==0)
       return 0;
    if(i>1){
       i = i * fact(--i);
    }

   return i;
}

1
Tôi nghĩ OP đang hỏi nếu có một hàm trong API, chứ không phải cách viết một hàm. Ngoài ra, 0! = 1 - bạn có thể muốn cập nhật mã của mình để bao gồm trường hợp đó.
SL Barth - Phục hồi Monica vào

sẽ luôn luôn có kết quả là 0
Tom Brito

0

Chúng ta cần triển khai lặp đi lặp lại. Nếu chúng ta triển khai đệ quy, nó sẽ gây ra StackOverflow nếu đầu vào trở nên rất lớn (tức là 2 tỷ). Và chúng ta cần sử dụng số kích thước không bị ràng buộc như BigInteger để tránh tràn số học khi một số giai thừa trở nên lớn hơn số tối đa của một kiểu nhất định (tức là 2 tỷ đối với int). Bạn có thể sử dụng int cho tối đa 14 giai thừa và long cho tối đa 20 giai thừa trước khi tràn.

public BigInteger getFactorialIteratively(BigInteger input) {
    if (input.compareTo(BigInteger.ZERO) <= 0) {
        throw new IllegalArgumentException("zero or negatives are not allowed");
    }

    BigInteger result = BigInteger.ONE;
    for (BigInteger i = BigInteger.ONE; i.compareTo(input) <= 0; i = i.add(BigInteger.ONE)) {
        result = result.multiply(i);
    }
    return result;
}

Nếu bạn không thể sử dụng BigInteger, hãy thêm kiểm tra lỗi.

public long getFactorialIteratively(long input) {
    if (input <= 0) {
        throw new IllegalArgumentException("zero or negatives are not allowed");
    } else if (input == 1) {
        return 1;
    }

    long prev = 1;
    long result = 0;
    for (long i = 2; i <= input; i++) {
        result = prev * i;
        if (result / prev != i) { // check if result holds the definition of factorial
            // arithmatic overflow, error out
            throw new RuntimeException("value "+i+" is too big to calculate a factorial, prev:"+prev+", current:"+result);
        }
        prev = result;
    }
    return result;
}

0
public int factorial(int num) {
        if (num == 1) return 1;
        return num * factorial(num - 1);
}

Nên sử dụng dài hoặc BigInteger;)
AxelH

0

vòng lặp while (dành cho số lượng nhỏ)

public class factorial {

public static void main(String[] args) {
    int counter=1, sum=1;

    while (counter<=10) {
        sum=sum*counter;
        counter++;
   }

    System.out.println("Factorial of 10 is " +sum);
   }
}

0

Tôi nhận được cái này từ EDX sử dụng nó! nó được gọi là đệ quy

   public static int factorial(int n) {
    if (n == 1) {
        return 1;
    } else {
        return n * factorial(n-1);
    }
}

0

với đệ quy:

public static int factorial(int n)
{
    if(n == 1)
    {
        return 1;
    }               
    return n * factorial(n-1);
}

với vòng lặp while:

public static int factorial1(int n)
{
    int fact=1;
    while(n>=1)
    {
        fact=fact*n;
        n--;
    }
    return fact;
}

0

SỬ DỤNG LẬP TRÌNH ĐỘNG LÀ HIỆU QUẢ

nếu bạn muốn sử dụng nó để tính toán nhiều lần (như bộ nhớ đệm)

Mã Java:

int fact[]=new int[n+1]; //n is the required number you want to find factorial for.
int factorial(int num)
 {
    if(num==0){
     fact[num]=1;
     return fact[num];
       }
     else
       fact[num]=(num)*factorial(num-1);

     return fact[num];
 }

0

sử dụng đệ quy là phương pháp đơn giản nhất. nếu chúng ta muốn tìm giai thừa của N, chúng ta phải xét hai trường hợp N = 1 và N> 1 vì trong giai thừa chúng ta cứ nhân N, N-1, N-2 ,,,,, cho đến 1 nếu chúng ta chuyển đến N = 0, chúng ta sẽ nhận được 0 cho câu trả lời. Để ngăn giai thừa bằng không, phương pháp đệ quy sau được sử dụng. Bên trong hàm giai thừa, khi N> 1, giá trị trả về được nhân với một lần khởi tạo khác của hàm giai thừa. điều này sẽ giữ cho mã gọi một cách đệ quy giai thừa () cho đến khi nó đạt đến giá trị N = 1. Đối với trường hợp N = 1, nó trả về chính N (= 1) và tất cả kết quả đã xây dựng trước đó của trả về nhân N sẽ được nhân với N = 1. Do đó cho kết quả giai thừa.

static int factorial(int N) {
    if(N > 1) { 
    return n * factorial(N - 1);
    }
    // Base Case N = 1
    else { 
    return N;
    }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.