Implementación y análisis de algoritmos recursivos y de secuencia de Fibonacci y algoritmos iterativos.

recursividad

La técnica de programación de llamar al programa en sí se llama recursividad. La recursividad
tiene dos procesos: en pocas palabras, uno es el proceso recursivo y el otro es el proceso recursivo .
Dos condiciones necesarias para la recursividad
1. Existe una condición de restricción . Cuando se cumple esta condición de restricción, la recursividad ya no continuará.
2. Cada llamada recursiva se acerca cada vez más a esta restricción
.
La esencia de la recursividad es una llamada a función , y su esencia es formar y liberar un marco de pila. Llamar a una función tiene un costo, y este costo se refleja en la formación y liberación de la pila. En el marco: tiempo + espacio. La recursividad es el proceso de formación continua de marcos de pila.
A través de lo anterior, también podemos comprender algunas limitaciones de la recursividad.

  1. Los recursos de memoria y CPU son limitados, lo que significa que la recursividad razonable no puede continuar indefinidamente.
  2. La recursividad no se puede utilizar en ningún momento, pero debe cumplir con sus propios escenarios de aplicación, es decir, los subproblemas del problema objetivo también se pueden resolver utilizando el mismo algoritmo.La esencia es la idea de divide y vencerás.
  3. Idea central: reducir cosas grandes a cosas pequeñas + exportación recursiva

Secuencia Fibonacci

La secuencia de Fibonacci, también conocida como secuencia de la sección áurea, fue introducida por el matemático Leonardoda Fibonacci utilizando el ejemplo de la reproducción del conejo, por lo que también se la llama "secuencia del conejo". Es la siguiente secuencia: 0, 1, 1, 2, 3 , 5, 8, 13, 21, 34,…

Antes de explicar la secuencia recursiva de Fibonacci, debemos comprender la naturaleza de la recursividad.

A través de la definición anterior, podemos definir y usar código para implementar la secuencia de Fibonacci.
Primero, veamos la versión recursiva del código.

versión recursiva

int Fib(int n)
{
    
    	
	if(n<=0)
	{
    
    
		return 0;
	}
	else if (1 == n || 2 == n)
	{
    
    
		return 1;
	} 
	return Fib(n - 1) + Fib(n - 2);
} 
int main()
{
    
    
	int n = 5;
	int x = Fib(n);
	printf("fib(%d): %d\n", n, x);
	return 0;
}

Insertar descripción de la imagen aquíEste es el diagrama de la teoría de llamadas recursivas formado por la fórmula anterior cuando n=5, es decir, Fib(5)

La esencia de la recursividad mencionada anteriormente es la llamada a funciones. Se puede ver que cuando el valor de n es mayor (es decir, el número de Fibonacci requerido es mayor), se llaman cada vez más funciones, por lo que estamos aquí. El costo de tiempo y El costo del espacio es muy alto
(la complejidad del tiempo es O(2^N) , la complejidad del espacio es O(N) ).

Se puede probar con el siguiente código.

int main()
{
    
    
	int n = 10; //n从40开始,时间就会慢慢增大得明显
	//使用win提供的GetTickCount()函数,来获取开机到现在的累计时间(单位毫秒)
	double start = GetTickCount(); 
	int x = Fib(n);
	double end = GetTickCount();
	printf("%lf ms\n", (end - start));//二者进行相减就可以算出递归所用时间
	system("pause");
	return 0;
}

Cuando n comienza desde 40, el tiempo obviamente aumentará. En este momento, incluso si n solo agrega 1 cada vez para probar el tiempo, el tiempo será mucho mayor. Esto se debe a que se puede ver en el diagrama de la teoría de llamadas recursivas. arriba cuando F Cuando (4) cambia a F(5), F(3) debajo de la rama F(4) y la siguiente serie aparecerá en la otra rama de F(5). Se puede ver que cuando F( 40) se convierte en Cuando es F(41), F(39) y su serie aparecerá en la otra rama final de F(41), por lo que incluso si solo agrega 1 cada vez, el cambio es enorme.

A continuación, utilizamos la idea de iteración para mejorar la eficiencia del tiempo y el espacio (para decirlo sin rodeos, la iteración es un ciclo)

Versión iterativa

int Fib(int n)
{
    
    
	int *dp = (int*)malloc(sizeof(int)*(n+1)); //多加一个是为了下标保持一致方便计算
	//[0]不用(当然,也可以用,不过这里我们从1开始,为了后续方便)
	dp[1] = 1;
	dp[2] = 1;
	int i = 3;
	while(i<=n)
	{
    
    
		dp[i] = dp[i - 1] + dp[i - 2];
		i++;
	} 
	int ret = dp[n];
	free(dp);
	return ret;
}

De hecho, este también es un algoritmo de programación dinámica, pero también podemos ver algunos inconvenientes, es decir, guardamos la secuencia de Fibonacci del 1 al n en el espacio del montón que asignamos mal (el espacio es complejo Tiempo O (n )), sin embargo, cualquier número de Fibonacci solo está relacionado con los dos primeros números, por lo que podemos realizar una optimización adicional

Versión iterativa mejorada

int Fib(int n)
{
    
    
	int first = 1;
	int second = 1;
	int third = 1;
	while (n > 2){
    
    
		third = second + first;
		first = second;
		second = third;
		n--;
	} 
return third;
}

Esta versión iterativa del código se ha optimizado enormemente en complejidad tanto temporal como espacial.

Resumir

En resumen, también podemos ver en el ejemplo de Fibonacci que los algoritmos recursivos generalmente no son muy eficientes, mientras que la iteración es más eficiente que la recursividad. Sin embargo, el código para la recursividad es relativamente más simple que la iteración. Fuerte legibilidad Los escenarios donde se usa a menudo la
recursividad son:
1. Cuando el problema y los subproblemas tienen una relación recursiva (factorial).
1. Estructuras de datos con propiedades recursivas (listas enlazadas, árboles).

Por lo tanto, la función recursiva es solo una técnica de resolución de problemas. Al igual que otras técnicas, también tiene ciertos defectos. Específicamente: la sobrecarga de tiempo y memoria de la función recursiva es muy grande e incluso puede causar que el programa se bloquee en casos extremos. ( Por lo tanto, las funciones recursivas deben regresar después de la recursividad; de lo contrario, eventualmente causará un desbordamiento de la pila ). Por lo tanto, siempre debemos considerar esto cuando usemos la recursividad en el futuro.

Supongo que te gusta

Origin blog.csdn.net/kklovecode/article/details/132259395
Recomendado
Clasificación