Если вы создаете минимальное количество операций с обычного компьютера с нуля, «Итерация» стоит на первом месте в качестве строительного блока и требует меньше ресурсов, чем «рекурсия», следовательно, это быстрее.
Мы создадим иерархию понятий, начиная с нуля и определяя в первую очередь базовые, основные понятия, затем создадим понятия второго уровня с ними и так далее.
Первая концепция: ячейки памяти, память, состояние . Чтобы что-то сделать, вам нужны места для хранения конечных и промежуточных значений результатов. Давайте предположим, что у нас есть бесконечный массив «целочисленных» ячеек, называемый Memory , M [0..Infinite].
Инструкции: сделать что-то - трансформировать ячейку, изменить ее значение. изменить состояние . Каждая интересная инструкция выполняет преобразование. Основные инструкции:
а) Установить и переместить ячейки памяти
- сохранить значение в памяти, например: сохранить 5 м [4]
- скопируйте значение в другую позицию: например: store m [4] m [8]
б) логика и арифметика
- и, или, XOR, не
- добавить, sub, mul, div. например, добавить m [7] m [8]
Исполнительный агент : ядро современного процессора. «Агент» - это то, что может выполнять инструкции. Агент также может быть людьми по алгоритму на бумаге.
Порядок шагов: последовательность инструкций : то есть: сделать это сначала, сделать это после и т. Д. Обязательная последовательность инструкций. Даже однострочные выражения являются «обязательной последовательностью инструкций». Если у вас есть выражение с определенным «порядком оценки», то у вас есть шаги . Это означает, что даже одно составное выражение имеет неявные «шаги», а также имеет неявную локальную переменную (назовем ее «результат»). например:
4 + 3 * 2 - 5
(- (+ (* 3 2) 4 ) 5)
(sub (add (mul 3 2) 4 ) 5)
Выражение выше подразумевает 3 шага с неявной переменной «result».
// pseudocode
1. result = (mul 3 2)
2. result = (add 4 result)
3. result = (sub result 5)
Поэтому даже инфиксные выражения, поскольку у вас есть определенный порядок вычисления, являются обязательной последовательностью инструкций . Выражение подразумевает последовательность операций, которые должны быть выполнены в определенном порядке, и, поскольку существуют шаги , существует также неявная промежуточная переменная «result».
Указатель инструкций : если у вас есть последовательность шагов, у вас также есть неявный «указатель инструкций». Указатель инструкции отмечает следующую инструкцию и продвигается после чтения инструкции, но до ее выполнения.
В этой псевдо-вычислительной машине указатель инструкций является частью памяти . (Примечание: обычно указатель инструкций будет «специальным регистром» в ядре ЦП, но здесь мы упростим концепции и предположим, что все данные (включая регистры) являются частью «памяти»)
Переход - если у вас есть заказанное количество шагов и указатель инструкций , вы можете применить инструкцию « store », чтобы изменить значение самого указателя инструкций. Мы будем называть это конкретное использование инструкции store новым именем: Jump . Мы используем новое имя, потому что его проще воспринимать как новую концепцию. Изменяя указатель инструкции, мы инструктируем агента «перейти к шагу x».
Бесконечная итерация : отскочив назад, теперь вы можете заставить агента «повторить» определенное количество шагов. На данный момент у нас есть бесконечная итерация.
1. mov 1000 m[30]
2. sub m[30] 1
3. jmp-to 2 // infinite loop
Условно - условное выполнение инструкций. С помощью условного предложения вы можете условно выполнить одну из нескольких инструкций в зависимости от текущего состояния (которое может быть установлено с помощью предыдущей инструкции).
Правильная итерация : теперь с помощью условного предложения мы можем избежать бесконечного цикла инструкции возврата . Теперь у нас есть условный цикл, а затем правильная итерация
1. mov 1000 m[30]
2. sub m[30] 1
3. (if not-zero) jump 2 // jump only if the previous
// sub instruction did not result in 0
// this loop will be repeated 1000 times
// here we have proper ***iteration***, a conditional loop.
Именование : присвоение имен определенной ячейке памяти, содержащей данные или шаг . Это просто «удобство». Мы не добавляем никаких новых инструкций, имея возможность определять «имена» для областей памяти. «Наименование» - это не инструкция для агента, а просто удобство для нас. Присвоение имен делает код (на данный момент) более простым для чтения и изменения.
#define counter m[30] // name a memory location
mov 1000 counter
loop: // name a instruction pointer location
sub counter 1
(if not-zero) jmp-to loop
Одноуровневая подпрограмма . Предположим, вам нужно выполнить серию шагов. Вы можете сохранить шаги в именованной позиции в памяти, а затем перейти к этой позиции, когда вам нужно выполнить их (вызвать). В конце последовательности вам нужно вернуться к точке вызова, чтобы продолжить выполнение. С помощью этого механизма вы создаете новые инструкции (подпрограммы), составляя основные инструкции.
Реализация: (новые концепции не требуются)
- Сохранить текущий указатель инструкций в предварительно определенной позиции памяти
- перейти к подпрограмме
- в конце подпрограммы вы извлекаете указатель инструкций из предопределенной области памяти, фактически возвращаясь к следующей инструкции исходного вызова
Проблема с одноуровневой реализацией: Вы не можете вызвать другую подпрограмму из подпрограммы. Если вы это сделаете, вы перезапишете возвращающий адрес (глобальную переменную), поэтому вы не сможете вкладывать вызовы.
Чтобы иметь лучшую реализацию для подпрограмм: вам нужен STACK
Стек : Вы определяете пространство памяти для работы в качестве «стека», вы можете «выталкивать» значения в стек, а также «выталкивать» последнее «вытолкнутое» значение. Для реализации стека вам понадобится указатель стека (подобный указателю инструкций), который указывает на фактическую «верхушку» стека. Когда вы «нажимаете» значение, указатель стека уменьшается, и вы сохраняете значение. Когда вы «выталкиваете», вы получаете значение в фактическом указателе стека, а затем увеличивается указатель стека.
Подпрограммы Теперь, когда у нас есть стек, мы можем реализовать надлежащие подпрограммы, разрешающие вложенные вызовы . Реализация аналогична, но вместо того, чтобы хранить указатель инструкций в предопределенной позиции памяти, мы «помещаем» значение IP в стек . В конце подпрограммы мы просто «выталкиваем» значение из стека, фактически возвращаясь к инструкции после исходного вызова . Эта реализация, имеющая «стек», позволяет вызывать подпрограмму из другой подпрограммы. С помощью этой реализации мы можем создать несколько уровней абстракции при определении новых инструкций в качестве подпрограмм, используя базовые инструкции или другие подпрограммы в качестве строительных блоков.
Рекурсия : что происходит, когда подпрограмма вызывает себя? Это называется "рекурсия".
Проблема: Перезаписывая локальные промежуточные результаты, подпрограмма может быть сохранена в памяти. Поскольку вы вызываете / повторно используете одни и те же шаги, если промежуточный результат хранится в предопределенных ячейках памяти (глобальных переменных), они будут перезаписаны при вложенных вызовах.
Решение: чтобы разрешить рекурсию, подпрограммы должны хранить локальные промежуточные результаты в стеке , поэтому при каждом рекурсивном вызове (прямом или косвенном) промежуточные результаты хранятся в разных местах памяти.
...
Наконец, обратите внимание, что у вас есть много возможностей использовать рекурсию. Повсюду у вас есть рекурсивные структуры данных , сейчас вы смотрите на них: части DOM, поддерживающие то, что вы читаете, - это RDS, выражение JSON - это RDS, иерархическая файловая система на вашем компьютере - это RDS, то есть: у вас есть корневой каталог, содержащий файлы и каталоги, каждый каталог, содержащий файлы и каталоги, каждый из этих каталогов, содержащий файлы и каталоги ...