Требования к пространству для сортировки слияния
Я пытаюсь понять требования к пространству для Mergesort, O (n).
Я вижу, что временные требования в основном, количество уровней (logn) * merge (n), что делает (n log n).
Теперь мы все еще выделяем n за уровень, в 2 разных массивах, влево и вправо.
Я понимаю, что ключевым моментом здесь является то, что при возврате рекурсивных функций пространство становится освобожденным, но я не вижу его слишком очевидным.
Кроме того, вся информация, которую я нахожу, просто указывает, что требуется пространство, это O (n), но не объяснять это.
Любой намек?
function merge_sort(m)
if length(m) ≤ 1
return m
var list left, right, result
var integer middle = length(m) / 2
for each x in m up to middle
add x to left
for each x in m after middle
add x to right
left = merge_sort(left)
right = merge_sort(right)
result = merge(left, right)
return result
ИЗМЕНИТЬ
Хорошо, спасибо @Uri, это трюк
То, что я не видел в самом начале, это то, что время добавляет только время, в то время как память добавляет и вычитает, поэтому максимальное количество времени находится в конце выполнения, но максимальный объем памяти находится в нижней части рекурсивного стека.
Итак, если мы добавим n + n/2 + n/4 + n/8.... неважно, сколько раз мы добавляем, оно никогда не будет больше 2n, и когда мы достигнем рекурсивное нижнее дно стека и начать движение вверх, мы не сохраняем память, используемую для предыдущей ветки, поэтому при max 2n будет использоваться объем памяти O (n).
Ответы
Ответ 1
Существуют версии слияния-сортировки, которые могут работать на месте.
Однако в большинстве реализаций пространство является линейным по размеру массива. Это означает, что n для первого уровня, n/2 для второго, n/4 для третьего и т.д. К тому времени, когда вы находитесь в нижней части своей рекурсии, эта серия добавляет около 2n, что является линейным.
Ответ 2
Это мое объяснение сложности пространства для вашего кода. В принципе, поскольку алгоритм достигает результатов, как мы это делаем в памяти.
1) Каждый вызов рекурсии, который вы делаете, имеет постоянный размер выделенного кадра стека, а также любые переменные, которые не являются функцией "n".
Позвольте называть этот constanct "c". Так как вы идете на уровни lg (n), результат получается c * lg (n), который является O (lg (n)).
2) Теперь, когда мы вычисляем результат, выделяем пространство для элементов n/(2 ^ k), где k - уровень, на котором вы находитесь.
left = merge_sort(left)
right = merge_sort(right)
Для людей, которые могут задаться вопросом, как мы пришли с n/(2 ^ k), обратите внимание, что сначала мы собираемся выделить память при решении первой половины массива i.e left = merge_sort (слева). Однажды это дерево рекурсии завершено, мы в конечном итоге освобождаем всю память и возвращаемся к исходной точке, прежде чем решать для правой стороны. Следовательно, его n/(2 ^ k).
Это будет O (n).
3) Наконец, процедура слияния может также выделять лишнее пространство (при использовании связанного списка это пространство может не понадобиться), которое равно O (n)
Окончательный ответ = O (lg (n)) + O (n) + O (n), который является O (n).