Эффективный алгоритм для преобразования между системой счисления
Есть ли эффективный алгоритм преобразования между системой счисления, когда размер исходного целого числа является произвольным?
Например, предположим, что существует целочисленный массив {1, 4, 8}, который в качестве входных данных равен 148 в десятичном формате. Он может быть преобразован в {9, 4} в шестнадцатеричном формате, или {2, 2, 4} в восьмеричном, или {1, 0, 0, 1, 0, 1, 0, 0} в двоичном формате, или просто {148} в 1234-м формате или что-то в этом роде.
Это просто, когда фактическое значение может быть выражено в формате слова, поддерживаемом машиной. Но когда дело доходит до произвольного размера, я не могу найти эффективный способ лучше, чем O(n^2).
2 ответа
Разделите на основание, отодвиньте модуль назад, промойте и повторите, пока коэффициент!= 0.
Так, например, конвертировать 148 в базу 16
148 / 16 = 9 r 4
9 / 16 = 0 r 9
Итак, 148 в гексе - это 0x94. Это не должно занять так много времени.
Предположим, мы хотим преобразовать из базы в базу. Если и обе степени общего основанияb
сказатьb1 = b^i1
иb2 = b^i2
то это можно сделать вO(n)
. В частности, это относится, например, к преобразованию между двоичными, восьмеричными, шестнадцатеричными,2^32
,2^64
и т. д. Это потому, что вы действительно просто перераспределяете биты. Рассмотрим биты цифр числа 3214 в восьмеричном и шестнадцатеричном виде:
3214 = 12*16^2 + 8*16 + 14 = 6*8^3 + 2*8^2 + 1*8 + 6
= {1100, 1000, 1110} base 16
= {110, 010, 001, 110} base 8
Вы можете видеть, что это одни и те же биты, просто по-разному разделенные между цифрами в каждой базе. Нетрудно создать алгоритм, который преобразует одно в другое, используя битовые сдвиги и маски. То же самое относится и к преобразованию, например, из базы 9 в базу 27, за исключением того, что вы разделяете цифры базы 3, а не биты, а затем перемещаете их в разные сегменты.
Сейчас еслиb1
иb2
несоизмеримы, то это не так просто, но это все же можно сделать за субквадратичное время, используя подходы «разделяй и властвуй». Раздел 7.1 учебника [1] описывает алгоритмы 1.25 и 1.26 для преобразования базы. Презумпция этих алгоритмов заключается в том, что вы конвертируете в базу и из нее, которая используется для арифметики больших целых чисел (и что вы можете выполнять арифметику больших целых чисел в этой базе). Вы можете использовать это для реализации произвольного базового преобразования путем преобразованияb1 -> bigint -> b2
хотя.
Здесь я продемонстрирую алгоритм 1.25 на Python, который естественным образом поддерживает арифметику больших целых чисел из коробки:
# Algorithm 1.25:
def parse_int(S: str, B: int = 10) -> int:
"""parse string S as an integer in base B"""
m = len(S)
l = list(map(int, S[::-1]))
b, k = B, m
while k > 1:
last = [l[-1]] if k % 2 == 1 else []
l = [l1 + b*l2 for l1, l2 in zip(l[::2], l[1::2])]
l.extend(last)
b, k = b**2, (k + 1) >> 1
[l0] = l
return l0
Поскольку это сначала преобразует все символы вint
сstr
эта реализация будет работать только для оснований от 2 до 10, но если бы ввод был предоставлен в виде списка целых чисел, а не строки, тогда этот шаг не был бы необходим. Обратите внимание, что здесь в этом коде это не указано явно, ноl1 + b*l2
иb**2
нужно вычислять с помощью большой целочисленной арифметики. Здесь возвращается большое целое число.
Алгоритм 1.26 может преобразовать большое целое число в строку цифр по любому основанию. Простая реализация:
# Algorithm 1.26:
def format_int(A: int, B: int = 10) -> str:
"""Format integer A as a string in base B"""
if A < B:
# Here we use str for the base case of a single digit but probably
# there should be some cutoff for using an O(n^2) algorithm for
# sufficiently small inputs.
return str(A)
else:
# find k so that B**(2*k-2) <= A < B**(2*k)
k = 1
B2k = B2 = B**2
while A > B2k:
k += 1
B2k *= B2
if A == B2k:
k += 1
# assert B**(2*k - 2) <= A < B**(2*k)
Q, R = divmod(A, B**k)
r = format_int(R, B)
q = format_int(Q, B)
pad = '0'*(k - len(r))
return ''.join([q, pad, r])
Оба эти алгоритма имеют сложностьM(n)*log(n)
где количество битов в большом целом числе иM(n)
это стоимость умножения двухn
битовые целые числа. Если умножение и деление больших целых чисел имеет субквадратичную производительность для умножения, то и эти алгоритмы будут такими же.
[1] Ричард П. Брент и Пол Циммерманн, Современная компьютерная арифметика. https://members.loria.fr/PZimmermann/mca/mca-cup-0.5.9.pdf