函数与递归(包括例题)

目录

(1)函数引言: 首先提出一个问题,为什么要用到函数呢,把所有代码都写到主函数里不是更好吗?

(2)在了解了函数过后我们先来简单的看几个函数例题

(3)初识递归

(4)递归的例题


(1)函数引言: 首先提出一个问题,为什么要用到函数呢,把所有代码都写到主函数里不是更好吗?

那这个问题的结果肯定大家心知肚明,函数的创建是十分有必要的,首先把所有代码写到主函数里会使主函数变得庞杂,让我们对程序的阅读与维护变得困难,再其次有时候程序要多次实现某一功能,如果不去创建函数,就需要多次去编写实现此功能的代码,这会使程序变得冗长

那么此时就来讲解一下函数,函数其实就是一个功能,每一个函数用来实现一个特定的功能,在使用函数之前,我们班必须要去定义函数,如果不事先定义,编译系统怎么知道这个函数是什么,该去实现怎样的功能。

那么我们该怎么去定义一个函数呢?定义函数需要包括四个内容

1.定义函数名,以便于后续调用

2.定义函数的类型,确定函数返回值的类型

3.定义函数的参数名和类型,用于在调用函数时向他们传递数据。当然了假如你要创建的无参函数,则不需要进行这项

4.定义函数应当完成怎样的操作,也就是函数是干什么的————即函数的功能

(2)在了解了函数过后我们先来简单的看几个函数例题

首先先来看这个简单的例题

问题 1:定义一个函数add(a,b)=a+b,请输出该函数的的值。

    输入数据有多组。
    首先输入n,接下来输入n组add(a,b);保证a<10&&b<10.
    每组数据以回车键结束。

    输出函数对应顺序的值,每组数据占一行。

这题的思路很简单,首先创建一个函数add,在add函数中实现a+b的功能,然后再在主函数中调用add函数就可以解决问题了,那么请看代码

int add(int a,int b)//这里是自己创建的add函数,a,b是函数的参数
{
    int c=a+b;
    return c;//返回值要返回到c的值
}
int main()
{
    int n;
    scanf("%d",&n);
    int a,b,c;
    while(n--)
    {
        scanf("%d%d",&a,&b);
        c=add(a,b);
        printf("%d",c);
    }
    return 0;
}

这样我们就完成了创建一个变量去完成两个数之和的工程;

   接着上升一点难度看第二个题目

问题2:相信同学们对这题目已经很熟悉了,就不解释了,但是这道题目要用函数做哦!
规定要用到  int isLeap(int year){返回一个整型数据 },如果不使用以上函数则会出现编译错误!! 

输入一个年份   输入有多组

是闰年则输出yes,否则输出no

思路:首先我们需要了解到如何去判断是否是闰年,闰年的判断方法为能被4整除但不能被一百整除,或者能被400整除

此时我们就可以写出代码

#include
int isLeap(int year)//创建一个isleap函数
{
    if((year%4==0 && year%100!=0) || year%400==0)//判断是否是闰年
        return 1;//是闰年则返回值为1
    else
     return 0;//不是闰年则返回值为0
}

int main()
{
    int n;
    int t;//定义一个t,用于存储isLeap函数的返回值
    while(~scanf("%d", &n))
    {
        if(n==0) break;//如果n为0,则立刻跳出循环
        else t = isLeap(n);

        if(t==1)//判断isLeap返回值来确定是否是闰年
            printf("yes\n");
        else
            printf("no\n");
    }
    return 0;
}

接下来是一个比较难的调用函数的例题 

问题3:13号又是星期五是一个不寻常的日子吗? 13号在星期五比在其他日少吗?为了回答这个问题,写一个程序来计算在n年里13 日落在星期一,星期二......星期日的次数.这个测试从1900年1月1日到 1900+n-1年12月31日.n是一个非负数且不大于400. 这里有一些你要知道的: 1900年1月1日是星期一. 4,6,11和9月有30天.其他月份除了2月有31天.闰年2月有29天,平年2月有28天. 年份可以被4整除的为闰年(1992=4*498 所以 1992年是闰年,但是1990年不是闰年) 以上规则不适合于世纪年.可以被400整除的世纪年为闰年,否则为平年.所以,1700,1800,1900和2100年是平年,而2000年是闰年. 请不要预先算好数据!

输入一个整数n.

输出七个在一行且相分开的整数,它们代表13日是星期六,星期日,星期一.....星期五的次数.

思路:首先做这题我们要了解到一个知识点,名为蔡勒公式,这个公式可以求出某年某月某日到底是星期几

蔡勒(Zeller)公式,是一个计算星期的公式,随便给一个日期,就能用这个公式推算出是星期几。

1582年10月4日后:w = (d + 1+ 2m+3(m+1)/5+y+y/4-y/100+y/400)%7;

1582年10月4日前:w = (d+1+2m+3(m+1)/5+y+y/4+5) % 7;

w:星期; w对7取模得:0-星期日,1-星期一,2-星期二,3-星期三,4-星期四,5-星期五,6-星期六

c:世纪(注:一般情况下,在公式中取值为已经过的世纪数,也就是年份除以一百的结果,而非正在进行的世纪,也就是现在常用的年份除以一百加一;不过如果年份是公元前的年份且非整百数的话,c应该等于所在世纪的编号,如公元前253年,是公元前3世纪,c就等于-3)

y:年(一般情况下是后两位数,如果是公元前的年份且非整百数,y应该等于cMOD100+100)

m:月(m大于等于3,小于等于14,即在蔡勒公式中,某年的1、2月要看作上一年的13、14月来计算,比如2003年1月1日要看作2002年的13月1日来计算)

d:日

[ ]代表取整,即只要整数部分。


//使用蔡勒的同余算法来确定给定日期的星期几

#include 
int caile(int year, int month, int day)//这边直接拿蔡勒的名字来定义函数
{
    //如果月份等于1或2,会将月份加12,并将年份减1。
    //这是因为在一些日期计算的算法中,将1月和2月视为前一年的13月和14月。
    if (month == 1 || month == 2)
    {
        month += 12;
        year--;
    }
    int century = year / 100;//这边的世纪是已经过去的世纪比如说2016年,则此时的century为20
    year = year % 100;
    int week = year + year / 4 + century / 4 - 2 * century + 26 * (month + 1) / 10 + day - 1;
    week = (week % 7 + 7) % 7;
    //w对7取模得:0-星期日,1-星期一,2-星期二,3-星期三,4-星期四,5-星期五,6-星期六
    return week;//返回值为具体的星期几
}
int leapyear(int year)//判断是否是闰年,前面有例题讲过不过多阐述了
{
    if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
    {
        return 1;
    }
    else
    {
        return 0;
    }
}

int main()
{
    int n;//定义一个过去了多长时间的变量n
    scanf("%d", &n);//输入过去了多长时间

    int count[7] = {0};//定义了一个count数组,用于统计一周中每天计数
    for (int year = 1900; year < 1900 + n; year++)//循环,从1900年开始,一直到n年后结束
    {
        for (int month = 1; month <= 12; month++)
        {
            int day = 13;//将天数定义到13,用于蔡勒求余公式
            //(因为题干中要求的是统计每月13号为星期几)
            int week = caile(year, month, day);//调用caile函数
            count[week]++;//每月13好对应的周几自增
        }
    }
    printf("%d ",count[6]);//题目上要求先输出周六的

    for (int i = 0; i < 6; i++)
    {
        printf("%d ", count[i]);//通过循环将周天到周五的天数输出
    }

    return 0;
}

(3)初识递归

   递归的定义:程序调用自身的一种编程技巧

(1)该如何去理解函数递归?

1.在调用自身层面:函数递归就是函数自己调用自己的方法

2.在编程技巧方面,是一种大事化小的一种方法,将复杂的程序变为简单的程序

(2)递归的两个条件

1.存在限制条件,当满足这个限制条件的时候,递归就不再继续

2.每次递归调用后,会越来越接近限制条件

(3)递归的优点缺点

[1]优点:1.简洁性:用少量的代码就可以实现复杂的功能。相对于使用循环来处理的嵌套结构,                                     递归代码更加简洁,便于维护。

               2.可读性:可以增加代码的可读性,更直观地表示问题的解决方案,特别是涉及到嵌套                                     结构的问题。

               3.灵活性:递归可以应对未知深度的数据结构,因为其不需要提前知道要处理的嵌套层级

               4.大事化小:通过将问题划分为更小的问题,使复杂的问题的解决变得更加可行。

[2]缺点 1.效率的降低:递归是函数调用本身,而函数的调用需要时间以及空间的消耗,每一次调                                         用函数都是要在内存栈中分配空间以保存参数返回地址以及临时变量,                                         而且往栈里压入数据和弹出数据都需要时间,故效率要比循环低。

            2.可能会有重复计算:递归的本身就是将一个大的复杂的问题拆解成多个小问题进行解                                                       决,若多个小问题直接存在相互重叠的部分可能就会有重复计算 。

            3.栈溢出 :由于每一次函数调用都得在内存栈中分配空间,而每个进程的栈容量是有限                                     的。当递归调用的层级太多了,就会超出栈的容量,导致栈溢出。

(4)递归的例题

例题1:求n的阶乘(使用递归)

代码如下:

#include 
int factorial(int n)//创建一个用于计算阶乘的函数
{
    if(n==1)
        return 1;//当n的值为1时,返回值为1
    else
        return n*factorial(n-1);//当n的不为1时,要通过递归再次调用函数本身
}
int main()
{
    int n,sum;
    scanf("%d",&n);
    sum=factorial(n);//调用factorial函数,去求出n的阶乘
    printf("%d\n",sum);
    return 0;
}

例题2:表示裴波那契数列的第n项

(1 , 1 , 2 , 3 , 5 , 8 , 13 , 21 , 34 .........这样的数列被称之为裴波那契数列)

代码如下:

#include 
int fib(int n)//创建一个函数以实现求出第n项的大小
{
    if(n<=2)
        return 1;//代表裴波那契数列前两项为1
    else
        return fib(n-1)+fib(n-2);//当n大于3时,返回到前两项的和
}
int main()
{
    int n,a;//创建变量a用于存储裴波那契第n项的值
    scanf("%d",&n);
    a=fib(n);//调用函数
    printf("%d\n",a);
    return 0;
}

例题3:使用函数在不创建变量的情况下求出字符串长度(不能用strlen库函数)

代码如下

#include 
int mystrlen(char* str)//既然不能用strlen,那就创建一个自己的函数,定义为mystrlen
{
    if(*str=='\0')
        return 0;//当指针读取到字符串的\0之后就结束了
    else
        return 1+mystrlen(str+1);
//假如没有读取到字符串的\0,那么就返回值加1,并且指针加1,读取下一个字符
}
int main()
{
    char* p="abcd";
    int len=mystrlen(p);
    printf("%d\n",len);
    return 0;
}

最后来一个压轴例题:汉诺塔

相传在印度的贝纳雷斯有座大寺庙,寺庙内有一块红木板,上面插着三根钻石棒,A,B,C,在盘古开天地,世界刚创造不久之时,神便在其中的一根钻石棒上放了64枚大小不一的纯金圆盘。有一个叫婆罗门的门徒,不分日夜地向这座寺庙赶路,抵达后,就尽力将64枚纯金的圆盘从A移到C钻石棒上,要求相邻之间的圆盘,在上方的要比在下方的小,且每天只能移动一块圆盘。等到婆罗门完成这项工作,寺庙和婆罗门本身都崩溃了,世界在一声霹雳中也毁灭了。现在要求你输出金盘数在23以内的,需要移动的天数。

  输入n,表示n组测试数据,接下来输入n个数(均大于0小于等于23)。

 输出每组数据需要的天数,每组输出占一行。

4
11
13
8
14
2047天
8191天
255天
16383天

先分析一下思路,首先要实现这个功能要现将n-1个盘子从a借助c转移到b上,然后再将这n-1个盘子从b借助a转移到c上,每转移一次,则天数加一

这题略有难度,代码发在下面了 

#include 
void hannuota(char a, char b, char c, int n, int *count)
//汉诺塔的递归可以这么理解,将n个圆盘从a借助b,移到c,count,用于统计移动次数
{
    if (n == 1)
    {
        (*count)++;//增加移动次数
    }
    else
    {
        hannuota(a, c, b, n-1, count);//使用B作为辅助,将n-1张光盘从A移动到C
        (*count)++;
        hannuota(b, a, c, n-1, count);//使用C作为辅助,将n-1张光盘从B移动到A
    }
}

int main()
{
    int t=0;
    scanf("%d",&t);
    while(t--)
    {
        char a,b,c;
        int n=0;
        scanf("%d", &n);
        int count = 0;
        hannuota(a, b, c, n, &count);
        printf("%d天", count);
        printf("\n");
    }
    return 0;
}
(5)迭代与递归

在递归例题中,我们了解到了求裴波那契数列第n项的递归写法,那我们是不是也可以不用递归去完成呢?

这时我们应该会考虑到迭代,那么什么是迭代呢?

迭代:每一次对过程的重复称之为一次“迭代”

但是对于目前新手期的我来说,还是会简单的将迭代认为成循环结构

这个例子还算简单,就不过多阐述了,请看代码

#include 
int fib(int n)
{
	int a = 1;
	int b = 1;
	int c = 1;
	while (n > 2)
	{
		c = a + b;
		a = b;
		b = c;
		n--;
	}
	return c;
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	int ret = fib(n);
	printf("%d\n", ret);
	return 0;
}

但是通过自己的实践可知,迭代和递归得出的结果是相同的,但是迭代的运行速度更快

那我们应当思考一下递归和迭代的关系。

1.大多数问题我们还是要通过递归来写的,因为递归写出来的要比非递归的形式的写出来更加清晰

2.迭代有一个最大的好处就是处理效率高,运行时间短,唯一的缺陷就是可读性不高

3.当碰到一个庞杂的问题时,递归实现的简洁性就可以弥补它所带来的运行开销

你可能感兴趣的:(算法,数据结构)