Реализация умножения Карацубы

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

процедура каратсуба (номер1, номер2)
если (num1 < 10) или (num2 < 10)
    вернуть num1*num2
  /* вычисляет размер чисел */
  m = max(size_base10(num1), size_base10(num2))
  м2 = м /2
  /* разбить последовательность цифр на середину */
  high1, low1 = split_at(num1, m2)
  high2, low2 = split_at(num2, m2)
  /* 3 звонка на номера примерно вдвое меньше */
  z0 = каратсуба (низкий1, низкий2)
  z1 = каратсуба ((низкий1+ высокий1), (низкий2+ высокий2))
  z2 = каратсуба (высокая1, высокая2)
  возврат (z2*10^(2* м2)) + ((z1-z2-z0)*10^(м2)) + (z0)

Вот моя реализация Python:

def karat(x,y):
    if len(str(x)) == 1 or len(str(y)) == 1:
        return x*y
    else:
        m = max(len(str(x)),len(str(y)))
        m2 = m / 2

        a = x / 10**(m2)
        b = x % 10**(m2)
        c = y / 10**(m2)
        d = y % 10**(m2)

        z0 = karat(b,d)
        z1 = karat((a+b),(c+d))
        z2 = karat(a,c)

        return (z2 * 10**(2*m2)) + ((z1 - z2 - z0) * 10**(m2)) + (z0)

Мой вопрос об окончательном слиянии z0, z1, а также z2,
z2 сдвинут на m цифр (где m - длина наибольшего из двух умноженных чисел).
Вместо простого умножения на 10 ^ (м), алгоритм использует * 10 ^ (2 * м2) *, где m2 - это м / 2.

Я попытался заменить 2 * m2 на m и получил неправильные результаты. Я думаю, что это связано с тем, как делятся числа, но я не совсем уверен, что происходит.

8 ответов

В зависимости от вашей версии Python вы должны или должны заменить / с явным оператором разделения этажа // что здесь уместно; это округляет, гарантируя, что ваши показатели остаются целыми числами.

Это важно, например, при разделении ваших операндов на старшие цифры (путем деления по полу на 10^m2) и младшие цифры (принимая остаточное по модулю 10^m2) это не будет работать с дробным m2,

Это также объясняет, почему 2 * (x // 2) не обязательно равен x скорее x-1 если х нечетно. В последней строке алгоритма 2 m2 правильно, потому что то, что вы делаете, дает a а также c их нули обратно.

Если вы используете более старую версию Python, ваш код все еще может работать, потому что / раньше интерпретировалось как деление по полу применительно к целым числам.

def karat(x,y):
    if len(str(x)) == 1 or len(str(y)) == 1:
        return x*y
    else:
        m = max(len(str(x)),len(str(y)))
        m2 = m // 2

        a = x // 10**(m2)
        b = x % 10**(m2)
        c = y // 10**(m2)
        d = y % 10**(m2)

        z0 = karat(b,d)
        z1 = karat((a+b),(c+d))
        z2 = karat(a,c)

        return (z2 * 10**(2*m2)) + ((z1 - z2 - z0) * 10**(m2)) + (z0)

Я реализовал ту же идею, но я ограничил 2-значное умножение в качестве базового случая, потому что я могу уменьшить умножение с плавающей точкой в ​​функции

import math

def multiply(x,y):
    sx= str(x)
    sy= str(y)
    nx= len(sx)
    ny= len(sy)
    if ny<=2 or nx<=2:
        r = int(x)*int(y)
        return r
    n = nx
    if nx>ny:
        sy = sy.rjust(nx,"0")
        n=nx
    elif ny>nx:
        sx = sx.rjust(ny,"0")
        n=ny
    m = n%2
    offset = 0
    if m != 0:
        n+=1
        offset = 1
    floor = int(math.floor(n/2)) - offset
    a = sx[0:floor]
    b = sx[floor:n]
    c = sy[0:floor]
    d = sy[floor:n]
    print(a,b,c,d)

    ac = multiply(a,c)
    bd = multiply(b,d)

    ad_bc = multiply((int(a)+int(b)),(int(c)+int(d)))-ac-bd
    r = ((10**n)*ac)+((10**(n/2))*ad_bc)+bd

    return r

print(multiply(4,5))
print(multiply(4,58779))
print(int(multiply(4872139874092183,5977098709879)))
print(int(4872139874092183*5977098709879))
print(int(multiply(4872349085723098457,597340985723098475)))
print(int(4872349085723098457*597340985723098475))
print(int(multiply(4908347590823749,97098709870985)))
print(int(4908347590823749*97098709870985))

Я попытался заменить 2*m2 на m и получил неправильные результаты. Я думаю, что это связано с тем, как делятся числа, но я не совсем уверен, что происходит.

Это связано с тем, как вы разделяете свои номера для рекурсивных вызовов. Если вы решили использовать нечетное n затем n//2 будет округлено до ближайшего целого числа, что означает, что ваш второй номер будет иметь длину floor(n/2) и вам придется дополнить первый с floor(n/2) нули. Так как мы используем то же самое n для обоих чисел это относится к обоим. Это означает, что если вы придерживаетесь оригинальной нечетной n для последнего шага, вы будете дополнять первый член оригиналом n нули вместо числа нулей, которые могут возникнуть в результате комбинации первого дополнения и второго дополнения (floor(n/2)*2)

Вы использовали m2 как поплавок. Это должно быть целое число.

def karat(x,y):
    if len(str(x)) == 1 or len(str(y)) == 1:
        return x*y
    else:
        m = max(len(str(x)),len(str(y)))
        m2 = m // 2

        a = x // 10**(m2)
        b = x % 10**(m2)
        c = y // 10**(m2)
        d = y % 10**(m2)

        z0 = karat(b,d)
        z1 = karat((a+b),(c+d))
        z2 = karat(a,c)

        return (z2 * 10**(2*m2)) + ((z1 - z2 - z0) * 10**(m2)) + (z0)

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

      def number_of_digits(number: int) -> int:
    return int(math.log10(abs(number))) + 1

def multiply(x: int, y: int) -> int:
    # We CAN multiply small numbers
    if abs(x) < 10 or abs(y) < 10:
        return x * y

    # Calculate the size of the numbers
    digits = max(number_of_digits(x), number_of_digits(y))
    midpoint = 10 ** (digits // 2)

    # Split digit sequences in the middle
    high_x = x // midpoint
    low_x = x % midpoint
    high_y = y // midpoint
    low_y = y % midpoint

    # 3 recursive calls to numbers approximately half the size
    z0 = multiply(low_x, low_y)
    z1 = multiply(low_x + high_x, low_y + high_y)
    z2 = multiply(high_x, high_y)

    return (z2 * midpoint**2) + ((z1 - z2 - z0) * midpoint) + (z0

print(multiply(2**100, 3**100))

Я бы сказал, что:

  1. имена переменных понятнее
  2. вnumber_of_digitsвспомогательная функция должна использовать log10 для поиска количества цифр вместо str+len
  3. Математика становится немного яснее, если извлечь термин из 10**цифр//2.

Я думаю, что лучше, если вы использовали math.log10 функция для вычисления количества цифр вместо преобразования в строку, примерно так:

def number_of_digits(number):
  """
  Used log10 to find no. of digits
  """
  if number > 0:
    return int(math.log10(number)) + 1
  elif number == 0:
    return 1
  else:
    return int(math.log10(-number)) + 1 # Don't count the '-'

Ваш код и логика верны, в базовом случае есть проблема. Так как в соответствии с алгоритмом a,b,c,d являются двузначными числами, вы должны изменить базовый вариант и сохранить длину x и y равной 2 в базовом случае.

Базовый случай if len(str(x)) == 1 or len(str(y)) == 1: return x*y это неверно. Если вы запустите любой из приведенного в ответах кода Python против больших целых чисел, karat() Функция не даст правильный ответ.

Чтобы сделать код правильным, вам нужно изменить базовый вариант на if len(str(x) < 3 or len(str(y)) < 3: return x*y,

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

def karat(x,y):
    if len(str(x)) < 3 or len(str(y)) < 3:
        return x*y

    n = max(len(str(x)),len(str(y))) // 2

    a = x // 10**(n)
    b = x % 10**(n)
    c = y // 10**(n)
    d = y % 10**(n)

    z0 = karat(b,d)
    z1 = karat((a+b), (c+d))
    z2 = karat(a,c)

    return ((10**(2*n))*z2)+((10**n)*(z1-z2-z0))+z0
Другие вопросы по тегам