121-Análisis de la recursividad en lenguaje C

Recursividad

En el proceso de llamar a una función, la función en sí se llama directa o indirectamente, lo que se denomina llamada recursiva de la función.
El efecto Drost es una forma visual de recursividad.
La recursividad generalmente transforma un problema grande y complejo en un problema más pequeño similar al problema original a resolver.Cuando el problema es pequeño hasta cierto punto, la respuesta es obvia.
La recursividad requiere condiciones de contorno, sección de avance recursiva y sección de retorno recursiva.

Ejecución de la función recursiva en dos procesos "Recursividad" y "retorno", estos dos procesos están controlados por la condición de terminación, es decir, recursiva capa por capa, hasta que se cumple la condición de terminación recursiva, la terminación recursiva, capa por capa y luego regresa a
la llamada recursiva con la ordinaria La función es la misma, cada vez que ocurre una llamada, se debe asignar un nuevo marco de pila (nuevos datos de parámetros, protección de campo, variables locales). A diferencia de las funciones ordinarias, porque el proceso recursivo es una capa por proceso de llamada de capa, hay Un proceso de asignación sucesiva de marcos de pila capa por capa no comienza a regresar hasta que se encuentra la condición de terminación recursiva, y luego el espacio de marco de pila se libera capa por capa, volviendo a la capa anterior y finalmente regresando a la función de llamada principal.

Por ejemplo: hay 5 estudiantes sentados juntos y pregunte cuántos años tiene el quinto estudiante. Dijo que era 2 años mayor que el cuarto estudiante y preguntó la edad del cuarto estudiante. Dijo que era 2 años mayor que el tercer estudiante. Le preguntó al tercer estudiante. Dijo que tenía 2 años más que el segundo estudiante. Pregúntale al segundo estudiante. El estudiante dijo que era 2 años mayor que el primer estudiante, y finalmente le preguntó al primer estudiante. Dijo que tenía 10 años. ¿Qué edad tiene el quinto estudiante?

El método de resolución de problemas no recursivo es el siguiente:

//非递归求年龄
int Age(int n)
{
    
    
	int tmp = 10; //第一个人年龄
	for(int i=1;i<n;i++)
	{
    
    
		tmp += 2;//后面的人比前一个多 2 岁
	}
	return tmp;
}

Entonces, ¿cómo lidiar con la recursividad?
Si la función Edad se usa para encontrar la edad, entonces
Edad (1) representa la edad de la primera persona;
Edad (2) representa la edad de la segunda persona;

Edad (n-1) representa la edad de la primera persona. n-1 la edad de la persona;
Edad (n) representa la edad de la enésima persona

//递归求年龄
int Age(int n)
{
    
    
	int tmp;//保存年龄

	if(n == 1)
		tmp = 10;
	else
		tmp = Age(n-1) + 2;//当前第n个比第n-1个年龄多 2

	return tmp;
}

Inserte la descripción de la imagen aquí
El rojo en la figura anterior indica el proceso de llamada de la función. En este proceso, cada función aún no se ha ejecutado, por lo que el espacio de memoria ocupado por cada función no se puede liberar y la llamada de la función debe ocupar una determinada pila espacio (un marco de pila), y el espacio de pila es muy pequeño (en el capítulo de memoria dinámica, hablamos de la pila 1M). Cuando el número de recursiones es muy grande, puede que no haya suficiente espacio de pila

Inserte la descripción de la imagen aquí

//递归求年龄
int Age(int n)
{
    
    
	int tmp;//保存年龄

	if(n == 1)
		tmp = 10;
	else
		tmp = Age(n-1) + 2;//当前第 n 个比第 n-1 个多 2

	return tmp;
}
//递归调用次数太多,程序崩溃
int main()
{
    
    
	printf("%d\n",Age(5000));//windows 系统,程序崩溃

	return 0;
}

Ejemplo: ¡Usa la recursividad para encontrar el factorial n!
Inserte la descripción de la imagen aquí

//递归求 n 的阶乘
//Fac(0)表示 0 的阶乘
//Fac(1)表示 1 的阶乘
//Fac(2)表示 2 的阶乘
//......
//Fac(n-1)表示 n-1 的阶乘
//Fac(n)表示 n 的阶乘
#include<stdio.h>
int Fac(int n)
{
    
    
	if(n==0 || n==1)
		return 1;
	else
		return Fac(n-1)*n;
}
int main()
{
    
    
	for(int i=0;i<10;i++)
	{
    
    
		printf("%d!=%d\n",i,Fac(i));
	}

	return 0;
}

Inserte la descripción de la imagen aquí

Usa la recursividad para encontrar 1 + 2 + 3 + ... + n.
Inserte la descripción de la imagen aquí

//递归求和
//Sum(1)表示从 1 加到 1
//Sum(2)表示从 1 加到 2
//Sum(3)表示从 1 加到 3
//......
//Sum(n-1)表示从 1 加到 n-1
//Sum(n)表示从 1 加到 n
int Sum(int n)
{
    
    
	if(n < 0) return -1;
	if(n==0 || n==1)
		return n;
	else
		return Sum(n-1) + n;
}

Ejemplo: usar la recursividad para encontrar la secuencia de Fibonacci es en
Inserte la descripción de la imagen aquí
realidad el ejemplo más inadecuado para la recursividad.

//非递归求斐波那契数列
#include<stdio.h>
int Fibon_for(int n)
{
    
    
	int f1 = 1;
	int f2 = 1;
	int f3 = 1;
	for(int i=2;i<n;i++)
	{
    
    
		f3 = f1 + f2;
		f1 = f2;
		f2 = f3;
	}
	return f3;
}

//递归求斐波那契数列
int Fibon(int n)
{
    
    
	if(n==1 || n==2)
		return 1;
	else
		return Fibon(n-1) + Fibon(n-2);
}

int main()
{
    
    
	printf("非递归结果:");
	for(int i=1;i<10;i++)
	{
    
    
		printf("%d ",Fibon_for(i));
	}
	printf("\n");
	printf("递归结果: ");
	for(int i=1;i<10;i++)
	{
    
    
		printf("%d ",Fibon(i));
	}

	return 0;
}

Inserte la descripción de la imagen aquí
Cuando el valor es grande, la eficiencia (tiempo) de las dos ejecuciones es muy diferente
Utilice procesamiento recursivo. La complejidad de tiempo es O (2 ^ n), es decir, 2 elevado a la enésima potencia.
Utilice procesamiento no recursivo. La complejidad de tiempo es O (n)

Inserte la descripción de la imagen aquí
describe el número de Fibonacci más inadecuado para el arrendamiento recursivo; la columna que se describe a continuación es un ejemplo adecuado de recursividad: Torre de Hanoi
Torre de Hanoi . Había una Pagoda del Vaticano en la antigüedad. Había tres asientos A, B y C en la torre. Al principio,
había varias placas en el asiento A. Las placas eran de diferentes tamaños, el más grande en el abajo y el más pequeño arriba. Un viejo monje quería mover estas placas del asiento A al asiento C, pero se estipuló que solo se podía mover una placa a la vez, y durante el movimiento, la placa grande siempre estaba en la parte inferior y la placa pequeña estaba en la parte superior. El bloque B se puede utilizar durante el movimiento. Solicitud para programar para generar los pasos de mover una placa

Inserte la descripción de la imagen aquí
Inserte la descripción de la imagen aquí
Inserte la descripción de la imagen aquí
Lo anterior es el caso de 2 platos, que es muy sencillo. ¿Qué pasa si hay 3 placas?
Inserte la descripción de la imagen aquí
Inserte la descripción de la imagen aquí
Inserte la descripción de la imagen aquí
Analice cuidadosamente las Figuras 2 y 5, solo cuando se alcance esta situación, la placa inferior se puede mover de A a C, y luego las otras placas pequeñas se pueden mover a C.

#include<stdio.h>
//模拟从 x 搬运到 y 的过程
void Move(char x,char y)
{
    
    
	printf("%c -> %c\n",x,y);
}

//将 n 个盘子的汉诺塔从 a 通过 b 搬到 c
void Hanoi(int n,char a,char b,char c)
{
    
    
	if(n == 1)//只有一个盘子,直接搬
	{
    
    
		Move(a,c);
	}
	else //先将上面的n-1个搬到b上,然后搬最下边的一个到c,再把n-1个从b搬到c
	{
    
    
		Hanoi(n-1,a,c,b);//上面 n-1 个从 a 通过 c 搬到 b
		Move(a,c);//只剩最后一个,直接搬
		Hanoi(n-1,b,a,c);//把上面搬到 b 上的 n-1 个盘子有从 b 通过 a 搬到 c
	}
}

int main()
{
    
    
	Hanoi(2,'A','B','C');
	return 0;
}

El resultado de la operación es el siguiente para
Inserte la descripción de la imagen aquí
ilustrar el proceso recursivo:
Inserte la descripción de la imagen aquí
Inserte la descripción de la imagen aquí
Resumen: Cuando se llama a una función, ya sea que sea llamada por sí misma o por otras funciones, los marcos de pila se asignarán a la función llamada.
No hay una recursividad infinita.
Es decir, la función recursiva debe tener una salida que sea el final de la recursividad (debe haber una declaración condicional que termine en la recursividad)
. La escala del problema no debe ser demasiado grande y la recursividad es demasiado profunda, lo que provoca desbordamiento de pila

Mire la siguiente pregunta:
Ingrese un número entero (entero sin signo) y use un algoritmo recursivo para generar los números enteros en orden inverso
Inserte la descripción de la imagen aquí
¿Cuáles son los resultados de salida de los dos métodos anteriores?
Izquierda: 4 3 2 1
printf ("% d", n% 10); luego se ejecuta de forma recursiva cada vez

#include<stdio.h>
void backward(int n)
{
    
    
	if(n>0)
	{
    
    
		printf("%d ",n%10);
		backward(n/10);
	}
}
int main()
{
    
    
	int n=0;
	scanf("%d",&n);
	printf("原整数:%d\n",n);
	printf("反向数:");
	backward(n);
	printf("\n");
	return 0;
}

Inserte la descripción de la imagen aquí

Derecha: 1 2 3 4 se
mantiene de forma recursiva hasta que se encuentra la condición de terminación y luego vuelve a imprimir uno por uno

#include<stdio.h>
void backward(int n)
{
    
    
	if(n>0)
	{
    
    
		backward(n/10);
		printf("%d ",n%10);
		
	}
}
int main()
{
    
    
	int n=0;
	scanf("%d",&n);
	printf("原整数:%d\n",n);
	printf("反向数:");
	backward(n);
	printf("\n");
	return 0;
}

Inserte la descripción de la imagen aquí
Inserte la descripción de la imagen aquí
La condición de terminación de la recursividad es muy importante, de lo contrario, se repetirá sin cesar y caerá en un bucle infinito, lo que eventualmente hará que se agote el espacio de la pila y se informará un error de StackOverflow.

Nota:
1. Restricciones: al diseñar un proceso recursivo, debe haber al menos una condición que pueda terminar la recursividad, y también debe tratar situaciones en las que dichas condiciones no se cumplan dentro de un número razonable de llamadas recursivas. Si no hay ninguna condición que se pueda cumplir en circunstancias normales, el proceso caerá en el alto riesgo de ejecutar un bucle infinito
2. Uso de memoria: El espacio utilizado por las variables locales de la aplicación es limitado. Cada vez que un procedimiento se llama a sí mismo, ocupará más espacio de memoria para guardar copias adicionales de sus variables locales. Si este proceso continúa indefinidamente, eventualmente conducirá al error StackOverflowException
3. Eficiencia: Es posible realizar un bucle en lugar de recursividad en casi cualquier situación. El bucle no produce la sobrecarga requerida para pasar variables, inicializar espacio de almacenamiento adicional y devolver valores, por lo que usar bucles es equivalente a usar llamadas recursivas para mejorar en gran medida el rendimiento.
4. Recurrencia mutua: si dos procedimientos se llaman entre sí, el rendimiento puede deteriorarse. Incluso producir recursividad infinita. Los problemas causados ​​por este tipo de diseño son los mismos que los causados ​​por un solo proceso recursivo, pero es más difícil de detectar y depurar
5. Use paréntesis al llamar: Cuando un procedimiento de función se llama a sí mismo de manera recursiva, se deben agregar paréntesis después de nombre del procedimiento (incluso si no hay una lista de parámetros). De lo contrario, el nombre de la función se considerará como el valor de retorno de la función.
6. Prueba: al escribir un proceso recursivo, debe probarlo con mucho cuidado y confianza para asegurarse de que siempre puede cumplir con ciertas restricciones y terminar el proceso recursivo. protección contra quedarse sin memoria debido a llamadas recursivas excesivas

Inserte la descripción de la imagen aquí

Supongo que te gusta

Origin blog.csdn.net/LINZEYU666/article/details/111852228
Recomendado
Clasificación