На днях я пытался решить проблему, связанную с древовидной структурой данных. Обычно большинство решений проблем, связанных с деревьями / графами, носят рекурсивный характер.
Рекурсия предполагает использование неявных стеков. Это реализуется в фоновом режиме компилятором, который используется для компиляции вашего кода.
Этот фоновый стек, созданный компилятором, известен как « Стек вызовов ». Википедия определяет стек вызовов как стек структуру данных, в которой хранится информация об активных подпрограммах компьютерной программы. У него есть и другие названия, такие как стек времени выполнения, но это наиболее распространенное.
Каждый вызов функции / подпрограммы использует фрейм внутри стека вызовов, который называется фрейм стека. Когда функция возвращает значение, ее кадр стека извлекается из стека вызовов.
Это заставило меня задуматься, а что, если бы я реализовал свой собственный явный стек для решения проблемы вместо того, чтобы использовать рекурсию для выполнения задачи за меня? Какие компромиссы?
Переполнение стека
Принципиальное различие между двумя стеками состоит в том, что пространство, выделяемое компилятором для стека вызовов программы, является фиксированным. Это означает, что произойдет переполнение стека, если вы не уверены в максимальном значении «нет». ожидаемых рекурсивных вызовов функций и слишком много вызовов, чем пространство, выделенное для стека, может обработать в данный момент времени.
С другой стороны, если вы определяете явный стек, он реализуется в пространстве кучи, выделенном программе компилятором во время выполнения. И угадайте, размер кучи не фиксирован и может динамически увеличиваться во время выполнения, когда это необходимо. Вам действительно не нужно беспокоиться о явном переполнении стека. Это хорошо для масштабируемости и управления неизбежными крайними случаями, когда есть глубокие рекурсивные вызовы.
Пространство и время
Какой из них будет быстрее в данной ситуации?
Итерация в явном стеке может быть быстрее, чем рекурсия в языках, которые не поддерживают оптимизацию, связанную с рекурсией, например оптимизацию хвостового вызова для хвостовой рекурсии.
Что такое T рекурсия?
Хвостовая рекурсия - это частный случай рекурсии, когда рекурсивная функция больше не выполняет вычислений после вызова рекурсивной функции, то есть последний шаг функции - это вызов рекурсивной функции.
Что такое Дополнительная оптимизация (TCO)?
Оптимизация хвостового вызова - это когда вы можете избежать выделения нового кадра стека для функции, потому что вызывающая функция просто вернет значение, которое она получает от вызываемой функции.
Таким образом, компиляторы / языки, поддерживающие оптимизацию хвостового вызова, реализуют вызов рекурсивной функции только с одним кадром стека в стеке вызовов. Если ваш компилятор / язык не поддерживает это, то использование явного стека сэкономит вам МНОГО места и времени.
Python не поддерживает оптимизацию хвостового вызова. Основная причина этого - наличие полной и четкой трассировки стека, которая обеспечивает эффективную отладку.
Практически все компиляторы C / C ++ поддерживают оптимизацию хвостового вызова.
Интуиция
Некоторым людям очень трудно представить себе рекурсивное решение. Просто он когнитивно не щелкает. Иногда явное управление стеком помогает упростить работу при использовании нескольких параметров.
С другой стороны, рекурсивное решение значительно уменьшает размер исходного кода и делает его более удобным в обслуживании.
Вывод
В конце концов, однозначного ответа нет. Для конкретного сценария необходимо учитывать множество факторов, таких как масштабируемость, ремонтопригодность кода, используемый язык / компилятор и т. Д.
Лучшим способом было бы реализовать решение обоими способами, рассчитав 2 решения на входном наборе и проанализировав пиковое использование пространства перед его развертыванием в производственной установке.