Компьютерный форум OSzone.net  

Компьютерный форум OSzone.net (http://forum.oszone.net/index.php)
-   Программирование и базы данных (http://forum.oszone.net/forumdisplay.php?f=21)
-   -   Ускорение поиска числа факториала (http://forum.oszone.net/showthread.php?t=108223)

DaRiYs 01-06-2008 13:00 815795

Ускорение поиска числа факториала
 
Люди помогите сделать бысрый алгоритм нахождения числа из его факториала.
Я писал вот такой код но он оч медленный
Код:

#include <iostream.h>
#include <fstream.h>
int faknom(int a)
{
        int i=1;
        while(a!=i)
        {
                a=a/i;
                i++;
        };
        return i;
};
int main()
{
        int Nfak;
        ifstream fin("input.txt");
        fin>>Nfak;
        fin.close();
        ofstream fout("output.txt");
        fout<<faknom(Nfak)<<endl;
        fout.close();
        return 0;
}

В файле input.txt лежит значение факториала N! числа N, а в файле output.txt должно быть записано значение N.
Как ускорить алгоритм расчета?

Altair86 01-06-2008 13:47 815832

Мне кажется, алгоритм расчета тут не ускорить. Если бы числа были большими, можно было бы поискать приближенную формулу для вычисления. А так-- только последовательное деление на целые числа, начиная с 1, все правильно. int 32-разрядные вроде? примерно до 2*10^9-- это факториалы чисел до 12 или 13 только.

Coutty 01-06-2008 13:50 815833

Не понимаю, почему медленно он работает.
Вот пример на JavaScript'е (ну не знаю я C), который отрабатывает мгновенно (учитывайте, что int - не безразмерен):
HTML код:

var fak = 39916800; // можно оформить в виде функции и передавать это число в неё, но для теста пойдёт и простое присваивание. Это число - факториал от 11.
var i = 2;
while(i) {
  if (fak == i)
    {
    alert("Заданное число является факториалом от " + i);
    break;
    }
  if (fak/i == parseInt(fak/i))
    fak = fak/i;
  else
    {
    alert("Нет такого числа!");
    break;
    }
  i++;
}

Попробуйте изменить fak, скажем, на 39916801 и число найдено не будет.

DaRiYs 01-06-2008 13:51 815836

Altair86 Нет я помню точно гдето видел чото тип какойто формулы или спец метода. Но не помню где.
По инету шарил но ненашол. :(
Алгоритм быстрее этого точно есть.
Coutty мне нада скорость до 1 секунды, а при некоторых значениях скорость 1,015сек. Ето в моей ситуации неприпустимо. Используются факториалы чисел до 2000, тоесть максимально 2000!

ivank 01-06-2008 14:00 815846

http://en.wikipedia.org/wiki/Factorial#Rate_of_growth

Там есть только приблизительные формулы, для вычисления n! по n, Если вычислять в обратную сторону по требуется на порядок больше операций, чем для честного деления. Потому что, уже указали, что в int поместится только 12!. Т.е. в для такой операции достаточно всего 12 операций делений.

Drongo 01-06-2008 14:04 815855

DaRiYs,
Цитата:

Цитата DaRiYs
мне нада скорость до 1 секунды, а при некоторых значениях скорость 1,015сек. »

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

Coutty 01-06-2008 14:06 815858

DaRiYs, так вот Altair86 и говорит - в Int влезет факториал не более чем от 12 или 13.

Иногда для скорости можно пожертвовать объёмами. Используйте подстановочные таблицы с заранее сопоставленными величинами (пусть не для всех чисел, а, скажем, каждое двадцатое. Остальные просчитывать).
Алгоритм:
1. Находим число (a), которое меньше полученного из файла (f) (следующее число из таблицы будет уже больше).
2. Определяем сопоставляемое этому значению число (b).
3. И число (a) умножаем на (b++), пока произведение не будет равно или не превысит (f).

Drongo, перед выполняемой функцией определяешь системное время с точностью до миллисекунд, потом то же самое после функции. И сравниваешь значения. Если интересно, ещё по поводу замеров fps могу посоветовать :)

DaRiYs 01-06-2008 14:16 815870

А может этот кусок кода на ассемблере написать? Ток я ассемблера незнаю :( :(

ivank 01-06-2008 14:38 815881

DaRiYs, Этот код работает долго для факториалов больше 13, потому что он неправильный. В какой-то момент он превращается в перебор всех целых чисел до нуля. Можете вставить отладочную печать и убедиться в этом самостоятельно.

DaRiYs 01-06-2008 14:41 815884

Цитата:

Цитата ivank
DaRiYs, Этот код работает долго для факториалов больше 13, потому что он неправильный. »

А какой правильный тогда?

ivank 01-06-2008 14:47 815888

Такой, который использует длинную арифметику. Лучше не a делить на i, а ввести некоторое b, которое домножать на i до тех пор пока b < a. (потому что умножение длинных чисел сильно быстрее деления)

DaRiYs 01-06-2008 15:06 815907

Всеравно исчерпывается лимит времени. :(
Тут полный проход по деления или умножению некатит. Над
какойто иной способ.
А на ассемблере что никто и нескажет как написать?

Altair86 01-06-2008 15:21 815920

А для чисел меньше 12 за сколько времени прога срабатывает?

Admiral 01-06-2008 15:33 815933

На ассемблере (как и в Паскале и в С) есть так называемые операции сдвига:
вправо (shr) и влево (shl). Для десятичной системы это выглядит так
Код:

3 shl 5 => (3x(2в пятой степени))
5 shr 3 =>(5/(2в третей степени))

На примере двоичной системе становится ясно откуда они получили своё имя
00010101 shl 3 => 10101000
и в другую сторону при shr
Они могут быть полезны, если придумать как их здесь можно применить.

DaRiYs 01-06-2008 16:09 815962

Че придумывать если я ассемблера незнаю? :(

mrcnn 01-06-2008 16:13 815966

Почему бы не создать список всех факториалов до 2000 в файле!?

номер факториала соответствует номеру строки.
Считываем заданное число, помещаем в строку, определяем длину строки

и начинаем сравнивать длину строки заданного числа с длинами строк в файле,
Начать нужно не с нуля, а со строки номер которой равен длине строки заданного числа.

Если длины строки равны, то номер строки в файле искомый факториал.

Таким образом задача избавляется от чрезмерно быстрого факториального роста. задача сводится к поиску. Можно создать 2000 файлов, чтобы не мучать один файл и сравнивать уже количество байт файла в котором заданное число и количество байт в файле, в котором факториалы. Просто тупо проверяем есть ли файл заданного размера. Без вычислений.

DaRiYs 01-06-2008 16:24 815970

Понимаете ето задача для онлайн контестера тип АСМ. Время выполнения программы для даной задачи ограничено 1 секундой и обьем кода до 4кб. И кроме кода сторонних фаилов использовать нельзя.

Admiral 01-06-2008 16:28 815974

DaRiYs, я указал что
Цитата:

Цитата Admiral
как и в Паскале и в С »

во вторых я показал математику этого оператора
Цитата:

Цитата Admiral
Код:

3 shl 5 => (3x(2в пятой степени))
5 shr 3 =>(5/(2в третей степени))

»

Я оператор с ассемблером связал, так как при изучении его с ним познакомился, а потом обнаружил его в С и Паскале. (вернее в С я про него узнал, но природу процесса понял при изучении ассемблера).

mrcnn 01-06-2008 16:38 815985

Надо было сразу указывать эти ограничения. Что такое контестер, что такое ACM и что такое контестер типра ACM?

Drongo 01-06-2008 16:40 815987

Coutty,
Цитата:

Цитата Coutty
Drongo, перед выполняемой функцией определяешь системное время с точностью до миллисекунд »

Если честно, то я не знаю, как его определять?! Можно на примере или прочесть где?! Чтобы доходчиво было.

ivank 01-06-2008 17:26 816011

Цитата:

Цитата DaRiYs
Время выполнения программы для даной задачи ограничено 1 секундой и обьем кода до 4кб. »

Обычно 64кб ограничение на размер исходника (по ACM'овским правилам).

Цитата:

Цитата mrcnn
Что такое контестер, что такое ACM и что такое контестер типра ACM? »

http://en.wikipedia.org/wiki/ACM_Int...amming_Contest

Так вот, если ограничение всё-таки 64 кб, то спокойно можно сделать таблицу. (длина десятичной записи n!) -> значение n. Длина для всех факториалов начиная с 10! точно различна (для значений меньше можно использовать честный подбор). Длина 2000! - 5737. Элементарная программа на питоне (или любом другом языке, в котором встроена поддержка больших чисел) легко позволяет построить такую таблицу за пару минут.

DaRiYs 01-06-2008 17:43 816025

Ну так у меня ограничение кода до 4 кб. Эт не АСМ а ему подобный

ivank 01-06-2008 17:52 816032

Если очень хочется извратиться и впихнуть всё в 4кб, то можно закодировать таблицу в виде сдвигов между соседними значениями. Т.к. мы умножаем максимум на 2000, то длина изменяется не более, чем на 4 знака каждый раз; но не менее, чем на один. Для хранения этой информации достаточно 2 бит. А для хранения всех сдвигов - 4000 бит, которые можно записать в виде строки (если использовать только печатные символы из нижней половины ascii, чтобы можно было вставить в исходный текст без изменений) всего в 4000/log2(64)=667 символов, ещё 3кб на код останется :). И потом декодировать оттуда нужную информацию небольшим кусочком кода.

Всё решение будет типа такого:
Код:

unsigned char[] diff_table="blah-blah blah..."; //667 символов таблицы.

// получить i-й сдвиг из таблицы.
int getDiff(int i) {
    unsigned char c = diff_table[i/6] - 32;
    int diff = (c >> ((i%6)*2)) & 3;
    return diff;
}
// ....
char fac_str[10000];
cin >> fac_str; // зачитываем факториал, _как строку_

int need_len = strlen(fac_str); // длина факториала, для которого надо найти n
int n = 0; // ответ будет здесь
if (need_len <= 8) {
    // n <= 10, тупо подбираем
    int facn = 1;
    int fac = atoi(fac_str);
    for (n = 1; facn < fac; ++n) {
        facn *= n;
    }
} else {
    int len = 8; // длина факториала 10
    // начинаем подбирать с 11
    for (n = 11; len < need_len; ++n) {
        len += getDiff(n);
    }
}

А можно, всё-таки, честно запрограммировать длинное умножение и не париться с извращениями. Ссылку в яндекс, где объясняется, как правильно умножать длинные числа, я уже бросал.

Altair86 01-06-2008 18:53 816069

Могу предложить еще вариант без длинной арифметики. Не знаю, будет ли он работать быстрее, поскольку там много вычислений с плавающей точкой... и влезет ли в нужный объем.
Правильно работать он будет, только если число в файле заведомо является факториалом.
Способ: приближенное значение натурального логарифма факториала числа n равно
ln(n!)=1/2(ln(2)+ln (Pi))+ln(n)+n*ln(n)-n
десятичный логарифм равен
Log(n!)=ln(n!)/ln(10)
Количество знаков в факториале будет равно Log(n!)+1, округленному снизу.
Считываем из файла факториал в виде символьной строки. Считаем количество знаков в этой строке. Дальше ищем на отрезке числовой оси (1;2000) нужное нам число n методом деления отрезка пополам. То есть, проверяем числа 1 и 2000 (считаем по формуле), если кол-ва цифр в их факториалах не подходят, берем число в середине отрезка (например 1000), считаем кол-во цифр в его факториале. Если опять не подходит, берем середину отрезка (1;1000) или (1000;2000)-- в зависимости от того, больше или меньше кол-во знаков в числе из файла, чем в 1000! Дальше снова, максимум за 10--12 шагов число n точно определяется
И одна тонкость: для чисел, меньших 7, лучше сохранить факториалы в файле с программой, и сначала проверять не является ли n одним из этих чисел. после чего делить отрезок не от 1 до 2000, а от 7 до 2000. (потому, что факториалы разных чисел, меньших 7, могут иметь одинаковое кол-во знаков)

DaRiYs 01-06-2008 19:56 816100

Мож хтонибудь всетаки напишет код на ассемблере для использования его в С++

Admiral 01-06-2008 19:57 816102

Цитата:

Цитата Drongo
Можно на примере или прочесть где?! »

Пример для MSVS 6+

Код:

#include <Windows.h>
#include <iostream>

using namespace std;

int main(int argc, char* argv[])
{
SYSTEMTIME st;
GetSystemTime(&st);//GetLocalTime()
cout << "\n" <<st.wSecond << " " <<st.wMilliseconds;
Sleep(1000);
GetSystemTime(&st);//GetLocalTime()
cout << "\n" <<st.wSecond << " " <<st.wMilliseconds;
return 0;
}


Для данного примера разницы между GetSystemTime() и GetLocalTime() нет. А вообще первая возвращает время по Гринвичу UTC, вторая по выбранному свойствах системы часовому поясу.

ivank, а как быть с iostream что б по стандарту? Или это » только в Linux, а micorosoft будет оставлять совместимость в своём компиляторе?
cout удобние printf
Или можно просто вывести значения st.wSecond и st.wMilliseconds через printf не усложняя код?
Со строчкой printf("%s %s",st.wSecond, st.wMilliseconds); компилится, но во время выполнения ошибка естественно
Цитата:

The instruction at "0x00000000" referenced memory at "0x00000000". The memory could not be "read".
Так как не совпадение типов. Чем собственно и удобен cout, что не брезгливый к этому. За это он не в стандарте?

ivank 01-06-2008 20:10 816110

Admiral,
Цитата:

Цитата Admiral
ivank, а как бы по стандарту что в ситуации с iostream? Или это только в Linux, а micorosoft будет оставлять совместимость в своём компиляторе? »

Не понял вопроса :) Заголовочный файл iostream есть в стандарте и всегда будет и в gcc и в msvc. Нет в стандарте заголовочного файла iostream.h, который объявляет всё в глобальной области видимости (а не в std), но абсолютное большинство компиляторов его всё равно поддерживают, как раз для совместимости.

Цитата:

Цитата Admiral
cout удобние printf »

Спорное утверждение :) Если попробовать вывести что-то с более-менее сложным форматом, то форматная строка printf намного удобнее (мне во всяком случае).

Цитата:

Цитата Admiral
Со сторчкой printf("%s %s",st.wSecond, st.wMilliseconds); компилится но во время выполнения, естественно ошибка »

Все приличные компиляторы умеют выдавать warning на такой код, очень уж распространённая ошибка :)

DaRiYs, за вас никто ничего писать не будет. Вам предложили методы решения задачи (целых 3). Если вы чётко объясните, что вас в них не устраивает (или что непонятно), то можно будет продолжать диалог.

Admiral 01-06-2008 20:27 816117

ivank, вопрос навеян этим постом, я об этом в предыдущем посте указал », когда сказал про Linux.
Да я понимаю, что строка printf("%s %s",st.wSecond, st.wMilliseconds); ошибочна. Я к примеру показал сравнения с cout << "\n" <<st.wSecond << " " <<st.wMilliseconds; которой кроме себя самой не нужно приведения типов, что б не вызывать ошибку при выполнении.
Компилятор MS C++ 2008 Express прохлопал всё без Warnings, возможно по дефолтному уровню предупреждений не положено замечать эту строчку как потенциально опасной. Сам я использую в основном printf, но когда отлаживаю программу и ещё не знаю нужной функции приведения типов, использую cout, так как он их щёлкает как орешки и выводит мне нужное значение. Можно и Debug средствами среды узнавать значения переменных, но они не всегда доступны.

ivank 01-06-2008 20:32 816124

Цитата:

Цитата Admiral
ivank, вопрос навеян этим постом, я об этом в предыдущем посте указал », когда сказал про Linux. »

Вероятно я там неясно выразился. <iostream.h> официально не существует, просто <iostream> - вполне есть в стандарте.

Admiral 01-06-2008 20:44 816131

ivank, не прошло и дня.
printf("time %02u:%02u:%02u:%03u\n", st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
и ни каких сout с #include <iostream> и using namespace std;
а лишь printf c #include <stdio.h>
нашёл здесь.

Admiral 13-06-2008 05:24 824893

Цитата:

Цитата ivank
<iostream> - вполне есть в стандарте. »

А может он описан в С++ стандарте, а <iostream.h> в С?
Цитата:

Цитата hasherfrog
ЕМНИП: <iostream.h> используется для С, <iostream> для С++. >>

Или память всё же изменила hasherfrog?

ivank 14-06-2008 00:54 825518

Admiral, нет. В C вообще нет такого понятия, как потоки ввода-вывода. Так что да, у hasherfrog'а изменят память. Со всеми бывает.

Admiral 14-06-2008 01:26 825536

ivank, я никого обвинять не собирался. Дело всё в том что после этой темы стал внимательнее смотреть исходники, форумные сообщения на данный сабж. Самым большим удивлением было увидеть iostream.h в печатной литературе. Автора называть не стану, книга ссылается на стандарт ISO/IEC 14882 (1998). Теперь буду иметь ввиду. :happy:

ivank 14-06-2008 02:36 825573

На крайний случай можно глянуть в драфт стандарта (вторая ссылка): http://www.google.ru/search?q=c%2B%2B+standard Никакого iosteam.h там не упоминается, в отличие от iostream (701 страница). Больше половины нынешней печатной литературы по программированию - мукулатура. К сожалению.

superlom 14-06-2008 04:01 825600

я не понимаю - если известен предел, по которому будет высчитываться факториал (тут n до 2000), то почему бы сперва не создать алгоритмик, который запишет текстовый файлик со всеми значениями n до 2000, и потом будет просто вестись вборка.. факториал же можно высчитать только для натуральных чисел - поэтому никаких дробей.
да.. числа огромные будут - но таблица все равно будет не больше 10кб..
или тут нужен именно алгоритм?? а то меня учили идти по пути наименьшего сопротивления и без выпендрежа)

Busla 14-06-2008 12:39 825746

DaRiYs, поскольку вы просите помочь с оптимизацией - уточните условия по максимуму!

В частности - необходимо вычислить один "обратный" факториал или данный алгоритм будет вызываться из цикла?
На входе только факториалы - т.е. можно ли использовать приближённую оценку?

Использование ассемблера заметных плюсов в скорости выполнения не даст. Примитивный цикл с умножением/делением компилируется довольно эффективно.


Время: 13:24.

Время: 13:24.
© OSzone.net 2001-