Могу ли я уменьшить вычислительную сложность этого?

112
12

Ну, у меня есть этот бит кода, который сильно замедляет работу программы, потому что это линейная сложность, но многократно вызывающая квадратичную сложность программы. Если возможно, я хотел бы уменьшить его вычислительную сложность, но в остальном я просто буду оптимизировать его там, где могу. До сих пор я сократил до:


def table(n):
a = 1
while 2*a <= n:
if (-a*a)%n == 1: return a

a += 1


Кто-нибудь видит то, что я пропустил? Спасибо!


EDIT: Я забыл упомянуть: n всегда простое число.


EDIT 2: Вот моя новая улучшенная программа (спасибо за все вклады!):


def table(n):
if n == 2: return 1
if n%4 != 1: return

a1 = n-1
for a in range(1, n//2+1):
if (a*a)%n == a1: return a


РЕДАКТИРОВАТЬ 3: И проверять его в реальном контексте намного быстрее! Ну, этот вопрос кажется решенным, но есть много полезных ответов. Я также должен сказать, что, как и вышеописанные оптимизации, я воспользовался функцией, использующей словари Python...

спросил(а) 2020-03-28T18:19:39+03:00 1 месяц, 4 недели назад
1
Решение
93

Посмотрите http://modular.fas.harvard.edu/ent/ent_py.
Функция sqrtmod выполняет задание, если вы устанавливаете a = -1 и p = n.


Небольшая точка, которую вы пропустили, заключается в том, что время работы вашего улучшенного алгоритма все еще находится в порядке квадратного корня n. До тех пор, пока у вас есть только малые простые числа n (пусть меньше 2 ^ 64), и вы, вероятно, предпочтете, чтобы ваша реализация была более сложной.


Если число n больше, вам, возможно, придется переключиться на алгоритм, используя немного теории чисел. Насколько я знаю, ваша проблема может быть решена только с вероятностным алгоритмом во времени log (n) ^ 3. Если я правильно помню, если предположить, что гипотеза Римана имеет место (что большинство людей делает), можно показать, что время выполнения следующего алгоритма (в ruby ​​- извините, я не знаю python) - log (log (n)) * войти (п) ^ 3:


class Integer
# calculate b to the power of e modulo self
def power(b, e)
raise 'power only defined for integer base' unless b.is_a? Integer
raise 'power only defined for integer exponent' unless e.is_a? Integer
raise 'power is implemented only for positive exponent' if e < 0
return 1 if e.zero?
x = power(b, e>>1)
x *= x
(e & 1).zero? ? x % self : (x*b) % self
end
# Fermat test (probabilistic prime number test)
def prime?(b = 2)
raise "base must be at least 2 in prime?" if b < 2
raise "base must be an integer in prime?" unless b.is_a? Integer
power(b, self >> 1) == 1
end
# find square root of -1 modulo prime
def sqrt_of_minus_one
return 1 if self == 2
return false if (self & 3) != 1
raise 'sqrt_of_minus_one works only for primes' unless prime?
# now just try all numbers (each succeeds with probability 1/2)
2.upto(self) do |b|
e = self >> 1
e >>= 1 while (e & 1).zero?
x = power(b, e)
next if [1, self-1].include? x
loop do
y = (x*x) % self
return x if y == self-1
raise 'sqrt_of_minus_one works only for primes' if y == 1
x = y
end
end
end
end

# find a prime
p = loop do
x = rand(1<<512)
next if (x & 3) != 1
break x if x.prime?
end

puts "%x" % p
puts "%x" % p.sqrt_of_minus_one


Медленная часть теперь набирает простое число (которое принимает приблизительно log (n) ^ 4 целую операцию); поиск квадратного корня из -1 принимает для 512-битных простых чисел еще меньше секунды.

ответил(а) 2020-03-28T18:30:04.109725+03:00 1 месяц, 4 недели назад
92

Игнорируя алгоритм на мгновение (да, я знаю, плохая идея), время работы этого может быть значительно уменьшено путем переключения с while на for.


for a in range(1, n / 2 + 1)

(Надеюсь, что у него нет ошибки "по очереди". Я склонен это сделать.)


Еще одна вещь, которую я попробую, - посмотреть, можно ли увеличить ширину шага.

ответил(а) 2020-03-28T18:19:39+03:00 1 месяц, 4 недели назад
82

Рассмотрим предварительную вычисление результатов и сохранение их в файле. В настоящее время многие платформы имеют огромную емкость диска. Тогда получение результата будет выполнять операцию O (1).

ответил(а) 2020-04-07T18:33:23.540806+03:00 1 месяц, 2 недели назад
64

Похоже, вы пытаетесь найти квадратный корень из -1 по модулю n. К сожалению, это непростая задача, в зависимости от того, какие значения n вводятся в вашу функцию. В зависимости от n может быть даже не решение. Для получения дополнительной информации об этой проблеме см. Wikipedia.

ответил(а) 2020-03-28T18:19:39+03:00 1 месяц, 4 недели назад
66

Редактировать 2: Удивительно, что снижение производительности при сокращении сокращает время, по крайней мере, на моей установке Python2.5. (Я удивлен, потому что думал, что накладные расходы интерпретатора занимают большую часть времени, и это не уменьшает количество операций во внутреннем цикле.) Сокращает время с 0.572 до 0.146s для таблицы (1234577).


 def table(n):
n1 = n - 1
square = 0
for delta in xrange(1, n, 2):
square += delta
if n <= square: square -= n
if square == n1: return delta // 2 + 1

strager отправил ту же идею, но я думаю, что менее жестко закодирован. Опять же, jug answer лучше всего.


Оригинальный ответ: Еще одна тривиальная настройка кодирования поверх Konrad Rudolph's:


def table(n):
n1 = n - 1
for a in xrange(1, n // 2 + 1):
if (a*a) % n == n1: return a

Ускоряет это на моем ноутбуке. (Около 25% для таблицы (1234577).)


Изменить: Я не заметил тег python3.0; но основным изменением была та часть, которая была выведена из цикла, а не использование xrange. (Академический, так как там лучший алгоритм.)

ответил(а) 2020-03-28T18:19:39+03:00 1 месяц, 4 недели назад
66

(На основе ответа Адама).
Посмотрите на страницу Википедии квадратичная взаимность:


x ^ 2 ≡ -1 (mod p) разрешимо тогда и только тогда, когда p ≡ 1 (mod 4).



Тогда вы можете избежать поиска корня именно для тех нечетных простых n, которые не совпадают с 1 по модулю 4:


def table(n):
if n == 2: return 1
if n%4 != 1: return None # or raise exception
...

ответил(а) 2020-03-28T18:19:39+03:00 1 месяц, 4 недели назад
65

На основе второго редактирования OP:

def table(n):
if n == 2: return 1
if n%4 != 1: return

mod = 0
a1 = n - 1
for a in xrange(1, a1, 2):
mod += a

while mod >= n: mod -= n
if mod == a1: return a//2 + 1

ответил(а) 2020-03-28T18:19:39+03:00 1 месяц, 4 недели назад
53

Можно ли кэшировать результаты?


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

ответил(а) 2020-03-28T18:19:39+03:00 1 месяц, 4 недели назад
55

Я прошел и исправил версию Гарварда, чтобы заставить ее работать с python 3.
 http://modular.fas.harvard.edu/ent/ent_py


Я сделал некоторые небольшие изменения, чтобы сделать результаты точно такими же, как функция OP. Есть два возможных ответа, и я заставил его вернуть меньший ответ.


import timeit

def table(n):

if n == 2: return 1
if n%4 != 1: return

a1=n-1

def inversemod(a, p):
x, y = xgcd(a, p)
return x%p

def xgcd(a, b):
x_sign = 1
if a < 0: a = -a; x_sign = -1
x = 1; y = 0; r = 0; s = 1
while b != 0:
(c, q) = (a%b, a//b)
(a, b, r, s, x, y) = (b, c, x-q*r, y-q*s, r, s)
return (x*x_sign, y)

def mul(x, y):
return ((x[0]*y[0]+a1*y[1]*x[1])%n,(x[0]*y[1]+x[1]*y[0])%n)

def pow(x, nn):
ans = (1,0)
xpow = x
while nn != 0:
if nn%2 != 0:
ans = mul(ans, xpow)
xpow = mul(xpow, xpow)
nn >>= 1
return ans

for z in range(2,n) :
u, v = pow((1,z), a1//2)
if v != 0:
vinv = inversemod(v, n)
if (vinv*vinv)%n == a1:
vinv %= n
if vinv <= n//2:
return vinv
else:
return n-vinv

tt=0
pri = [ 5,13,17,29,37,41,53,61,73,89,97,1234577,5915587277,3267000013,3628273133,2860486313,5463458053,3367900313 ]
for x in pri:
t=timeit.Timer('q=table('+str(x)+')','from __main__ import table')
tt +=t.timeit(number=100)
print("table(",x,")=",table(x))

print('total time=',tt/100)


Эта версия занимает около 3 мс для выполнения описанных выше тестовых примеров.


Для сравнения с использованием простого числа 1234577

OP Edit2 745ms

Принятый ответ 522ms

Вышеуказанная функция 0.2ms

ответил(а) 2020-03-28T18:19:39+03:00 1 месяц, 4 недели назад
54

Одна вещь, которую вы делаете, повторяет вычисление -a * снова и снова.


Создайте таблицу значений один раз, а затем просмотрите основной цикл.


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

ответил(а) 2020-03-28T18:19:39+03:00 1 месяц, 4 недели назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема