8

Я искал высоко и низко и, похоже, не нашел много материала, связанного со сложностями времени выполнения, рекурсией и java.Сложности во время выполнения для рекурсивных алгоритмов

В настоящее время я изучаю сложности во время выполнения и нотацию Big-O в классе «Алгоритмы», и у меня возникают проблемы с анализом рекурсивных алгоритмов.

private String toStringRec(DNode d) 
{ 
    if (d == trailer) 
     return ""; 
    else 
     return d.getElement() + toStringRec(d.getNext()); 
} 

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

Единственное, что я могу придумать, это то, что он имеет сложность O (n) во время выполнения, поскольку количество вызовов рекурсивных методов будет зависеть от количества узлов в DList, но я до сих пор не знаю, t чувствовать себя комфортно с этим ответом.

Я не уверен, должен ли я составлять учет d и d.getNext().

Или я просто полностью отключен от дорожки, и сложность времени выполнения постоянна, так как все его выполнение извлекает элементы из DNodes в DList?

+0

См. Http://stackoverflow.com/questions/1126388/slow-string-concatenation-over-large-input – dyoo

ответ

0

Алгоритм имеет сложность времени выполнения O (n), как вы предлагаете. В вашем списке есть n элементов, и алгоритм будет выполнять почти фиксированный объем работы для каждого элемента (работа которого является элементом Element и Next access, а также новым вызовом toStringRec). Извлечение элемента из DNode занимает постоянное время, а постоянное время отбрасывается в нотации с большими выводами.

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

+1

Данное объяснение, к сожалению, приводит к некорректному выводу. Он не учитывает стоимость конкатенации строк. То есть, стоимость каждого элемента ** не является постоянной **. Это важный момент этой проблемы. Пожалуйста, исправьте это. – dyoo

+0

Я согласен, что стоимость каждого элемента не постоянна, но я не согласен с тем, что это O (n). Стоимость 'string1 + string2' равна O (m), где m - длина результирующей строки. Конкретно, объединение двух строк в худшем случае создает новый char [] длины m и однократное копирование каждого символа из исходных строк. Когда на n-й итерации приведенного кода toStringRec может возвращать очень длинную строку, но стоимость конкатенации по-прежнему равна O (m). m в этом примере напрямую не привязан к n, так как 'getElement' может возвращать пустые или очень длинные строки. – sgmorrison

+0

Предположим, что существует некоторая длина 'm', которая является верхней границей размера любого конкретного d.getElement(). Тогда размер возвращаемой строки, возвращаемой с 'toStringRec (node)', привязан длиной цепочки, начинающейся с 'node'. Пусть «T (n)» - стоимость вычисления. Тогда: 'T (n) dyoo

3

На первый взгляд это выглядит как классический случай tail recursion modulo cons, обобщение хвостового вызова. Это эквивалентно циклу с числом итераций.

Однако, это не так просто: сложная вещь здесь является добавлением d.getElement() к растущей строке: это сам по себе линейной операции, и это повторяется N раз. Следовательно, сложность вашей функции - O(N^2).

+0

Хм, я думал, что d.getElement() должен был получить данные, хранящиеся в узле d. Ему нужно сделать свой вопрос немного яснее об этом, я думаю ... –

+2

@XiaoChuanYu Нет, 'd.getElement()' is 'O (1)'. Это последовательная строка, которая является линейной. – dasblinkenlight

+0

Да, спасибо, что не проигнорировали стоимость конкатенации строк. Это точно. Вступает в игру сумма gauss '1 + 2 + ... + n', и именно здесь возникает квадрат. – dyoo

2

Если T (n) - число элементарных операций (в этом случае - когда мы вводим тело функции, любая из внутренних строк выполняется не более одного раза, и все, кроме второго возврата, не являются O (1)) выполняется путем вызова toStringRec в списке п элементов, то

T(0) = 1 - as the only things that happen is the branch instruction and a 
       return 
    T(n) = n + T(n-1) for n > 0 - as the stuff which is being done in the 
       function besides calling toStringRec is some constant time stuff and 
       concatenating strings that takes O(n) time; and we also run 
       toStringRec(d.getNet()) which takes T(n-1) time 

на данный момент мы описали сложность алгоритма. Теперь мы можем вычислить замкнутую форму для T, T (n) = O (n ** 2).

+0

Нет: «материал, выполняемый в функции», не является O (1).Это работа, которая занимает время пропорционально «n», если размер строки в каждом элементе не пуст. Следовательно, замкнутая форма для 'T (n)' заканчивается похожим на T (n) = 1C + 2C + 3C + ... + nC' для некоторой константы C. Это сумма Гаусса. 'T (n)' является квадратичным, а не линейным. – dyoo

+0

Хороший улов, спасибо! –

2

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

T(0) = 0 
T(n) = C + T(n-1) 

Вы можете решить для работы времени, используя замену, чтобы найти ряд :

T(n) = C + T(n-1) = 2C + T(n-2) = 3C + T(n-3) = ... = nC + T(n-n) = nC + 0 = nC 

По определению O это уравнение является O (n). Этот пример не особенно интересен, но если вы посмотрите на что-то вроде времени выполнения mergesort или другого алгоритма разделения и покорения, вы можете получить лучшее представление о повторных отношениях.

+0

Конечно, в этом примере вы тоже можете это понимать: вы печатаете каждый узел в связанном списке, поэтому количество выполняемых вами отпечатков растет точно так же, как и размер списка. Так что это линейное время. – cjm

+1

В этом конкретном примере мы не можем предположить, что работа, выполняемая на каждом шаге, является постоянным временем, учитывая, как работает конкатенация строк в Java. – dyoo

+0

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

0

Для таких рекурсивных алгоритмов обычно можно написать рекурсивное уравнение для вычисления порядка. Обычно отображается количество команд, выполняемых с помощью T (n). В этом примере мы имеем:

Т (п) = Т (п - 1) + O (1)

(Предположив, что функция getElement работает в постоянном времени.) Решение этого уравнения является тривиальным Т (n) = O (n).

Это был общий случай. Однако иногда вы можете анализировать алгоритм без написания такого уравнения. В этом примере вы можете легко утверждать, что каждый элемент доступен не чаще одного раза, и каждый раз, когда выполняется некоторая постоянная работа времени; поэтому для выполнения задания требуется O (n).

Смежные вопросы