2015-01-21 5 views
1

Я студент-программист, и в следующем семестре я собираюсь начать курс C. Поэтому, чтобы немного подготовиться, я начал изучать С сам и наткнулся на интересную задачу, предназначенную, как мне показалось, на первый взгляд, не очень продвинутый уровень.Треугольник Паскаля в C

Задача состоит в том, чтобы написать программу для вычисления значения данной позиции в Треугольник Паскаля. И формула, заданная для вычисления, написана как element = row!/(Позиция * (строка - позиция!))

Я написал простую консольную программу, которая, кажется, работает хорошо, пока я не получаю, чтобы испытывать его с больших чисел.

При попытке этой программы с строкой 16 и позицией 3 она вычисляет значение как 0, хотя очевидно, что такого значения не может (на самом деле оно должно вычислять значение как 560), все ячейки этого треугольник должен быть целым числом и быть больше одного.

Я полагаю, что у меня возникла проблема с хранением и обработкой больших чисел. Факториальная функция, похоже, работает нормально, и используемая мной формула работает до тех пор, пока я не получу попытку больших чисел.

Пока лучшее решение было найдено здесь - How do you printf an unsigned long long int(the format specifier for unsigned long long int)? с использованием библиотеки inttypes.h с типом uint64_t, но это все еще не дает мне нужен результат.

#include <stdio.h> 
#include <stdlib.h> 
#include <inttypes.h> 

void clear_input(void); 
uint64_t factorial(int x); 

int main() 
{ 
    // Printing 
    printf("This program computes the value of a given position in Pascal's Triangle.\n"); 
    printf("You will be asked for row and position of the value.\n"); 
    printf("Note that the rows and positions starts from 0.\n"); 
    printf("\n"); 
    printf("  1   * 0 \n"); 
    printf(" 1 1   * 1 \n"); 
    printf(" 1 2 1  * 2 \n"); 
    printf(" 1 3 3 1  * 3 \n"); 
    printf(" 1 4 6 4 1  * 4 \n"); 
    printf(" **************** \n"); 
    printf(" 0 1 2 3 4   \n"); 
    printf("\n"); 

    // Initializing 
    int row, pos; 

    // Input Row 
    printf("Enter the row: "); 
    scanf("%d", &row); 
    clear_input(); 

    // Input Position 
    printf("Enter the position in the row: "); 
    scanf("%d", &pos); 
    clear_input(); 

    // Initializing 
    uint64_t element, element_1, element_2, element_3, element_4; 

    // Previously written as -> element = (factorial(row))/(factorial(pos) * factorial(row - pos)); 
    // Doesn't fix the problem 
    element_1 = factorial(row); 
    element_2 = factorial(pos); 
    element_3 = factorial(row - pos); 
    element_4 = element_2 * element_3; 

    element = element_1/element_4; 

    // Print result 
    printf("\n"); 
    printf("%"PRIu64"\n", element_1); // Temporary output 
    printf("%"PRIu64"\n", element_2); // Temporary output 
    printf("%"PRIu64"\n", element_3); // Temporary output 
    printf("%"PRIu64"\n", element_4); // Temporary output 
    printf("\n"); 
    printf("The element is %"PRIu64"", element); 
    printf("\n"); 

    return 0; 
} 

void clear_input(void)           // Temporary function to clean input from the keyboard 
{ 
    while(getchar() != '\n'); 
} 

uint64_t factorial(int x)          // Function to calculate factorial 
{ 
    int f = 1, i = x; 
    if (x == 0) { 
     return 1; 
    } 
    while (i != 1) { 
     f = f * i; 
     i = i - 1; 
    } 
    return f; 
} 
+1

Если вы используете большое количество (> 32 бит), то с помощью 'int' собирается получить вас неправильные результаты. Если вы используете тип данных 'uint64_t' для указания типа возврата, для вычисления вашего расчета вам нужно использовать тот же тип данных. Теперь ваша функция использует 'int' внутри, и результат неявно приводится к' uint64_t', что на самом деле вам не поможет. –

ответ

1

Факториалы получают really big really fast (прокрутите вниз немного, чтобы увидеть список). Даже 64-битное число подходит только для 20!. Поэтому перед тем, как начать умножаться, вам нужно сделать небольшую предварительную обработку.

Общая идея состоит в том, чтобы умножить числитель и знаменатель и удалить все общие факторы. Поскольку результаты Треугольника Паскаля всегда целые числа, вам гарантируется, что знаменатель будет равен 1 после удаления всех распространенных факторов.

Например, скажем, у вас есть row=35 и position=10. Тогда расчет

element = 35!/10! * 25! 

который

35 * 34 * 33 * ... * 26 * 25 * 24 * ... * 3 * 2 * 1 
--------------------------------------------------- 
    10!    * 25 * 24 * ... * 3 * 2 * 1 

Таким образом, первое упрощение является то, что чем больше факториала в знаменателе отменяет все меньшие сроки числителя. Какие листья

35 * 34 * 33 * ... * 26 
----------------------- 
10 * 9 * 8 * ... * 1  

Теперь нам нужно удалить оставшиеся общие факторы в числителе и знаменателе. Это помогает поместить все число числителей в массив. Затем для каждого числа в знаменателе вычислите greatest common divisor (gcd) и разделите числитель и знаменатель на gcd.

Следующий код демонстрирует технику.

array[10] = { 35, 34, 33, 32, 31, 30, 29, 28, 27, 26 }; 

for (d = 10; d >= 2; d--) 
{ 
    temp = d; 
    for (i = 0; i < 10 && temp > 1; i++) 
    { 
     common = gcd(array[i], temp); 
     array[i] /= common; 
     temp /= common; 
    } 
} 

Вот что делает код шаг за шагом

d=10 i=0 temp=10 array[0]=35 ==> gcd(35,10)=5, so array[0]=35/5=7 and temp=10/5=2 
d=10 i=1 temp=2 array[1]=34 ==> gcd(34, 2)=2, so array[1]=34/2=17 and temp=2/2=1 
inner loop breaks because temp==1 
d=9 i=0 temp=9 array[0]=7 ==> gcd(7,9)=1, so nothing changes 
d=9 i=1 temp=9 array[1]=17 ==> gcd(17,9)=1, so nothing changes 
d=9 i=2 temp=9 array[2]=33 ==> gcd(33,9)=3, so array[2]=11 and temp=3 
d=9 i=3       ==> gcd(32,3)=1 
d=9 i=4       ==> gcd(31,3)=1 
d=9 i=5 temp=3 array[5]=30 ==> gcd(30,3)=3, so array[5]=10 and temp=1 
inner loop breaks 

Когда все сказано и сделано массив заканчивается как

array[10] = { 1, 17, 11, 1, 31, 1, 29, 14, 3, 26 } 

Умножьте эти цифры вместе, и ответ 183579396, и весь расчет может быть выполнен с использованием 32-битных int. В общем случае, до тех пор, пока ответ вписывается в 32-битные, вычисления могут быть выполнены с 32-битами.

1

При вычислении факториала, даже если вы возвращаете 64-разрядное целое число не будет иметь значение, если вы используете регулярные Int переменные для промежуточных вычислений. Изменение этого:

uint64_t factorial(uint64_t x) 
{ 
    uint64_t f = 1, i = x; 
    if (x == 0) { 
     return 1; 
    } 
    while (i != 1) { 
     f = f * i; 
     i = i - 1; 
    } 
    return f; 
} 

Кроме того, подумайте о том, как вы можете изменить уравнение так, что вам не нужно вычислить действительно большие промежуточные значения. Например, вы можете изменить это:

элемент = (факториал (ряд)/факторный (поз.))/Factorial (row-pos);

Тогда вы не будете умножать два факториала вместе и получать действительно большое количество.

Кроме того, при вычислении factorial (row)/factorial (pos) вы можете исключить термины, которые будут как в факториале (строке), так и в factorial (pos), поэтому вам не нужно вычислять все факториалы.

3

(мой C ржавый, так что это не может быть супер точным)

Вашей факторный функция возвращает uint64_t, но это делает вычисление с регулярным Интсом. Если вы изменили f и i на uint64_t, я думаю, вы избежите текущей проблемы с переполнением целых чисел.

Однако вы все равно столкнетесь с переполнением довольно быстро (uint64_t будет переполняться вокруг 21!). Чтобы этого избежать, вы можете быть немного умнее с алгоритмом. С row = 16 и position = 3 вам нужно 16!/(3! * 13!). Вы можете отменить большинство условий (16!/13! Всего 14 * 15 * 16) и в итоге получится 14 * 15 * 16/(1 * 2 * 3). Это позволит вашей программе идти намного дальше строки 21.

0

Это будет работать:

#include <stdio.h> 

int main() 
    { 
    printf ("\n"); 
    int n = 10; 
    int i; 
    int j; 
    int x[n]; 

    for (i = 0; i < n; i++) 
     x[i] = 0; 

    for (i = 1; i <= n; i++) 
     { 
     for (j = n - 1; j >= 1; j--) 
       x[j] = x[j-1] + x[j]; 

     x[0] = 1; 

     int s = n - i; 

     for (j = 0; j < s; j++) 
       printf (" "); 

     for (j = 0; j < n; j++) 
       { 
       if (x[j] != 0) 
        printf (" %3d", x[j]); 
       } 

     printf ("\n"); 
     } 

    printf ("\n"); 
    return 0; 
    }