Какова временная сложность печати целого числа?

Я пытаюсь понять временную сложность простой программы, печатающей целое число. Код Python выглядит следующим образом:

n = 123456789
print(n)

Сначала я думал, что сложность O(1), поскольку печать кажется операцией с постоянным временем. Однако, учитывая, что n имеет цифры мм, где m≈log⁡10(n)+1, кажется, что печать каждой цифры займет O(1) время, что усложнит общую сложность O(m).

Учитывая, что m — это количество цифр, пропорциональное log⁡10(n), означает ли это, что временная сложность на самом деле равна O(log⁡n)?

Может ли кто-нибудь уточнить, равна ли временная сложность печати целого числа O(1) или O(log⁡n)? Если это O(log⁡n), не могли бы вы объяснить почему подробнее?

Спасибо!

Это было бы так же быстро, как O(logn), если бы вы могли воспроизводить каждую цифру числа за постоянное время. Это было бы разумным предположением для системы счисления степени 2, такой как шестнадцатеричная, но это явно неверно для десятичной системы: извлечение каждой цифры - это, по сути, операция деления с остатком, которая займет время, которое зависит от величины номер. Точный ответ будет зависеть от используемого алгоритма деления.

jasonharper 23.06.2024 20:08

@jasonharper, ты имеешь в виду, что это точно нет O(1)?

ThunderPhoenix 23.06.2024 20:17

Этого абсолютно не может быть O(1), учитывая, что объем вывода разный!

jasonharper 23.06.2024 20:19

Поскольку размер вывода равен O(log n), а каждая цифра должна стоить вам еще O(log n), общая сложность должна быть близка к O(log^2(n)). Эксперименты с Python подтверждают, что время выполнения примерно пропорционально квадрату количества цифр (для чисел до 1000000 десятичных цифр).

n. m. could be an AI 23.06.2024 21:00

С практической точки зрения это O(1). Если вы хотите узнать точный результат и вам действительно очень хочется это знать, вам нужно проверить, как он реализован, как хранятся цифры/биты, как они преобразуются в символы, как они отправляются, например. стандартный выход и т. д. В 99,99999% случаев это не имеет значения, и его можно считать O(1).

luk2302 23.06.2024 21:32

@ luk2302, как существует «практическая» точка зрения на большое О? Большой О касается асимптотического поведения, а не 99,99999% случаев.

trincot 23.06.2024 21:35

Это в некоторой степени зависит от контекста. Часто при анализе алгоритмов неявно предполагается, что существует фиксированный максимальный размер для всех целых величин, если только из контекста не очевидно, что программа предназначена для работы со значениями произвольной точности («большими числами»). При этом предположении печать действительно занимает O(1), поскольку количество цифр также ограничено фиксированной константой.

Nate Eldredge 24.06.2024 01:28
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
7
109
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Временная сложность печати целых чисел в Python равна O(log(n)^2), что можно увидеть, наблюдая за временем, затрачиваемым на str(n) для все более больших n (Python 3.10.12 в Linux):

$ python3 -m timeit -s 'n=1<<512' 'str(n)'
100000 loops, best of 5: 1.4 usec per loop
$ python3 -m timeit -s 'n=1<<1024' 'str(n)'
50000 loops, best of 5: 4.46 usec per loop
$ python3 -m timeit -s 'n=1<<2048' 'str(n)'
10000 loops, best of 5: 15.9 usec per loop
$ python3 -m timeit -s 'n=1<<4096' 'str(n)'
5000 loops, best of 5: 59.5 usec per loop
$ python3 -m timeit -s 'n=1<<8192' 'str(n)'
1000 loops, best of 5: 231 usec per loop

Каждый раз, когда мы удваиваем размер n (удваиваем log(n)), количество времени увеличивается в 4 раза, что почти идеально соответствует O(log(n)^2).

Почему это O(log(n)^2)? Получение каждой цифры вывода включает многократное деление числа на 10. Само деление требует времени, пропорционального длине числа. Следовательно, мы делаем O(log(n)) делений, каждое из которых стоит O(log(n)) времени.

Мы можем добиться гораздо большей временной сложности, используя шестнадцатеричное представление, а не десятичное. Поскольку числа внутренне представлены в двоичном формате, преобразование в шестнадцатеричное не требует деления; вместо этого каждая группа из четырех битов напрямую преобразуется в соответствующую шестнадцатеричную цифру. Таким образом, мы ожидаем, что временная сложность шестнадцатеричного вывода будет равна O(log(n)), и это именно то, что мы видим:

$ python3 -m timeit -s 'n=10**150' 'hex(n)'
500000 loops, best of 5: 325 nsec per loop
$ python3 -m timeit -s 'n=10**300' 'hex(n)'
500000 loops, best of 5: 554 nsec per loop
$ python3 -m timeit -s 'n=10**600' 'hex(n)'
200000 loops, best of 5: 1.04 usec per loop
$ python3 -m timeit -s 'n=10**1200' 'hex(n)'
100000 loops, best of 5: 2 usec per loop
$ python3 -m timeit -s 'n=10**2400' 'hex(n)'
50000 loops, best of 5: 3.9 usec per loop

Здесь я использую степени десяти, сравнимые по размеру с приведенными выше двоичными числами, чтобы не просто выводить длинные строки нулей.

Квадратичная сложность преобразования больших чисел в десятичные числа и обратно привела к тому, что Python ввел ограничения на длину десятичных чисел в Python 3.11, чтобы избежать атак типа «отказ в обслуживании»; см. https://docs.python.org/3/library/stdtypes.html#int-max-str-digits для более подробной информации. В этом документе также упоминается алгоритм субквадратичной сложности. Точная сложность этого алгоритма зависит от используемого алгоритма деления. Он не используется в Python, поскольку его немного сложнее реализовать, но он реализован в GNU GMP (доступен через пакет gmpy).

Другие вопросы по теме