函数的参数、返回、调用、递归

一、函数的参数

1.形式参数和实际参数

1.1形式参数

形参出现在被调函数当中,在整个函数体内都可以使用。形参在定义时编译系统并不分配存储空间,只有在调用该函数时才分配内存单元。调用结束内存单元被释放,故形参只有在函数调用时有效,调用结束时不能再使用。

1.2实际参数

实参出现在主调函数当中,当函数调用时,朱调函数把实参的值传送给被调函数的形参,从而实现函数间的数据传递。传递方式有两种:值传递和地址传递方式。

2.变量作为函数参数

当形参定义为变量时,实参可以是常量、变量和表达式,这种函数间的参数传递为值传递方式。值传递的特点是参数的“单向传递”;

int swap(int a,int b)
{
   int temp;
   temp=a;
   a=b;
   b=temp;
   return 0;
}  
int main (void){
		int a=3,b=4;
		swap(a,b);
	}

由于是值传递,单向传递,并不会改变a,b的值。

3.数组作为函数参数

3.1数组元素作为函数参数

数组元素又称为下标变量,它具有普通变量的一切性质,因此数组元素作为函数的实参进行数据传递是与普通变量没有任何区别,也是值传递

int swap(int a,int b)
{
   int temp;
   temp=a;
   a=b;
   b=temp;
   return 0;
}  
int main (void){
		int a[]={3,4};
		swap(a[0],b[0]);
	}


同样是值传递并不会改变a[0]的值。

3.2一维数组名作为函数参数

数组名是一个地址,是数组的首地址,因此用数组名作为函数的参数进行数据传递时,执行的是地址传递方式。所谓地址传递,顾名思义实参传递的不是数据本身,而是数据存在的地址。函数调用时,把数组名即数组的首地址作为实参传递给形参(必须是可接受地址的数组名或者指针变量),形参数组名取得首地址后就有了实在的数组,这时实质上实参和形参是同一个数组,指向同一段存储空间,实参的改变就是对形参的改变,所以传址方式可看成是数据进行了“双向传递”。

3.3数组指针,即数组元素的地址作为函数参数

由于数组元素的地址的本质仍然为地址,所以属于地址传递方式。

int swap(int *a,int *b)
{
   int temp;
   temp=*a;
   *a=*b;
   *b=temp;
   return 0;
}  
int main (void){
		int arr[] = {1,2};
		int *a = &arr[0];
	    	int *b = &arr[1];
			swap(a,b);
	}


重点:

  • 数组元素(下标变量)作为函数的参数进行的数据传递是值传递方式,数组名(数组首地址)、数组元素的地址(&arr[0])作为函数参数进行的数据传递是地址传递方式。
  • 实参为数组名是,形参接收时可以有三种形式:带下标的数组名(arr[0])。不带下标的数组名(arr)、可接收地址值的指针变量名(*a)。由于是参数组和形参数组都指向同一段内存单元,故它们操作的是同一批数据,所以形参的改变就是改变了实参中的数据。

局部变量

是什么?首先是一个变量,其次,这个变量只是在程序的局部范围内有效;
局部变量定义在那些位置:
1. 函数的开头;
2. 函数内的复合语句内定义;
3. 形式参数;
4. 函数中间(非开头);

#include <stdio.h>
void main()
{
}
void fun1(char local_0)  //形式参数:只在此函数有效;
{
    char Local_1 = 'a';  //函数的开头:在本句以下的函数内有效;
    {
        char local_2 = 'b';  //函数的复合语句内定义:只能在本复合语句内切本行以下有效;
    }
    char Local_3 = 'c';  //函数中间(非开头):只能在本行以下有效;
}

注意:
1)程序执行到某个函数时,这个函数内部的局部变量将会被分配内存空间;局部变量在函数执行结束后,变量所占内存将会被释放;

全局变量

是什么?是变量,可以在全局范围内有意义的变量;所谓全局也并不是真正的全局,而是在定义处以下的范围内才是有效的;
全局变量定义的位置:
1. 文件开头;
2. 函数前;
3. 函数后;
4. 文件结尾;
举例:

#include <stdio.h>

char Global_1 = 'A';  //源文件开头:在此行以下的所有定义的函数都有效;
void main()
{
}

char Global_2='B';  //函数后和函数前:此全局变量在此行之前是无效,只能在此之下的函数中有效;

void fun1()
{
    char Local_1 = 'a';
    {
        char local_2 = 'b';
    }
    char Local_3 = 'c';
}

char Global_3 = 'B';//程序结尾:程序结尾的全局变量是没有意义的;

注意:
1)为了区别全局变量和局部变量,往往大家在写程序的时候都喜欢将全局变量的首字母大写,而局部变量的首字母小写;
2)全局变量的优点和缺点:
优点:C语言的函数,每次最多只能返回一个值,但是如果定义了全局变量,那么在这个变量的有效范围内,很多函数都能改变这个变量的值,所以增加了函数之间的联系,通过函数的调用可以得到一个或一个以上的值;
缺点:(大量使用全局变量的情况下)
1)占内存:全局变量所占的内存空间不会像局部变量一样会被释放;
2)降低程序清晰性:无法随时确定定义的全局变量的值的大小;
3)降低通用性:程序设计时要求函数的“内聚性”强,函数与函数之间“耦合性”弱;定义全局变是一定要注意在有效范围内变量不能重名,并且当全局变量被跨文件调用的函数调用时,不能出现全局变量与所跨文件中存在重名变量,否则有可能会出错;所以,为了提高程序的可靠性,可移植性和可读性等,全局变量尽量少用;

二、函数的返回

函数的返回值是指函数被调用之后,执行函数体中的代码所得到的结果,这个结果通过 return 语句返回。
return 语句的一般形式为:

return 表达式;

或者:

return (表达式);

有没有( )都是正确的,为了简明,一般也不写( )。例如:

return max;
return a+b;
return (100+200);

对C语言返回值的说明:

  1. 没有返回值的函数为空类型,用void表示。例如:
void func(){
    printf("http://c.biancheng.net\n");
}

一旦函数的返回值类型被定义为 void,就不能再接收它的值了。例如,下面的语句是错误的:

int a = func();

为了使程序有良好的可读性并减少出错, 凡不要求返回值的函数都应定义为 void 类型。

  1. return 语句可以有多个,可以出现在函数体的任意位置,但是每次调用函数只能有一个 return 语句被执行,所以只有一个返回值(少数的编程语言支持多个返回值,例如Go语言)。例如:
//返回两个整数中较大的一个
int max(int a, int b){
    if(a > b){
        return a;
    }else{
        return b;
    }
}

如果a>b成立,就执行return a,return b不会执行;如果不成立,就执行return b,return a不会执行。

  1. 函数一旦遇到 return 语句就立即返回,后面的所有语句都不会被执行到了。从这个角度看,return 语句还有强制结束函数执行的作用。例如:
//返回两个整数中较大的一个
int max(int a, int b){
    return (a>b) ? a : b;
    printf("Function is performed\n");
}

第 4 行代码就是多余的,永远没有执行的机会。
下面我们定义了一个判断素数的函数,这个例子更加实用:

#include <stdio.h>

int prime(int n){
    int is_prime = 1, i;

    //n一旦小于0就不符合条件,就没必要执行后面的代码了,所以提前结束函数
    if(n < 0){ return -1; }

    for(i=2; i<n; i++){
        if(n % i == 0){
            is_prime = 0;
            break;
        }
    }

    return is_prime;
}

int main(){
    int num, is_prime;
    scanf("%d", &num);

    is_prime = prime(num);
    if(is_prime < 0){
        printf("%d is a illegal number.\n", num);
    }else if(is_prime > 0){
        printf("%d is a prime number.\n", num);
    }else{
        printf("%d is not a prime number.\n", num);
    }

    return 0;
}

prime() 是一个用来求素数的函数。素数是自然数,它的值大于等于零,一旦传递给 prime() 的值小于零就没有意义了,就无法判断是否是素数了,所以一旦检测到参数 n 的值小于 0,就使用 return 语句提前结束函数。

return 语句是提前结束函数的唯一办法。return 后面可以跟一份数据,表示将这份数据返回到函数外面;return 后面也可以不跟任何数据,表示什么也不返回,仅仅用来结束函数。

更改上面的代码,使得 return 后面不跟任何数据:

#include <stdio.h>

void prime(int n){
    int is_prime = 1, i;

    if(n < 0){
        printf("%d is a illegal number.\n", n);
        return;  //return后面不带任何数据
    }

    for(i=2; i<n; i++){
        if(n % i == 0){
            is_prime = 0;
            break;
        }
    }

    if(is_prime > 0){
        printf("%d is a prime number.\n", n);
    }else{
        printf("%d is not a prime number.\n", n);
    }
}

int main(){
    int num;
    scanf("%d", &num);
    prime(num);

    return 0;
}

prime() 的返回值是 void,return 后面不能带任何数据,直接写分号即可。

三、函数的调用

一,函数的定义

一般来说,执行源程序就是执行主函数main,其他函数只能被主函数所调用,而其他函数之间也可以相互调用。

1.标准库函数:

分为:I/O函数,字符串,字符处理函数,数学函数,接口函数,时间转换和操作函数,动态地址分配函数,目录函数,过程控制函数,字符屏幕和图形功能函数。

这些库函数在不同的头文件中声明。比如:

math.h头文件中有:sin(x),cos(x),exp(x)(求e^x),fabs(x)(求x的绝对值)等库函数。

stdio.h头文件中有:scanf(),printf(),gets(),puts(),getchar(),putchar()等库函数。

string.h头文件中有:strcmp(),strcpy(),strcat(),strlen等库函数。

2.函数的定义:

(1)定义无参函数:

类型名  函数名()

{

函数体

}

or

类型名  函数名(void)

{

函数体

}

(2)定义有参函数

类型名 函数名(形式参数表列)

{

函数体

}

(3)定义空函数

类型名 函数名()

{    }

二,函数的调用

1.函数调用时的参数传递

函数间通过参数来传递数据,即通过主调函数中的实际参数(实参)向被调用函数中的形式参数(形参)进行传递。

实参向形参传递数据的方式:实参将值单向传递给形参,形参的变化不影响实参值。

for example:

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int a, b;
	void swap(int a, int b);		//函数声明
	scanf("%d%d", &a, &b);		//键盘输入
	swap(a, b);		//函数调用
	printf("最终的a,b值:\n  a=%d b=%d\n", a, b);
	system("pause");
	return 0;
}
void swap(int a, int b)
{
	int t;
	if (a < b)
	{
		t = a;
		a = b;
		b = t;		//a中放大值,b中放小值
	}
	printf("自定义函数的a,b值:\n  a=%d b=%d\n", a, b);
}

运行结果为:

分析:形参交换了数据,而实参保持原数据不变。这是单向的值传递,所以形参的值改变后而实参的值没有改变。

  • 形参在函数中是变量名,在函数调用时,形参被临时分配相应的内存,调用结束后,形参单元被释放,而实参单元保留并维持原值。
  • 实参是表达式,负责向对应的形参标识的内存单元传递数据,实参向形参的数据传递是“值传递”。
  • 实参与形参必须个数相同
  • 对应的形参和实参的类型必须一致

2.函数的返回值

(1)函数的返回值通过函数中的return语句获得。如果需要从调用函数带回一个函数值(供主函数使用),被调函数中需包含return语句。

for example:

int max(int x,int y)
{
    return(x>y?x:y);
}

(2)在定义函数时要指定函数值的类型

int max(float x,float y)            //函数值为整型
char letter(char c1,char c2)            //函数值为字符型
double max(int x,int y)            //函数值为双精度型

(3)函数类型决定返回值的类型。

3.函数的嵌套

定义函数时不能定义另一个函数,但是可以进行嵌套调用函数。

for example:

*用函数嵌套找出4个数中的最大值。

#include<stdio.h>
#include<stdlib.h>
int max2(int a, int b)			//找出a和b中的最大值
{
	if (a >= b)
	{
		return a;			//	a作为返回值
	}
	return b;				//	b作为返回值
}
int max4(int a, int b, int c, int d)	//定义找4个数最大值的函数
{
	int m;		//存最大值
	m = max2(a, b);		//调用max2函数,将a,b中大的值放在m中
	m = max2(m, c);		//调用max2函数,将a,b,c中大的值放在m中
	m = max2(m, d);		//调用max2函数,将a,b,c,d中大的值放在m中
	return m;		//返回到主函数
}
int main()
{
	int a, b, c, d;
	int max;
	printf("请输入四个数:\n");
	scanf("%d%d%d%d", &a, &b, &c, &d);
	max = max4(a, b, c, d);			//调用max4函数
	printf("最大数位:%d\n", max);
	system("pause");
	return 0;
}

三,函数的递归

1.递归概念:

函数的递归调用是指:一个函数在他的函数体内直接或间接地调用它自身。分为:直接递归(函数直接调用自身)和间接递归(函数通过其他函数调用自身)。可分为“回溯”和“递推”两个阶段。

2.递归函数的一般形式:

反值类型   递归函数名(参数说明表)

{

if(递归终止条件)

返回值=递归终止值;

else

返回值=递归调用(...)的表达式;

return 返回值;

}

3.递归函数举例:

*用递归求n的阶乘。

#include<stdio.h>
#include<stdlib.h>
int fac(int n)		//定义函数
{
	int f;
	if (n < 0)
		printf("数据错误\n");
	else if (n == 0 || n == 1)
		f = 1;
	else
		f = n* fac(n - 1);			//n!=n*(n-1)
	return f;			//返回主函数
}
int main()
{
	int n, y;
	printf("请输入n的值\n");
	scanf("%d", &n);
	y = fac(n);		//这里n为实参
	printf("%d!=%d\n", n, y);
	system("pause");
	return 0;
}

汉诺塔问题为经典的递归调用实例。代码如下:

#include<stdio.h>
#include<stdlib.h>
void move(char x, char y)	//输出移盘方案
{
	printf("%c->%c\n", x, y);
}
void hanoi(int n, char one, char two, char three)		//移盘
{
	if (n == 1)
		move(one, three);		//如果是1个盘,直接从第一个座移到第3个座上
	else
	{
		hanoi(n - 1, one, three, two);		
		move(one, three);
		hanoi(n - 1, two, one, three);
	}
}
int main()
{
	int n;
	printf("输入盘的个数\n");
	scanf("%d", &n);
	printf("移盘的步骤:\n");
	hanoi(n, 'A', 'B', 'C');
	system("pause");
	return 0;

分析:将n个盘子从A座上移到C座上需要三步:

(1)将A上的n-1个盘子借助C座先移到B座上;

(2)把A座上剩下的一个盘子移到C座上;

(3)将n-1个盘从B座上借助于A移到C座上。

函数的调用,一般是对同一个源文件中的其他函数进行调用的,也可以对另外一个源文件中的函数进行调用
C语言中,根据函数能否被其他源文件调用,分为内部函数和外部函数
外部函数,可以被其他源文件调用的函数
内部函数,只在定义的文件中有效

外部函数

开发大型项目,可能包含很多源文件来分别实现,最终,再整合在一起,有时,一个源文件中,需要调用其他源文件中的函数
调用外部函数之前,需要在当前源文件中定义外部函数
定义外部函数的方式,在函数的返回值类型前面添加extern关键字
示例代码
extern int add(int x,int y);
编译器,通过extern关键字会知道,add()函数是定义在其他文件中的外部函数
示例代码

第一个源文件
int add(int x,int y)
{
return x+y;
}
第二个源文件
#include <stdio.h>
extern int add(int x,int y);
void main()
{        
printf("sum=%d\n",add(1,2));
}

运行结果
这里写图片描述

C语言中
定义外部函数时,可以省略关键字extern
修改如下
int add(int x,int y);

由函数的返回类型、函数名和参数列表组成,这类格式的代码被称为函数原型
当代码中包含函数原型时,可能有两种情况
1、程序员希望编译器自动从其他文件中,查找该函数的定义
2、程序员先定义未实现的空函数,然后,在其他文件中具体实现

注意
声明外部函数时,无论有没有关键字extern,外部函数与原函数定义的返回值类型、函数名称和参数列表必须一致

内部函数

外部函数,只要声明一个函数原型,就可以调用其他源文件中的函数,但是,当多人开发时,可能出现函数重名的情况,不同源文件中的同名函数会相互干扰
此时,就需要一些特殊函数,只在定义的文件中有效,这类函数称为内部函数

定义内部函数
在定义内部函数时,需要在函数的返回值类型前面添加static关键字,也称静态函数
示例代码

第一个文件
#include<stdio.h>
void show()
{
printf("%s \n","first.c");
}
第二个文件
#include<stdio.h>
static void show()
{
printf("%s \n","second.c");
}
void main()
{        
show();
}

运行结果
这里写图片描述

如果,将第二个文件中的static去
运行程序会报错

四、函数的递归

函数递归的定义和优缺点

程序调用自身的行为就是递归。可以直接或间接的调用,本质是把复杂的问题转化为一个规模小的问题。递归一般只需少量的代码就可描绘出多次重复计算。其主要思考方式在于大事化小

优点是为具有某些特征的编程问题提供了最简单的策略,缺点是层层调用,算法的复杂度可能过高,以致于快速耗干了计算机的内存资源,不方便阅读和维护等。

递归的使用场景及必要条件

使用场景

  1. 能够要求转化为新的问题,且二者解决方法相同,所处理的对象存在规律变化。
  2. 非递归比较麻烦,而递归很简单。
  3. 有模板或是公式可以直接套用,不会出现明显问题。

必要条件

  • 明确存在限制条件
  • 每次递归越来越逼近条件

递归的细节说明

  • 每级递归都有自己的变量,可能名称相同,但是其值不同。

    递归调用时,系统自动保留当前函数的参数变量。每次调用系统都会为函数开辟相应的空间。

  • 每次调用都要返回值,递归执行结束后,控制权传回到上一级函数。

    调用结束后,系统释放本次调用所开辟的空间,程序返回到上一次的调用点,同时获得初进该级调用的参数。

    每级递归必须逐级返回,不可跳跃或间断。

  • 函数中递归语句之前的代码,按被调函数的顺序执行,递归之后的代码,与被调函数相反的顺序执行。

递归的习题讲解

1打印整数每一位

用递归的方式,实现打印一个整数的每一位的功能。

输入输出示例

输入:1234

输出:1 2 3 4

解题思路

print(1234)
= = = print(123)+4
= = = print(12)+3+4
= = = print(1)+2+3+4
= = = printf(1)+2+3+4

这便是前面使用场景中所写的,将题目要求问题转化为新的问题,且变量有规律的变化

代码逻辑

n是不是个位数,递推调用n / 10

n是个位数,回归打印n % 10

void Print(int n) 
{
	if (n > 9)
	{
		Print(n / 10);
	}
	printf("%d ", n%10);
}
int main()
{
	int num = 0;
	scanf("%d", &num);
	Print(num);	
	return 0;
}

2递归和非递归求n阶乘

用递归和非递归的方法,分别实现求n的阶乘的功能(不考虑溢出)。

输入输出示例

输入:5

输出:120

解题思路

n ∗ n − 1 ∗ n − 2 ∗ n − 3 ∗ … ∗ 1 n*n-1*n-2*n-3*…*1 n∗n−1∗n−2∗n−3∗…∗1

代码逻辑

f a c ( n ) = n ∗ f a c ( n − 1 ) , n > 0 fac(n) = n * fac(n-1) , n>0 fac(n)=n∗fac(n−1),n>0

f a c ( n ) = 1 , n = 0 fac(n) = 1 , n=0 fac(n)=1,n=0

int fac(int n)//非递归
{
	int ret = 1;
	for (int i = 1; i <= n; i++)
	{
		ret *= i;
	}
	return ret;
}
int fac(int n)//递归
{
	if (n > 0)
		return n * fac2(n - 1);
	else
		return 1;
}
int main()
{
	int n = 0;
	scanf("%d", &n);	
	printf("%d\n", fac(n));
	return 0;
}

3strlen函数模拟

输入输出示例

输入:abcdef

输出:6

解题思路

strlen(abcdef\0)
1+strlen(bcdef\0)
1+1+strlen(cdef\0)
1+1+1+strlen(def\0)
1+1+1+1+strlen(ef\0)
1+1+1+1+1+strlen(f\0)
1+1+1+1+1+1+strlen(\0)

代码逻辑

若 ∗ c h ≠ 0 , s t r l e n ( a r r ) = 1 + s t r l e n ( a r r + 1 ) 若 *ch≠0 , strlen(arr) = 1 + strlen(arr+1) 若∗ch​=0,strlen(arr)=1+strlen(arr+1)
若 ∗ c h = 0 , s t r l e n ( a r r ) = 0 若*ch=0 , strlen(arr) = 0 若∗ch=0,strlen(arr)=0

my_strlen求字符串长度函数解析

int my_strlen(char* ch)
{
	if (*ch != '\0')
	{
		return 1 + my_strlen(ch + 1);
	}
	return 0;
}
int main()
{
	char ch[20] = { 0 };
	scanf("%s", &ch);
	printf("%d", my_strlen(ch));
	return 0;
}

4逆序字符串

不开辟额外空间的情况下,不使用字符串库函数,递归实现字符串反向排列,而不是倒序打印。

输入输出示例

输入:abcdef

输出:fedcba

解题思路

abcdef

递推:(先把后面赋值给前面,后面用覆盖\0)

$ \Rightarrow$ f b c d e \0

⇒ \Rightarrow ⇒ f e c \0\0

⇒ \Rightarrow ⇒ f e d \0\0\0

回归:(把前面转移出去的字符对应赋值给\0)

$ \Rightarrow$ f e d c \0\0

⇒ \Rightarrow ⇒ f e d c b \0

⇒ \Rightarrow ⇒ f e d c b a

递归逆序字符串图示

代码逻辑

reverse("abcdef\0") 交换a和f+reverse("f bcde\0\0") 交换a和f+交换b和e+reverse("fe cd\0\0\0") 交换a和f+交换b和e+交换c和d+reverse("fed \0\0\0\0")

  • 交换两个字符
    1. 将在前的字符先放到一边存着
    2. 把在后的字符赋值到前面的位置
    3. 再把后面的位置对应覆盖为\0
  • 原在前字符替换\0
    1. 把事先存好的在前的字符对应替换到\0的位置上

递归逆序字符串代码详细解析

void reserve_string1(char* ch)//指针
{
	char* left = ch;
	char* right = ch + strlen(ch) - 1;
	while (left < right)
	{
		char tmp = *left;//不能交换地址,只能交换内容
		*left = *right;
		*right = tmp;
		left++;
		right--;
	}
}
void reserve_string2(char* ch)//数组
{
	int left = 0;
	int right = strlen(ch) - 1;
	while (left < right)
	{
		char tmp = ch[right];
		ch[right] = ch[left];
		ch[left] = tmp;
		left++;
		right--;
	}
}

void reverse_string3(char* ch)//递归
{
	char* left = ch;
	char* right = ch + strlen(ch) - 1;

	if (*ch != '\0')
	{
		char tmp = *left;//提取
		*left = *right;//赋值
		*right = '\0';//赋\0

		reverse_string3(ch+1);//ch+1,而不是ch++

		*right = tmp;//赋值
	}
}
int main()
{
	char ch[20] = "abcdef";
	//char* ch = "abcdef";//err - 常量字符串不可修改
	reverse_string3(ch);
	printf("%s\n", ch);

	return 0;
}

5递归实现数字各位之和

写一个递归函数DigitSum(),输入一个非负整数,返回组成它的数字之和

输入输出示例

输入:1234

输出:10

解题思路

1234
DigitSum(123)+4
DigitSum(12)+3+4
DigitSum(1)+2+3+4

1+2+3+4

1234%10=4
1234/10=123

123%10=3
123/10=12

12%10=2
12/10=1

1%10=1
1/10=0

一个数模10得到尾数,除10得到尾数前面的数字

通过不断的除10模10,就可以把每一位数字放到末尾,从而得到每一位数字

代码逻辑

若n不为个位数,先%10得到尾数,再/10

一定要有递归的出口,即当n为个位数时,函数返回n



int DigitSum(int n)
{
	if (n > 9)
		return DigitSum(n / 10) + n % 10;
	else
		return n;//递归的出口
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	printf("%d\n", DigitSum(n));

	return 0;
}

6求n的k次幂

输入两个整数分别代表底数和次幂,递归实现n的k次幂的功能。

输入输出示例

输入:2 3

输出:8

解题思路

k>0时,函数返回n*power(n,k-1)

k=0时,函数返回1,这是程序的出口,是程序递归到最后必须要计算的值

代码逻辑

n k = n ∗ n k − 1 , k > 0 n^k = n * n^{k-1} ,k > 0 nk=n∗nk−1,k>0
n k = 1 , k = 0 n^k = 1 , k = 0 nk=1,k=0

double power(int n,int k)
{
	if (k > 0)
		return n * power(n, k - 1);
	else if (k == 0)
		return 1.0;//递归的出口k=0
	else
		return 1.0 / power(n, -k);
}
int main()
{
	int n = 0;
	int k = 0;
	scanf("%d%d", &n, &k);
	printf("%lf\n", power(n, k));
    return 0;
}

7递归求斐波那契数列

递归和非递归分别实现求第n个斐波那契数

输入输出示例

输入:5

输出:5

解题思路

1 1 2 3 5 8 13 21 34 55 89 . . . 1\quad 1\quad 2\quad 3\quad 5\quad 8\quad 13\quad 21\quad 34\quad 55\quad 89\quad ... 1123581321345589...

代码逻辑

递归:

F i b ( n ) = F i b ( n − 1 ) + F i b ( n − 2 ) , n > 2 Fib(n) = Fib(n-1) + Fib(n-2) , n>2 Fib(n)=Fib(n−1)+Fib(n−2),n>2
F i b ( 1 ) = F i b ( 2 ) = 1 Fib(1) = Fib(2) = 1 Fib(1)=Fib(2)=1

非递归:

上一次的b换成这一次的a

上一次的c换成这一次的b

如此循环,就可以从前往后一个一个求。

非递归求斐波那契数列示例

int Fib(int n)
{
	if (n > 2)
		return Fib(n - 1) + Fib(n - 2);
	else
		return 1;
}

但是这个方法效率是非常低的,当数字特别大时,层层拆分下来,时间效率是 O ( 2 n ) O(2^n) O(2n)。

根据公式可知,第三个斐波那契数可由前两个得到,我们利用这个规律

int Fib(int n)
{
	if (n <= 2)
		return 1;
	int a = 1;
	int b = 1;
	int c = 1;//n=3时不用运算
	while (n >= 3)//从头开始移动n-2次,n=3时不用
	{
        c = a + b;
		a = b;//b赋值给a
		b = c;//c赋值给b		
		n--;
	}
	return c;
}

int main()
{
	int n = 0;
	scanf("%d", &n);
	printf("%d",Fib(n));
    
	return 0;
}

经典问题

汉诺塔问题

汉诺塔,小时候游戏机上经常看别人玩的,自己玩到三四局就玩不下去了的那款游戏。当然如果你觉得非常简单,小时候能玩的行云流水,那你有本事到我面前说,礼貌谢谢(狗头保命)。

游戏规则

有三根柱子,分别为A、B、C ,A柱上从上到下依次排列着由小到大的圆盘,我们需要把圆盘从A柱按照同样的摆放顺序放到C柱上,期间我们可以借助B柱。

  • 每次只能挪动一个且是最上面的圆盘
  • 按照从上到下依次是由小到大的顺序摆放。

解题思路

假设由N个盘子,只需要考虑第 N N N个盘子和其上 N − 1 N-1 N−1个盘子的整体。显然思路就是,第 N N N个是要放在 C C C柱上的,

  1. 首先将 N − 1 N-1 N−1个整体是先放在 B B B柱上;
  2. 其次将第 N N N个放在 C C C柱上;
  3. 最后将 N − 1 N-1 N−1个整体放到 C C C柱上。

即:第 N N N个 A → B A\rightarrow B A→B, N − 1 N-1 N−1个整体 A → B → C A\rightarrow B\rightarrow C A→B→C 。然后再考虑 N − 1 N-1 N−1个中把第 N − 1 N-1 N−1个当作最后一个,其上 N − 2 N-2 N−2个当作整体,到最后只剩一个直接放到 C C C柱上。这便是递归的整体思路。

在这里插入图片描述

void move(int n, int x, int z)
{
	printf("%d盘:%c->%c\n", n, x, z);
}
void hannoi(int n, char x, char y, char z)
{
	if (n == 1)
		move(n, x, z);
	else
	{
		hannoi(n - 1, x, z, y);
		move(n, x, z);
		hannoi(n - 1, y, x, z);
	}
}
int main()
{
	int input = 0;
	do 
	{
		printf("输入盘数开始测试(0. 退出测试)\n");
		scanf("%d", &input);
		switch (input)
		{
		case 0:
			break;
		default:
			hannoi(input, 'A', 'B', 'C');
			break;
		}
	} while (input);
	return 0;
}

青蛙跳台阶

游戏规则

​ 初阶版本

​ 青蛙一次可以跳一级台阶,也可以跳两级台阶。求该青蛙跳n级台阶共有多少种跳法?

​ 进阶版本

​ 青蛙一次可以跳一级台阶,也可以跳两级台阶,……,也可以跳n级台阶,求该青蛙跳上n级台阶的跳法种数。

青蛙跳台阶思维示例

解题思路

我们反向思考,当青蛙跳到最高阶 N N N阶时,他是怎么跳到第 N N N阶的呢?

有两种情况,

  • 从第 N − 1 N-1 N−1阶,跳到第 N N N阶,最后一次跳一阶。
  • 从第 N − 2 N-2 N−2阶,跳到第 N N N阶,最后一次跳两阶。

图中用灰框框出的部分,是最后一次跳一阶的,其余的是最后一次跳两阶的。

很显然,除了这两种情况,别无他法。所以计算青蛙

跳到 N N N阶的方法数 = = = 跳 N − 1 N-1 N−1阶的方法数 + + + 跳 N − 2 N-2 N−2 阶的方法数。

同样,图中用灰框框出的部分,也代表的是跳 N − 1 N-1 N−1阶的方法数,其余的是跳 N − 2 N-2 N−2 阶的方法数。

这其实就是斐波那契数列。

int fib(int n)
{
	if (n > 1)
		return fib(n - 1) + fib(n - 2);
	else
		return 1;
}

 

  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值