Струнный анализ - PullRequest
33 голосов
/ 12 мая 2011

Учитывая последовательность операций:

а * б * а * Ь * а * а * б * а * Ь

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

изготовление

a * b * a * b * a * a * b * a * b => c * a * c, где c = a * b * a * b

и затем увидев это

a * b * a * b => d * d, где d = a * b

В целом сокращение 8 начальных операций до 4, описанных здесь?

(c = (d = a * b) * d) * a * c

Цель курса - минимизировать количество операций

Я рассматриваю своего рода суффиксное дерево.

Меня особенно интересует эвристика или решения с линейным временем. Операции '*' на самом деле являются умножением матриц.

Ответы [ 5 ]

19 голосов
/ 12 мая 2011

Вся эта проблема известна как «Устранение общего выражения выражений» или CSE .Это немного меньшая версия проблемы под названием « Graph Reduction », с которой сталкивается разработчик компиляторов для функциональных языков программирования.Поиск в Google «Алгоритм исключения общего выражения выражений» дает множество решений, хотя я не вижу его, особенно для ограничений, задаваемых умножением матриц.

Страницы, на которые даны ссылки, дают множество ссылок.

MyСтарый ответ ниже.Однако, исследовав немного больше, мы просто создаем дерево суффиксов .Это можно сделать за O (N) время (множество ссылок на странице википедии).Сделав это, подвыражения (c, d и т. Д. В вашем вопросе) - это просто узлы в дереве суффиксов - просто вытяните их.


Однако я думаю, что MarcoS что-то делает спредложение Самая длинная повторяющаяся подстрока , так как приоритет сокращения графов может не допускать оптимизаций, которые могут быть разрешены здесь.

эскиз алгоритма:

optimise(s):
    let sub = longestRepeatingSubstring(s).
    optimisedSub = optimise(sub)
    return s with sub replaced by optimisedSub

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

14 голосов
/ 12 мая 2011

edit: порядки роста в этом ответе необходимы в дополнение к принятому ответу для запуска умножения CSE или матрицы-матрицы

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

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

Мы также можем определить оптимальное решение, если проигнорируем такие эффекты, как кеширование:

input: [some product of matrices to compute]

given that multiplying two NxN matrices is O(N^2.376)
given we can visualize the product as follows:
    [[AxB][BxC][CxD][DxE]...]
we must for example perform O(max(A,B,C)^2.376) or so operations in order to combine
    [AxB][BxC] -> [AxC]

The max(...) is an estimate based on how fast it is to multiply two square matrices;
a better estimate of cost(A,B,C) for multiplying an AxB * BxC matrix can be gotten 
from actually looking at the algorithm, or running benchmarks if you don't know the
algorithm used.

However note that multiplying the same matrix with itself, i.e. calculating
a power, can be much more efficient, and we also need to take that into account.
At worst, it takes log_2(power) multiplies each of O(N^2.376), but this could be
made more efficient by diagonalizing the matrix first.

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

aaaaabaab
compressing 'aa' results in ccabcb and compressing 'aab' is now impossible

Однако у меня есть догадка, что, если мы попробуем все порядки сжатия подстрок, мы, вероятно, не будем сталкиваться с этой проблемой слишком часто.

Таким образом, записав, что мы хотим (затраты) и рассмотрев возможные проблемы, у нас уже есть алгоритм грубой силы, который может это сделать, и он будет работать для очень небольшого числа матриц:

# pseudocode

def compress(problem, substring)
    x = new Problem(problem)
    x.string.replaceall(substring, newsymbol)
    x.subcomputations += Subcomputation(newsymbol=substring)

def bestCompression(problem)
    candidateCompressions = [compress(problem,substring) for each substring in problem.string]
    # etc., recursively return problem with minimum cost
    # dynamic programming may help make this more efficient, but one must watch
    #   out for the note above, how it may be hard to be greedy

Примечание: согласно другому ответу Асгейра, это известно как задача оптимизации матричного умножения цепей. Ник Фортескью отмечает, что это также более широко известно как http://en.wikipedia.org/wiki/Common_subexpression_elimination - таким образом, можно найти любой универсальный алгоритм / библиотеку CSE или Matrix-Chain-Multiplication из литературы, и подключить стоимость заказов Величина, о которой я упоминал ранее (вам понадобятся те средства, которые вы используете). Обратите внимание, что стоимость вышеуказанных вычислений (умножение, возведение в степень и т. Д.) Предполагает, что они выполняются эффективно с использованием современных алгоритмов; если это не так, замените экспоненты соответствующими значениями, которые соответствуют способу выполнения операций.

9 голосов
/ 27 мая 2011

Если вы хотите использовать наименьшее количество арифметических операций, вам следует взглянуть на умножение матрицы матрицы , которое можно уменьшить до O (n log n)

8 голосов
/ 12 мая 2011

Сверху головы проблема кажется в NP для меня. В зависимости от замен, которые вы делаете, другие подстановки будут возможны или невозможны, например, для строки d*e*a*b*c*d*e*a*b*c*d*e*a есть несколько возможностей.

Если вы возьмете самую длинную общую строку, это будет: f = d*e*a*b*c и вы можете заменить f*f*e*a, оставив вам три умножения в конце и четыре промежуточных (всего семь).

Если вместо этого вы подставите следующий путь: f = d*e*a вы получаете f*b*c*f*b*c*f, который вы можете заменить, используя g = f*b*c для g*g*f всего шесть умножений.

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

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

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