121-Analyse de la récursivité en langage C

Récursion

Dans le processus d'appel d'une fonction, la fonction elle-même est appelée directement ou indirectement, ce qui est appelé l'appel récursif de la fonction.
L'effet Drost est une forme visuelle de récursivité.
La récursivité transforme généralement un problème volumineux et complexe en un problème plus petit similaire au problème initial à résoudre. Lorsque le problème est petit dans une certaine mesure, la réponse est évidente.
La récursivité nécessite des conditions aux limites, une section avant récursive et une section de retour récursive.

Exécution de la fonction récursive en deux processus « récursivité » et « retour », ces deux processus sont commandés par la condition de terminaison, à savoir la couche récursive par couche, jusqu'à ce que la condition de terminaison récursif est satisfaite, la récursivité de terminaison, couche par couche, puis revenir à
la appel récursif avec l'ordinaire La fonction est la même, à chaque fois qu'un appel se produit, une nouvelle trame de pile (nouvelles données de paramètres, protection de champ, variables locales) doit être allouée. Contrairement aux fonctions ordinaires, car le processus récursif est une couche par couche processus d'appel de couche, il y a Un processus d'allocation successive de trames de pile couche par couche ne commence à revenir que lorsque la condition de terminaison récursive est rencontrée, puis l'espace de trame de pile est libéré couche par couche, revenant à la couche précédente, et enfin retournant à la fonction d'appel principale.

Par exemple: il y a 5 élèves assis ensemble et demandez quel âge a le 5ème élève? Il a dit qu'il avait 2 ans de plus que le quatrième élève et a demandé l'âge du quatrième. Il a dit qu'il avait 2 ans de plus que le troisième. Il a demandé au troisième. Il a dit qu'il avait 2 ans de plus que le troisième. Demandez au deuxième étudiant. L'élève a dit qu'il avait 2 ans de plus que le premier étudiant et a finalement demandé au premier étudiant. Il a dit qu'il avait 10 ans. Quel âge a le cinquième étudiant.

La méthode de résolution de problèmes non récursive est la suivante:

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

Alors, comment gérer la récursivité?
Si la fonction Age est utilisée pour trouver l'âge, alors
Age (1) représente l'âge de la première personne;
Age (2) représente l'âge de la deuxième personne;

Age (n-1) représente l' âge de la première personne. n-1 l'âge de la personne; l'
âge (n) représente l'âge de la nième personne

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

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

	return tmp;
}

Insérez la description de l'image ici
Le rouge dans la figure ci-dessus indique le processus d'appel de la fonction. Dans ce processus, chaque fonction n'a pas encore été exécutée, donc l'espace mémoire occupé par chaque fonction ne peut pas être libéré et l'appel de la fonction doit occuper une certaine pile space (un frame de pile), et l'espace de pile est très petit (dans le chapitre sur la mémoire dynamique, nous avons parlé de la pile 1M). Lorsque le nombre de récursions est très grand, l'espace de pile peut être insuffisant

Insérez la description de l'image ici

//递归求年龄
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;
}

Exemple: utilisez la récursivité pour trouver la factorielle n!
Insérez la description de l'image ici

//递归求 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;
}

Insérez la description de l'image ici

Utilisez la récursivité pour trouver 1 + 2 + 3 + ... + n.
Insérez la description de l'image ici

//递归求和
//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;
}

Exemple: l'utilisation de la récursivité pour trouver la séquence de Fibonacci est en
Insérez la description de l'image ici
fait l'exemple le moins approprié pour la récursivité.

//非递归求斐波那契数列
#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;
}

Insérez la description de l'image ici
Lorsque la valeur est élevée, l'efficacité (temps) des deux exécutions est très différente
. Utilisez un traitement récursif. La complexité temporelle est O (2 ^ n), c'est-à-dire 2 à la puissance nième.
Utilisez un traitement non récursif. La complexité temporelle est O (n)

Insérez la description de l'image ici
décrit le plus impropre à la location récursive nombre de Fibonacci que la colonne décrite ci-dessous est un exemple approprié de récursivité: Tour de Hanoi
Tour de Hanoi . Il y avait une pagode du Vatican dans les temps anciens. Il y avait trois sièges A, B et C dans la tour. Au début,
il y avait un certain nombre de plaques sur le siège A. Les plaques étaient de tailles différentes, la plus grande au en bas et le plus petit en haut. Un vieux moine voulait déplacer ces plaques du siège A au siège C, mais il était stipulé qu'une seule plaque pouvait être déplacée à la fois, et pendant le mouvement, la grande plaque était toujours sur le fond et la petite plaque était au sommet. Le bloc B peut être utilisé pendant le mouvement. Demande de programmation pour sortir les étapes de déplacement d'une plaque

Insérez la description de l'image ici
Insérez la description de l'image ici
Insérez la description de l'image ici
Ce qui précède est le cas de 2 plaques, ce qui est très simple. Et s'il y a 3 plaques?
Insérez la description de l'image ici
Insérez la description de l'image ici
Insérez la description de l'image ici
Analysez attentivement les figures 2 et 5, seulement lorsque cette situation est atteinte, la plaque inférieure peut être déplacée de A à C, puis les autres petites plaques peuvent être déplacées vers 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;
}

Le résultat de l'opération est le suivant pour
Insérez la description de l'image ici
illustrer le processus récursif:
Insérez la description de l'image ici
Insérez la description de l'image ici
Résumé: Lorsqu'une fonction est appelée, qu'elle soit appelée par elle-même ou par d'autres fonctions, des cadres de pile seront alloués à la fonction appelée.
Il n'y a pas de récursion infinie.
Autrement dit, la fonction récursive doit avoir une sortie qui est la fin de la récursivité (il doit y avoir une instruction conditionnelle qui se termine par la récursivité)
. L'échelle du problème ne doit pas être trop grande et la récursivité est trop profonde, ce qui entraîne débordement de pile

Examinez la question suivante:
Entrez un entier (entier non signé) et utilisez un algorithme récursif pour générer les entiers dans l'ordre inverse.
Insérez la description de l'image ici
Quels sont les résultats de sortie des deux méthodes ci-dessus?
Gauche: 4 3 2 1
printf ("% d", n% 10); puis exécuté de manière récursive à chaque fois

#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;
}

Insérez la description de l'image ici

Droite: 1 2 3 4
reste récursivement jusqu'à ce que la condition de fin soit rencontrée, puis revient à imprimer un par un

#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;
}

Insérez la description de l'image ici
Insérez la description de l'image ici
La condition de fin de la récursivité est très importante, sinon elle se répétera sans fin et tombera dans une boucle infinie, ce qui finira par épuiser l'espace de la pile et une erreur StackOverflow sera signalée.

Remarque:
1. Restrictions: Lors de la conception d'un processus récursif, il doit y avoir au moins une condition qui peut mettre fin à la récursivité, et il doit également traiter les situations où ces conditions ne sont pas remplies dans un nombre raisonnable d'appels récursifs. Si aucune condition ne peut être remplie dans des circonstances normales, le processus tombera dans le risque élevé d'exécuter une boucle infinie
2. Utilisation de la mémoire: L'espace utilisé par les variables locales de l'application est limité. Chaque fois qu'une procédure s'appelle elle-même, elle prendra plus d'espace mémoire pour enregistrer des copies supplémentaires de ses variables locales. Si ce processus se poursuit indéfiniment, il conduira finalement à l'erreur StackOverflowException
3. Efficacité: Il est possible de boucler au lieu de récursivité dans presque toutes les situations. La boucle ne produit pas la surcharge requise pour transmettre des variables, initialiser un espace de stockage supplémentaire et renvoyer des valeurs, donc utiliser des boucles équivaut à utiliser des appels récursifs pour améliorer considérablement les performances
4. Récursivité mutuelle: si deux procédures s'appellent, les performances peuvent se détériorer. Même produire récursivité infinie. Les problèmes causés par ce type de conception sont les mêmes que ceux causés par un seul processus récursif, mais il est plus difficile à détecter et à déboguer
5. Utilisez des parenthèses lors de l'appel: lorsqu'une procédure Function s'appelle elle-même de manière récursive, des parenthèses doivent être ajoutées après le nom de la procédure (même s'il n'y a pas de liste de paramètres). Sinon, le nom de la fonction sera considéré comme la valeur de retour de la fonction.
6. Test: Lors de l'écriture d'un processus récursif, vous devez le tester très soigneusement et en toute confiance pour vous assurer qu'il peut toujours répondre à certaines contraintes et terminer le processus récursif. Assurez-vous protection contre le manque de mémoire en raison d'appels récursifs excessifs

Insérez la description de l'image ici

Je suppose que tu aimes

Origine blog.csdn.net/LINZEYU666/article/details/111852228
conseillé
Classement