Si crea las operaciones mínimas de una computadora genérica desde cero, la "iteración" viene primero como un bloque de construcción y requiere menos recursos que la "recursividad", ergo es más rápido.
Estableceremos una jerarquía de conceptos, comenzando desde cero y definiendo en primer lugar los conceptos básicos y básicos, luego construiremos conceptos de segundo nivel con ellos, y así sucesivamente.
Primer concepto: celdas de memoria, almacenamiento, estado . Para hacer algo, necesita lugares para almacenar valores de resultados finales e intermedios. Supongamos que tenemos una matriz infinita de celdas "enteras", llamadas Memoria , M [0..Infinito].
Instrucciones: haz algo: transforma una celda, cambia su valor. Alterar el estado . Cada instrucción interesante realiza una transformación. Las instrucciones básicas son:
una) Establecer y mover celdas de memoria
- almacenar un valor en la memoria, por ejemplo: almacenar 5 m [4]
- copiar un valor a otra posición: por ejemplo: almacenar m [4] m [8]
si) Lógica y aritmética.
- y, o, xor, no
- sumar, sub, mul, div. por ejemplo, agregue m [7] m [8]
Un agente ejecutor : un núcleo en una CPU moderna. Un "agente" es algo que puede ejecutar instrucciones. Un agente también puede ser una persona que sigue el algoritmo en papel.
Orden de pasos: una secuencia de instrucciones : es decir: hacer esto primero, hacer esto después, etc. Una secuencia imperativa de instrucciones. Incluso las expresiones de una línea son "una secuencia imperativa de instrucciones". Si tiene una expresión con un "orden de evaluación" específico, entonces tiene pasos . Significa que incluso una sola expresión compuesta tiene "pasos" implícitos y también tiene una variable local implícita (llamémosla "resultado"). p.ej:
4 + 3 * 2 - 5
(- (+ (* 3 2) 4 ) 5)
(sub (add (mul 3 2) 4 ) 5)
La expresión anterior implica 3 pasos con una variable "resultado" implícita.
// pseudocode
1. result = (mul 3 2)
2. result = (add 4 result)
3. result = (sub result 5)
Entonces, incluso las expresiones infijadas, dado que tiene un orden específico de evaluación, son una secuencia imperativa de instrucciones . La expresión implica una secuencia de operaciones a realizar en un orden específico, y debido a que hay pasos , también hay una variable intermedia "resultado" implícita.
Puntero de instrucción : si tiene una secuencia de pasos, también tiene un "puntero de instrucción" implícito. El puntero de instrucción marca la siguiente instrucción y avanza después de leer la instrucción pero antes de que se ejecute.
En esta máquina de seudocomputación, el puntero de instrucción forma parte de la memoria . (Nota: Normalmente, el puntero de instrucción será un "registro especial" en un núcleo de CPU, pero aquí simplificaremos los conceptos y asumiremos que todos los datos (registros incluidos) son parte de "Memoria")
Saltar : una vez que tenga un número ordenado de pasos y un puntero de instrucción , puede aplicar la instrucción " almacenar " para modificar el valor del puntero de instrucción. Llamaremos a este uso específico de las instrucciones de la tienda con un nuevo nombre: Jump . Usamos un nuevo nombre porque es más fácil pensarlo como un nuevo concepto. Al alterar el puntero de instrucciones, le estamos indicando al agente que "vaya al paso x".
Iteración infinita : al saltar hacia atrás, ahora puede hacer que el agente "repita" una cierta cantidad de pasos. En este punto tenemos iteración infinita.
1. mov 1000 m[30]
2. sub m[30] 1
3. jmp-to 2 // infinite loop
Condicional : ejecución condicional de instrucciones. Con la cláusula "condicional", puede ejecutar condicionalmente una de varias instrucciones en función del estado actual (que se puede establecer con una instrucción previa).
Iteración adecuada : ahora con la cláusula condicional , podemos escapar del bucle infinito de la instrucción de salto hacia atrás . Ahora tenemos un bucle condicional y luego una iteración adecuada
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.
Naming : dar nombres a una ubicación de memoria específica que contiene datos o que mantiene un paso . Esto es solo una "conveniencia" de tener. No agregamos ninguna instrucción nueva al tener la capacidad de definir "nombres" para ubicaciones de memoria. "Nombrar" no es una instrucción para el agente, es solo una conveniencia para nosotros. La denominación hace que el código (en este punto) sea más fácil de leer y más fácil de cambiar.
#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
Subrutina de un nivel : suponga que hay una serie de pasos que debe ejecutar con frecuencia. Puede almacenar los pasos en una posición con nombre en la memoria y luego saltar a esa posición cuando necesite ejecutarlos (llamada). Al final de la secuencia, deberá volver al punto de llamada para continuar la ejecución. Con este mecanismo, está creando nuevas instrucciones (subrutinas) al componer las instrucciones principales.
Implementación: (no se requieren nuevos conceptos)
- Almacene el puntero de instrucción actual en una posición de memoria predefinida
- saltar a la subrutina
- al final de la subrutina, recupera el puntero de instrucción de la ubicación de memoria predefinida, saltando efectivamente a la siguiente instrucción de la llamada original
Problema con el nivel uno implementación de : no puede llamar a otra subrutina desde una subrutina. Si lo hace, sobrescribirá la dirección de retorno (variable global), por lo que no puede anidar llamadas.
Para tener una mejor implementación de subrutinas: necesita un STACK
Pila : define un espacio de memoria para que funcione como una "pila", puede "insertar" valores en la pila y también "extraer" el último valor "insertado". Para implementar una pila, necesitará un puntero de pila (similar al puntero de instrucciones) que apunta a la "cabeza" real de la pila. Cuando "empuja" un valor, el puntero de la pila disminuye y almacena el valor. Cuando hace “pop”, obtiene el valor en el Stack Pointer real y luego el Stack Pointer se incrementa.
Subrutinas Ahora que tenemos una pila , podemos implementar subrutinas adecuadas que permitan llamadas anidadas . La implementación es similar, pero en lugar de almacenar el puntero de instrucción en una posición de memoria predefinida, "empujamos" el valor de la IP en la pila . Al final de la subrutina, simplemente "sacamos" el valor de la pila, volviendo a la instrucción después de la llamada original . Esta implementación, tener una "pila" permite llamar a una subrutina desde otra subrutina. Con esta implementación, podemos crear varios niveles de abstracción al definir nuevas instrucciones como subrutinas, utilizando instrucciones centrales u otras subrutinas como bloques de construcción.
Recursión : ¿Qué sucede cuando una subrutina se llama a sí misma? Esto se llama "recursión".
Problema: sobrescribir los resultados intermedios locales que una subrutina puede estar almacenando en la memoria. Como está llamando / reutilizando los mismos pasos, si el resultado intermedio se almacena en ubicaciones de memoria predefinidas (variables globales), se sobrescribirán en las llamadas anidadas.
Solución: para permitir la recursividad, las subrutinas deben almacenar resultados intermedios locales en la pila , por lo tanto, en cada llamada recursiva (directa o indirecta) los resultados intermedios se almacenan en diferentes ubicaciones de memoria.
...
Finalmente, tenga en cuenta que tiene muchas oportunidades para usar la recursividad. Tienes estructuras de datos recursivas en todas partes, estás viendo una ahora: partes del DOM que respaldan lo que estás leyendo son un RDS, una expresión JSON es un RDS, el sistema de archivos jerárquico en tu computadora es un RDS, es decir: tienes un directorio raíz, que contiene archivos y directorios, cada directorio que contiene archivos y directorios, cada uno de esos directorios que contiene archivos y directorios ...