Наиболее эффективный алгоритм для вычисления общего числителя суммы дробей - PullRequest
4 голосов
/ 27 июля 2011

Я почти уверен, что это правильный сайт для этого вопроса, но не стесняйтесь перенести его на другой сайт stackexchange, если он там подходит лучше.

Предположим, у вас есть сумма дробей a1/d1 + a2/d2 + … + an/dn. Вы хотите вычислить общий числитель и знаменатель, то есть переписать его как p/q. У нас есть формула

p = a1*d2*…*dn + d1*a2*d3*…*dn + … + d1*d2*…d(n-1)*an
q = d1*d2*…*dn.

Какой самый эффективный способ вычислить эти вещи, в частности, p? Вы можете видеть, что если вы вычисляете это наивно, то есть, используя формулу, которую я дал выше, вы вычисляете много лишних вещей. Например, вы будете вычислять d1*d2 n-1 раз.

Моей первой мыслью было итеративное вычисление d1*d2, d1*d2*d3,… и dn*d(n-1), dn*d(n-1)*d(n-2),… но даже это неэффективно, потому что в итоге вы умножаете вычисления на «среднее» дважды (например, , если n достаточно велико, вы вычислите d3*d4 дважды).

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

И еще одно замечание: меня не волнует отмена, просто самый эффективный способ умножения вещей.

UPDATE:

Я должен был знать, что люди из stackoverflow будут предполагать, что это числа, но я настолько привык к своему случаю использования, что забыл упомянуть об этом.

Мы не можем просто «разделить» an от каждого термина. Вариант использования здесь является символической системой. На самом деле, я пытаюсь исправить функцию с именем .as_numer_denom() в системе компьютерной алгебры SymPy , которая в настоящее время вычисляет это наивным способом. См. соответствующий выпуск SymPy .

У деления вещей есть некоторые проблемы, которых я бы хотел избежать. Во-первых, нет гарантии, что все отменится. Это потому, что математически, (a*b)**n != a**n*b**n в целом (если a и b положительны, то это верно, но, например, если a == b ==-1 и n == 1/2, вы получите (a*b)**n == 1**(1/2) == 1, но (-1)**(1/2)*(-1)**(1/2) == I*I == -1). Поэтому я не думаю, что будет хорошей идеей предполагать, что деление на an отменит его в выражении (это может быть на самом деле необоснованно, мне нужно проверить, что делает код).

Во-вторых, я хотел бы также применить этот алгоритм для вычисления суммы рациональных функций. В этом случае члены будут автоматически умножаться вместе в один многочлен, и «деление» каждого an будет включать применение алгоритма деления многочлена. В этом случае вы можете видеть, что вы действительно хотите вычислить наиболее эффективное умножение.

ОБНОВЛЕНИЕ 2:

Я думаю, что мои опасения по поводу отмены символических терминов могут быть необоснованными. SymPy не отменяет такие вещи, как x**n*x**(m - n) автоматически, но я думаю, что любые показатели, которые будут объединяться с помощью умножения, также будут объединяться с помощью деления, поэтому полномочия следует отменять.

Существует проблема с автоматическим распределением констант по дополнениям, например:

In [13]: 2*(x + y)*z*(S(1)/2)
Out[13]: 
z⋅(2⋅x + 2⋅y)
─────────────
      2      

Но это, во-первых, ошибка , а вторая никогда не могла быть проблемой (я думаю), потому что 1/2 будет разбит на 1 и 2 по алгоритму, который получает числитель и знаменатель каждого срока.

Тем не менее , я все еще хочу знать, как это сделать, не "разделяя" di от каждого слагаемого, чтобы у меня был эффективный алгоритм суммирования рациональных функций.

Ответы [ 4 ]

3 голосов
/ 27 июля 2011

Вычислить два новых массива:

Первый содержит частичные кратные слева: l[0] = 1, l[i] = l[i-1] * d[i]

Второй содержит частичные кратные справа: r[n-1] = 1, r[i] = d[i] * r[i+1]

В обоих случаях 1 - это мультипликативная идентичность любого кольца, в котором вы работаете.

Тогда каждый из ваших терминов вверху, t[i] = l[i-1] * a[i] * r[i+1]

Это предполагает, что умножение ассоциативно, но оно не обязательно должно быть коммутативным.

В качестве первой оптимизации вам на самом деле не нужно создавать r в виде массива: вы можете выполнить первый проход для вычисления всех значений l и накапливать значения r в течение секунды (в обратном направлении). ) пройти, чтобы вычислить слагаемые. Нет необходимости хранить значения r, так как вы используете каждое из них по порядку.

В своем вопросе вы говорите, что это вычисляет d3*d4 дважды, но это не так. Он умножает два разных значения на d4 (одно умножение справа, а другое умножение слева), но это не совсем повторяющаяся операция. В любом случае, общее число умножений составляет около 4*n, против 2*n умножений и n делений для другого подхода, который не работает в некоммутативных умножениях или неполевых кольцах.

2 голосов
/ 27 июля 2011

Вместо суммирования n коэффициентов за один раз, я бы использовал попарное сложение факторов.

  • Если все сводится к частичным суммам, то числа или полиномы остаются меньшими, что ускоряет вычисления.

  • Вы избегаете проблемы вычисления одного и того же продукта несколько раз.

Вы можете попытаться упорядочить дополнения определенным образом, чтобы повысить вероятность отмены (возможно, сначала добавьте коэффициенты с маленькими знаменателями?), Но я не знаю, стоит ли это делать.

Если вы начнете с нуля, это будет проще реализовать, хотя я не уверен, что он подойдет для замены проблемной подпрограммы в SymPy.

Редактировать: Чтобы сделать это более явным, я предлагаю вычислить a1/d1 + a2/d2 + … + an/dn как (…(a1/d1 + a2/d2) + … ) + an/dn.

2 голосов
/ 27 июля 2011

Если вы хотите вычислить p в вышеприведенном выражении, один из способов сделать это - умножить вместе все знаменатели (в O (n), где n - количество дробей), чтобы это значение былоD.Затем выполните итерацию по всем дробям и для каждой дроби с числителем a i и знаменателем d i , вычислите a i * D / d i.Этот последний член равен произведению числителя дроби и всех знаменателей, кроме ее собственного.Каждый из этих терминов может быть вычислен за O (1) время (при условии, что вы используете аппаратное умножение, в противном случае это может занять больше времени), и вы можете суммировать их все за O (n) время.

Этодает алгоритм времени O (n) для вычисления числителя и знаменателя новой дроби.

0 голосов
/ 29 июля 2011

Мне также указали, что вы можете вручную отсеивать общие знаменатели и комбинировать их тривиально без умножения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...