C语言函数针对训练--递归篇(动画讲解,由易到难递归例题)

news2024/11/30 10:52:15

CSDN话题挑战赛第2期
参赛话题:学习笔记

前言
💖作者龟龟不断向前
简介宁愿做一只不停跑的慢乌龟,也不想当一只三分钟热度的兔子。
👻专栏:C++初阶知识点

👻工具分享

  1. 刷题: 牛客网 leetcode
  2. 笔记软件:有道云笔记
  3. 画图软件:Xmind(思维导图) diagrams(流程图)

在这里插入图片描述

如果觉得文章对你有帮助的话,还请点赞,关注,收藏支持博主🙊,如有不足还请指点,博主及时改正

函数递归

文章目录

    • 函数递归
      • 🚀1.递归的概念
        • 🍉史上最简单的递归
      • 🚀2.递归的必要条件
        • 🍉举例:递归设计循环输出数的每一位
        • 🍉递归展开图分析递归
      • 🚀3.初学递归必做练习题
        • 🍉递归实现strlen
        • 🍉递归求n的阶乘
        • 🍉求第n个斐波那契数
        • 🍉递归实现计算n的k次方
        • 🍉递归实现逆置字符串

🚀1.递归的概念

  上次我们介绍了函数的嵌套调用,一个函数的定义中除了可以调用另一个函数,还可以调用其本身。程序调用自身的编程技巧称为递归( recursion)。递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。

递归的主要思考方式在于:把大事化小

 

🍉史上最简单的递归

#include<stdio.h>

int main()
{
	printf("hello world\n");
	main();
	return 0;
}

  虽然说是递归,但其实是一个死递归。就像一个循环设计成死循环一样,是没有意义的。main函数调用main函数,该程序会一直输出hello world,直到程序挂掉(栈溢出stack overflow)。

解释

在这里插入图片描述

 

  咱们调用的时候也会有栈溢出的报错

在这里插入图片描述

 


 

🚀2.递归的必要条件

  在学习循环时,循环也有其对应的循环必要条件,这样才能构成一个功能完整的可控的循环。

相应地,想要形成一个功能完成的可控的递归,也有其对应的递归必要条件。

  • 存在限制条件,当满足这个限制条件的时候,递归便不再继续。
  • 每次递归调用之后越来越接近这个限制条件

 

🍉举例:递归设计循环输出数的每一位

递归的核心:

  1. 将大事化小,大问题->小问题->最小问题(而且这些小问题的处理方式逻辑是类似的)
  2. 到分解成最小子问题时候的处理方法

例如将顺序打印1234,给大事化小

  1. 顺序打印1234 -> 顺序打印123 + 输出1234的个位(因为4很好输出,就是1234的个位)
  2. 顺序打印123 -> 顺序打印12 + 输出123的个位
  3. 顺序打印12 -> 顺序打印1 + 输出12的个位

顺序打印一位数已经是最小子问题,最小子问题直接打印其个位即可

 

#include<stdio.h>

void seq_print(int n)
{
	if (n > 9)
	{
		seq_print(n / 10);
	}
	printf("%d ", n % 10);
}

int main()
{
	seq_print(1234);
	return 0;
}

 

🍉递归展开图分析递归

递归,这个名字取得非常的精简,顾名思义:递推回归!它即存在一个递推的过程,也有有一个回归的过程。

函数嵌套调用的过程:

在这里插入图片描述

图解

在这里插入图片描述


 

🚀3.初学递归必做练习题

🍉递归实现strlen

 

模拟实现strlen,迭代版本(非递归版本),以及递归版本

非递归版本:

在这里插入图片描述

#include<stdio.h>

int my_strlen(char* str)
{
	int count = 0;
	while (*str != '\0')
	{
		count ++;
		str++;
	}
	return count;
}

int main()
{
	char str[] = "hello world";
	int len = my_strlen(str);
	printf("len = %d\n", len);
	return 0;
}

 


 

递归版本:

还是那个核心,大事化小,递归逻辑和非递归的逻辑是类似的,只是思考角度不一样

如果第一个字符不是\0,那么字符串的长度为1 + 后面部分字符串的长度

abcdef的长度 -> 1 + bcdef的长度

bcdef的长度 -> 1 + cdef的长度

………………

ef的长度 -> 1 + f的长度

f的长度 -> 1 + ""的长度

最小子问题:""(空串)的长度返回0即可

 

#include<stdio.h>

int my_strlen(char* str)
{
	if ((*str) == '\0')//最小子问题的处理方式
	{
		return 0;
	}
	else
	{
		return 1 + my_strlen(str + 1);
	}
}

int main()
{
	char str[] = "hello world";
	int len = my_strlen(str);
	printf("len = %d\n", len);
	return 0;
}

 

🍉递归求n的阶乘

数学上计算n的阶乘的公式有

  1. 通项公式:n! = 1 * 2 * 3 * …* n
  2. 递推公式:n! = n*(n-1)!

  而我们递归所需要的就是递推公式,它可以现成的将大事化小

#include<stdio.h>

int Fac(int n)
{
	if (n < 2)
	{
		return 1;
	}
	else
	{
		return n*Fac(n - 1);
	}
}

int main()
{
	int num = 0;
	int ret = 0;
	scanf("%d", &num);
	ret = Fac(num);
	printf("%d\n", ret);
	return 0;
}

 


 

🍉求第n个斐波那契数

斐波那契数列:第一个数和第二个数为1,后面的数是前面两项数字的和,这样的数的组合就是斐波那契数列

例如:1 1 2 3 5 8 13 21 34 55…

非常明显:斐波那契数列的定义已经明确了他的递归公式:F(N) = F(N-1) + F(N-2)

#include<stdio.h>

int Fibonacci(int n)
{
	if (n <= 2)
	{
		return 1;
	}
	else
	{
		return Fibonacci(n - 1) + Fibonacci(n - 2);
	}
}

//斐波那契数列 1 1 2 3 5 8 13 21
int main()
{
	int n = 0;
	scanf("%d", &n);
	printf("%d\n", Fibonacci(n));
	return 0;
}

 

  但是很不幸,上述程序是效率很低的,如果计算45!左右时,都要计算老半天,效率非常低。

解释

原因是,里面存在着大量的重复计算,比如说你在计算第5个斐波那契数

在这里插入图片描述

在计算出F(4)的过程中,其实也会将F(3)给计算出来,可是到了计算右边的F(3)时候,又重复地计算了一遍F(3)

因为该递归计算出来的数,是没有保存的,当数字越来越大,就存在着大量的重复计算,效率也就自然低了

 

  我们可以看一看,当计算40!的时候,计算了多少次F(3)

int count = 0;

int Fibonacci(int n)
{
	if (n == 3)
	{
		++count;
	}
	if (n <= 2)
	{
		return 1;
	}
	else
	{
		return Fibonacci(n - 1) + Fibonacci(n - 2);
	}
}

int main()
{
	int n = 0;
	scanf("%d", &n);
	printf("%d! = %d\n", n, Fibonacci(n));
	printf("F(3) = %d\n", count);
	//printf("fibo(2) = %d\n", count);
	return 0;
}

 

在这里插入图片描述

 

所以,使用递归来实现斐波那契是很低效的,所以咱们要使用迭代。

图解思路

![在这里插在这里插入图片描述

#include<stdio.h>

int Fibonacci(int n)
{
	if (n <= 2)
	{
		return 1;
	}
	int a = 1;
	int b = 1;
	int c = 0;
	while ((n--) > 2)
	{
		c = a + b;
		a = b;
		b = c;
	}
	return c;
}


//斐波那契数列 1 1 2 3 5 8 13 21
int main()
{
	int n = 0;
	scanf("%d", &n);
	printf("%d! = %d\n", n, Fibonacci(n));
	return 0;
}

 

  小扩展:,其实大家平时听到的,青蛙跳台问题,生兔子问题实质都是斐波那契数列问题

这是剑指offer的题目哟!

#include<stdio.h>

int numWays(int n) {
        size_t left = 1;
        size_t right = 1;
        if(n < 2)
        {
            return 1;
        }

        while((n--) >= 2)
        {
            size_t ret = (left + right)%(1000000007);
            left = right;
            right = ret;
        }

        return right;
    }

 

🍉递归实现计算n的k次方

大家肯定可以想到累乘法–将n累乘k次即可

递归思路:n^k = n^(k-1) * k

n的k次方等于n的k-1次放乘以k

#include<stdio.h>

double Pow(double n, int k)//n的k次方
{
	if (k < 0)
	{
		k = -k;
		n = 1.0 / n;
	}
	if (k == 0)
	{
		return 1.0;
	}
	if (k == 1)
	{
		return n;
	}
	return n * Pow(n, k - 1);
}

int main()
{
	printf("%f\n", Pow(2, 3));
	printf("%f\n", Pow(2, -3));
	return 0;
}

解释

这里需要特殊处理一下k是负数,k是0的情况,如果k是负数,我们要将n变成1/n,k再取正数。

其实,这还是一道剑指offer上面的题,有个大佬想出了一种特别高效的算法,有兴趣的同学可以看一下,真的叫人拍案叫绝

 


 

🍉递归实现逆置字符串

非递归版本:

使用两个下标来实现左右字符交换即可,字符串的后面是放了\0的,图中没画

在这里插入图片描述

#include<stdio.h>
#include<string.h>

void reverse_string(char str[])
{
	int left = 0;
	int right = strlen(str) - 1;
	while (left < right)
	{
		char tmp = str[left];
		str[left] = str[right];
		str[right] = tmp;
		++left;
		--right;
	}
}

int main()
{
	char str[] = "abcdef";
	printf("逆置前\n");
	printf("%s\n", str);

	reverse_string(str);
	printf("逆置后\n");
	printf("%s\n", str);
	return 0;
}

 


 

递归版本:

实现逻辑和非递归是一致的

思考角度:逆置abcdef -> 交换af + 逆置bcde

逆置bcde -> 交换be + 逆置cd

逆置cd -> 交换be + 逆置""

最小子问题:空串或者串的长度为1,不做任何处理

想必大家都会交换af的操作,但是如何实现逆置bcde的操作呢,因为C字符串是以\0结尾的,如果先完成a和f的交换,那么如何取到bcde字符串?方法:a和f的交换先完成一部分:

  1. 提出a(保存a字符)
  2. 将f放进原来a的位置
  3. 后面的位置放\0–为了我们可以控制第4步的逆置
  4. 逆置bcde
  5. 再将a放到原来f的位置

1 2 5组合起来才是a和f交换的操作,现在将逆置bcde的步骤,插进交换a和f的中,即可实现递归

图解

在这里插入图片描述

#include<string.h>
#include<stdio.h>

void reverse_string(char * string)
{
	int len = strlen(string);
	if (len > 1)
	{
		char ch = string[0];//1.将第一个字符保存下来
		string[0] = string[len - 1];//2.最后一个字符将第一个字符的位置占据
		string[len - 1] = '\0';//3.最后一个位置给上\0便于操作,能实现递归的核心操作
		reverse_string(string + 1);//4.子问题
		string[len - 1] = ch;//5.将\0的位置用ch补上
	}
}


int main()
{
	char str[] = "abcdef";
	printf("逆置前\n");
	printf("%s\n", str);

	reverse_string(str);
	printf("逆置后\n");
	printf("%s\n", str);
	return 0;
}

 

  本片文章就讲到这里,如果不好理解递归的话,建议大家多画画递归展开图,对递归的递推和回归有个一个更深入的理解当然了,递归的路还很长,在以后的算法和数据结构中都会遇到,相信不同阶段大家会有不同的理解。希望能帮助到大家!

 

  下期递归小实战–实现汉诺塔问题,咱们下期间!

点赞

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/3080.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【FreeRTOS】队列的使用

队列的使用前言创建队列发送数据接收数据查询队列数据个数使用示例创建两个线程配置按键驱动编写按键发送子函数编写按键读取子函数前言 基于 FreeRTOS 的应用程序由一组独立的任务构成——每个任务都是具有独立权限的小程序。这些独立的任务之间很可能会通过相互通信以提供有…

leetcode算法每天一题010: 正则表达式,判断pattern和string是否匹配(动态规划)

题目描述 ‘.’ 匹配任意单个字符‘*’ 匹配零个或多个前面的那一个元素 PATTERNTRUEFALSEa.baab,abb,acba, ab,ba*bb,ab,aab,aaaba,abb,acbc*a.baab,caab,cccccacb,ccabbbaab,cabbbdp[i] [j] 的含义是当字符串 s 的长度为 i&#xff0c;模式串 p 的长度为 j 时&#xff0c;两…

KubeVela 插件指南:轻松扩展你的平台专属能力

KubeVela 插件&#xff08;addon&#xff09;可以方便地扩展 KubeVela 的能力。正如我们所知&#xff0c;KubeVela 是一个微内核高度可扩展的平台&#xff0c;用户可以通过模块定义&#xff08;Definition&#xff09;[1]扩展 KubeVela 的系统能力&#xff0c;而 KubeVela 插件…

C# Winform程序开发笔记

C#d的开发发展到今天,已经改进了不少,对于非常多的应用可以使用C#进行开发,也非常方便,希望以下笔记对于高级开发者(网络通信、线程应用)而言能有所帮助吧。 1、INI文件的读写操作 1.1 类文件 using System; using System.Collections.Generic; using System.IO; usin…

【SpringBoot笔记21】SpringBoot框架使用AOP + 自定义注解实现请求日志记录

这篇文章&#xff0c;主要介绍SpringBoot框架使用AOP 自定义注解实现请求日志记录。 目录 一、SpringBoot记录日志 1.1、环境搭建 1.2、配置FastJson 1.3、自定义LogRecord注解 1.4、定义日志实体类 1.5、创建HttpRequestUtil工具类 1.6、定义AOP切面 1.7、编写测试类…

arm服务器运行onlyoffice

公司的arm服务器上运行onlyoffice报错 是x86_64架构的镜像在arm服务器下不兼容 需要下载兼容arm架构的docker镜像 在dockerhub上 onlyoffice地址 Docker Hubhttps://hub.docker.com/r/onlyoffice/documentserver 下载兼容arm架构的镜像版本 # docker --version Docker ver…

《从0开始写一个微内核操作系统》5-页表映射

ChinOS https://github.com/jingjin666/GN-base/tree/chinos 页表需要多少空间 在第4节中&#xff0c;我们了解到&#xff0c;每一级页表实际上就是一个512大小的unsigned long数组&#xff0c;一个页表本身占用512*84K空间 /* PGD */ /* 每个ENTRY包含512G内存区域 */ typed…

JavaWeb传统商城(MVC三层架构)的促销功能模块【进阶版】

文章目录一.JavaWeb商城项目的促销功能模块【进阶版】开发过程记录1.1 项目背景1.2 需求分析1.3 开发流程/顺序二.促销页面(0.1颗星)2.1 需求介绍2.2 JSP页面2.3效果展示三,商品详情页面(0.2颗星)3.1 需求介绍和效果图3.2 数据库分析3.2 Servlet层3.3 Service层3.4 DAO层3.5 JS…

一本通1064;奥运奖牌计数

#include <iostream> using namespace std; int main() {int n, Jin, Yin, Tong;int JinSum 0, YinSum 0, TongSum 0, sum;cin >> n;for (int i 1; i < n; i) // 循环n次{cin >> Jin >> Yin >> Tong; // 输入一天获得的金银铜牌数JinSum …

InfluxDB学习记录(三)——influxdb的flux语法

什么是Flux Flux 是 InfluxData 的功能性数据脚本语言&#xff0c;设计用于查询、分析和处理数据&#xff0c;它是InfluxQL 和其他类似 SQL 的查询语言的替代品。 每个 Flux 查询都需要以下内容&#xff1a; 数据源时间范围数据过滤器 Flux代码示例 from(bucket:"example…

重装系统后打印机状态已暂停如何恢复

​当我们在使用打印机打印文件的时候&#xff0c;有时候会发现打印机状态已暂停&#xff0c;打印不下去了&#xff0c;这时候怎么恢复呢&#xff0c;其实只需要取消掉打印暂停就可以了&#xff0c;下面就和大家讲讲重装系统后打印机状态已暂停如何恢复吧。 打印机状态已暂停怎…

【前端】Vue+Element UI案例:通用后台管理系统-Home组件:卡片、表格

文章目录目标代码0.布局1.左上User卡片2.左下table卡片2.1数据&#xff1a;TableData.js2.2table2.3代码优化&#xff1a;循环3.右上数据卡片3.1数据&#xff1a;CountData3.2结构3.3布局3.4样式总代码Home.vue参考目标 红框内部分都是卡片&#xff0c;鼠标悬停会有阴影左下是表…

java计算机毕业设计基于安卓Android的天文观星系统app uniapp 小程序

项目介绍 信息技术的发展带来了大量的数据内容,在这些数据中,想要找到自己需要的只有通过搜索引擎。如今,通过百度去查找信息成为大众的首选,然而在经济利益的驱动下,许多百度来的信息都是商业内容,很难找到真实有用的实际信息。在互联网中平台,天文信息交流和资源共享是一个非…

【每日训练】进制转换

目录 题目链接&#xff1a; 测试用例&#xff1a; 解析&#xff1a; 程序&#xff1a; 题目链接&#xff1a; 进制转换_牛客题霸_牛客网 (nowcoder.com) 测试用例&#xff1a; 解析&#xff1a; 题目描述&#xff1a; 输入一个十进制数&#xff0c;转化为对应输入的几进制数…

微服务及其在app自动化领域的应用

微服务是一种软件开发技术- 面向服务的体系结构&#xff08;SOA&#xff09;架构样式的一种变体&#xff0c;它提倡将单一应用程序划分成一组小的服务&#xff0c;服务之间互相协调、互相配合&#xff0c;为用户提供最终价值。每个服务运行在其独立的进程中&#xff0c;服务与服…

5款可视化工具优缺点比对,谁赢了?

利用Excel表格进行汇报&#xff0c;底下坐着的领导可能会看起来眼花缭乱&#xff0c;但如果是以图表可视化的形式展现出来&#xff0c;那可简洁明了多了&#xff0c;不仅仅可以看到某个项目近几个月的走势&#xff0c;并且还能知道之后的决策。 可视化图表用什么工具做&#xf…

FP8训练调研

FP8训练调研 一、FP8训练相关技术要点总结 1、基于块的累加技术&#xff0c;减小低精度数之间相加的累积误差 2、随机舍入技术代替四舍五入&#xff0c;降低舍入误差 3、混合FP8技术&#xff0c;用1-4-3进行前向&#xff0c;1-5-2进行反向 4、设置指数偏移&#xff0c;使F…

windows搭建WebDAV服务,并内网穿透公网访问【无公网IP】

自己用Windows Server搭建了家用NAS主机&#xff0c;WebDAV的文件共享方式当然也是必不可少的。 本文使用的是WIN10 专业版。 1. 安装IIS必要WebDav组件 1.1 打开控制面板&#xff0c;查看方式改为“类别”&#xff0c;进入“程序”&#xff0c;“启用或关闭Windows功能” 1…

数据结构链表之无头单向循环链表的实现

文章目录前言1.链表的相关介绍1.什么是节点2.链表和顺序表的对比3.链表的种类2.链表的实现1.节点的定义和创建2.链表的相关函数接口的实现1.链表的创建2.数据的插入头插尾插指定位置插入3.数据的删除头删尾删指定位置删除4.打印显示节点数据5.数据查找6.链表销毁3.总结前言 之…

2022新版加壳工具-支持.NET虚拟化加密

.NET 虚拟化保护 .NET 程序的保护技术在对抗中不断演进&#xff0c;出现了控制流混淆、名称混淆、文件加壳、动态方法、JIT 加密等保护技术&#xff0c;这些保护技术都有其各自的优缺点&#xff0c;虽然组合起来也能达到一定的效果&#xff0c;但近几年已经流传出一些脱壳机和…