Я столкнулся с проблемой, когда мне нужно было рассчитать значения очень больших факториалов. Я решил эту проблему на С++ двумя разными способами, но хочу знать, действительно ли мой анализ сложности.
В любом из методов я представляю очень большие числа в виде векторов, где v[0] представляет наименее значащую цифру, а значение последнего индекса представляет собой наиболее значимую цифру. Код версии 1 можно найти в этом gist.
Учитывая приведенный выше код, кажется, что multiplyVectorByInteger() есть O(log(n*k)), где n - заданное целое число, а k - это число, представленное вектором. Моя логика заключается в том, что мы будем делать несколько шагов пропорционально длине результирующего числа n*k, чтобы создать вектор, представляющий n*k. Длина n*k равна O(log(n*k)) Некоторые из шагов будут выполняться в цикле for, другие - в цикле while.
В этой программе для поиска больших факториалов, всякий раз, когда мы называем multiplyVectorByInteger(), мы будем передавать целое число n и векторное представление (n-1)!. Это означает, что если мы хотим найти 6!, мы передадим целое число 6 и векторное представление 5!. Функция вернет векторное представление 6!. Используя предыдущую информацию, я считаю, что сложность - это O(log(i!)), где я - это переданное целое число. Чтобы найти большие факториалы, мы должны назвать этот метод O(n) times, где n - факториал, который мы пытаемся найти. Наша накопленная логика будет выглядеть так:
1! = 1!
1!*2 = 2!
2!*3 = 3!
3!*4 = 4!
...
(n-1)!*n = n!
Так как на каждом уровне мы вычисляем i!, мы последовательно выполняем шаги O(log(i!)) на каждом уровне. Суммирование, чтобы показать это, выглядит следующим образом:

Моя логика от перехода от второго суммирования к нотации Big-Oh выглядит следующим образом: нарушая это, получаем следующее:
1log(1) + 2log(2) + 3log(3) + ... + nlog(n)
Очевидно, что мы получаем O(n^2) члены log(1) + log(2) + ... + log(n). Правила журнала напоминают нам, что log(a) + log(b) = log(ab), что означает, что члены журнала в этом случае сворачиваются на log(n!). Таким образом, имеем O(n^2)log(n!).
Это создаст общую временную сложность этой программы O(n^2log(n!)). Правильно ли этот анализ?
Максимальная временная сложность версии
Чтобы анализировать сложность, я хочу взглянуть на то, что кажется менее эффективным решением. Предположим, мы изменили нашу функцию multiplyVectorByInteger() так, чтобы вместо умножения векторного представления k на целое число n в O(log(n!)) времени для создания n!, новая функция multiplyVectorByIntegerNaive() добавляет векторное представление числа вместе в общей сложности n раз.
multiplyVectorByIntegerNaive() существует в gist. В нем используется функция addVectors(), сложность которой O(n) где n размер большего из двух векторов.
Ясно, что мы по-прежнему вызываем эту новую функцию умножения n раз, но нам нужно увидеть, изменилась ли сложность. Например, учитывая целое число 6 и векторное представление 5!, добавим 5! + 5! + 5! + 5! + 5! + 5!, чтобы получить 6*5! = 6!. Если заданное целое число для нашей функции умножения i, то ясно, что мы делаем дополнения i-1. Мы можем перечислять шаги для предыдущего примера вызова нашей наивной функции умножения.
5! + 5!
2*5! + 5!
3*5! + 5!
4*5! + 5!
5*5! + 5!
Списание полного суммирования должно теперь давать:

Похоже, что асимптотическая сложность обоих методов одинакова, если мои вычисления точны. Это правда?