递归程序设计之赶鸭子

本文围绕递归程序设计展开,通过两个具体题目,即鸭子售卖问题和角谷定理问题,分别给出递归和非递归算法的分析、构造与实现。还进行了调试、测试并展示运行结果,最后归纳出递归程序设计的方法,强调递归出口和递归体的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

作者:无*
时间:2019-5-27

递归程序设计

一.题目分析

题目一:

一个人赶着鸭子去每个村庄卖,每经过一个村子卖去所赶鸭子的一半又一只。这样他经过了七个村子后还剩两只鸭子,问他出发时共赶多少只鸭子?经过每个村子卖出多少只鸭子?

递归算法:递归出口(num==0,num表示的是村庄数);递归表达式(duck(2*(sum+1),num-1)sum表示剩余鸭子数);先调用duck(2,7);可以求出鸭子总数,再在函数体内实现递归调用,最后求出每经过一个村庄所卖鸭子数。
非递归算法:调用此函数,函数体最后返回鸭子总数,函数体内通过一个for循环表示每经过一个村庄所卖鸭子数,主要变量有:最后剩余的鸭子数,所赶的鸭子数及所卖的鸭子数。
**主要算法规律总结:
所卖鸭子数=所赶鸭子数/2+1;
所赶鸭子数-(所赶鸭子数/2+1)=2 --------第7个村子
题目二:

角谷定理。输入一个自然数,若为偶数,则把它除以2,若为奇数,则把它乘以3加1。经过如此有限次运算后,总可以得到自然数值1。求经过多少次可得到自然数1。

递归算法:首先利用if语句判断奇数偶数,再通过判断数字是否为1,如果不为1,进行递归调用,继续判断,直至结果为一。其中还要设置一个STEP变量记录次数。
非递归算法:
通过while循环实现非递归算法,最后输出每一步的结果,并且输出执行次数STEP;
**主要算法
奇数和偶数的判断以及要执行的语句
判断偶数(num%2==0);

二.算法构造

题目一:
递归算法:

递归出口(num==0), 递归函数体:所赶鸭子数=2*(sum+1),此时村庄数num-1

  如果满足递归出口条件则直接返回sum;否则进行递归调用。注意先利用一个函数调用计算出总的鸭子数,再利用一个for循环计算出所卖出的鸭子,本天所卖出的鸭子数=本天所赶鸭子数x/2+1;下一天的所赶鸭子数x=本天所赶鸭子数x/2-1;

非递归算法:
定义三个变量最后剩余的鸭子数remanent=2,所赶的鸭子数y=0及所卖的鸭子数sellnum=0;通过一个for循环计算出每一步的结果,for循环中的主要执行语句为:
所赶鸭子数y=2*(剩余鸭子数remanent+1);所卖鸭子数sellnum=所赶鸭子数y/2+1;
最后每一次还要更新剩余鸭子数remanent=y;(即上一天所剩余的鸭子数=这一天所赶的鸭子数);最后返回鸭子总数y.
题目二:
递归算法:

递归出口(num==1)
num=num/2(偶数) num=3*num+1(奇数);

  如果不满足递归出口则实现递归调用jiaogu(num,STEP);

非递归算法:
利用while语句实现循环,多次调用。while语句的条件时(num!=1)输出每一步的结果,函数最后的返回值为STEP;

三.算法实现

(详细完整代码另附)
主要代码部分:
题目一:
递归部分:

public static int duck(int sum,int num)  //sum表示鸭子数,num表示村庄数
	{ if(num==0){                          //递归出口
		return sum;}
	else{
		return duck(2*(sum+1),num-1);
	}
		}

非递归部分:

public static int duck2() 
	{ int remanent=2;//最后剩余的鸭子数
	  int sellnum=0;//卖出的鸭子数
	  int y=0;//所赶的鸭子数 
	  for(int j=1;j<8;j++)
	  {  y=2*(remanent+1);
	     sellnum=y/2+1;
	     remanent=y;
	     System.out.println("经过第"+(8-j)+"个村庄"+"卖出的鸭子数为"+sellnum);

	  }	
	  return y;
	}

题目二:
递归算法:

public static  void jiaogu(int num,int STEP)  
	{ 
		if(num%2==0) //num为偶数
		{ num=num/2;
		  System.out.println(num);}
		else if(num%2!=0&&num!=1)  //num为奇数
		 {num=3*num+1;
		  System.out.println(num);}
		STEP++;                  //STEP表示次数
		if(num!=1)
			jiaogu(num,STEP);
	    if(num==1)
	    	{
	    	 System.out.println("STEP="+STEP);
	    	}
			
	}

非递归算法:

public static  int jiaogu2(int num,int STEP)  
	{ while(num!=1)
	    {if(num%2==0) //num为偶数
	        { num=num/2;}
		else
		 {num=3*num+1; }
	      System.out.println(num);
	  	  STEP++;
	    }
	return STEP;
	}

四.调试,测试及运行结果

1.调试:
题目一:
在这里插入图片描述
题目2::
在这里插入图片描述
2.测试
题目一测试代码及截图:

public static void test1(int ducknum)
	{for(int i=1;i<8;i++)
		ducknum=ducknum/2-1;
	  if(ducknum==2)
		  System.out.println("代码正确,测试成功");
	  else
		  System.out.println("测试有误");
	}

在这里插入图片描述
题目二:
在这里插入图片描述
3.运行结果
分别利用switch语句写了每个题目的递归算法和非递归算法
题目一:
递归算法:
在这里插入图片描述
非递归算法:
在这里插入图片描述
题目二:
递归算法:
在这里插入图片描述
非递归算法:
在这里插入图片描述

五.经验归纳

通过本次的上机题目,掌握了递归程序设计的方法,明确了递归的概念,通过对不同题目的分析,找出了递归的出口和对应的递归体。同时针对一个问题,还能有不同的思考方式,能将递归与非递归进行相应的转化。了解到了递归模型由递归出口和递归体两部分组成,前者确定递归何时结束,后者确定递归的方式。递归的应用在有些情况下能让程序更简洁易懂,改善程序的可读性。
在本次上机实验中,针对两个题目分别应用了递归算法和非递归算法,最后用了switch语句方便进行比较。通过本次上机,我总结了以下的方法:在递归程序的设计过程中,首先要进行正确的题目分析(如:用数学方法推导出递归公式等),可以先将自己的算法设计用普通方法编写出来。其次进一步就是设计大致的结构:递归的出口还有递归体分别是什么,最后再进行整合。

<think>根据问题描述,赶鸭经过每个村庄出所赶鸭子一半一只经过7个村子后剩下2只鸭子。需要计算出发时的总鸭子数以及每个村子出的鸭子数量。这是一个递归问题。我们可以定义递归函数:-递归出口:当村子数为0时(即出发时),鸭子数为初始值(待求)。但题目要求从经过7个村子后剩余2只鸭子倒推,因此递归函数可以设计为:已知第n个村子之后的鸭子数,求第n个村子之前的鸭子数。-递归关系:设经过第n个村子后剩余鸭子数为x,那么在第n个村子鸭子之前,鸭子数为:x_before=2*(x+1)(因为出了一半一只,即x=x_before/2-1,所以x_before=2*(x+1))-然后,在第n个村子出的鸭子数为:x_before/2+1我们已知经过7个村子后剩余2只鸭子,即第7个村子之后剩余2只。注意:题目中“经过七个村子后还剩两只鸭子”,那么这七个村子完之后剩下2只。所以我们可以从第7个村子开始倒推,直到第0个村子(出发时)。递归函数设计:函数名:`sellduck(duck,village)`参数:duck表示当前村子之后的鸭子数,village表示当前村子(从7开始递减,直到0)返回值:返回初始鸭子总数(即经过0个村子时的鸭子数)但是,我们还需要记录每个村子出的鸭子数量,所以可以在递归过程中打印每个村子出情况。具体步骤:1.递归出口:当village=0时,说明已经倒推到出发时,直接返回鸭子数(此时鸭子数就是初始总数)。2.递归体:根据当前村子之后的鸭子数duck,计算当前村子之前的鸭子数:`before=(duck+1)*2`。3.然后,当前村子(第village个村子出的鸭子数:`sell=before//2+1`(注意:因为是整数,所以整除)。4.递归调用:将before作为下一个村子(即village-1)的鸭子数(即进入前一个村子时的鸭子数),直到village=0。注意:题目中村子编号是从1到7,我们倒推时,从7开始,到1为止(出发时就是0个村子之后,即还没有经过村子)。所以递归调用时,village从7递减到0。但是,在递归过程中,我们是从第7个村子之后(剩2只)开始,倒推第7个村子之前(即刚到达第7个村子时)的鸭子数,然后第6个村子之前,...,直到第1个村子之前(即出发时)。所以,递归函数可以这样写(用Python实现):```pythondefsellduck(duck,village):ifvillage==0:returnduckelse:#计算到达当前村子时的鸭子数(即上一个村子之后剩下的鸭子数,也就是当前村子还没有时的鸭子数)before=(duck+1)*2#当前村子出的鸭子数sell=before//2+1#因为一半一只,所以是before的一半加一#注意:这里before是整数,所以整除#递归调用,计算前一个村子之前(即当前村子之前)的鸭子数,并继续倒推total=sellduck(before,village-1)#在递归返回的过程中打印,这样打印顺序是从第一个村子到第七个村子#但注意:我们的递归是从7到0,打印可以在递归返回时做,这样顺序就是正序(从村子1到村子7)#或者我们可以在递归调用前打印,这样顺序是倒序(村子7到村子1)。为了按村子顺序(1到7)输出,我们在递归返回时打印,并记录村子编号#由于递归是递减的,我们可以在递归返回时打印,此时village的值是当前村子编号(从1到7)#但是,我们递归调用时village-1,所以在返回时,village的值是当前村子编号(比如当前是村子1,递归调用village-1=0,返回后打印村子1)#所以我们在递归调用后打印#注意:为了避免重复打印,我们只打印村子1到7,所以当village>=1时打印ifvillage>=1andvillage<=7:print(f"经过第{village}个村子出{sell}只鸭子,剩余{duck}只鸭子")returntotal```但是,这样设计递归函数,在递归返回时打印,打印顺序是从村子1到村子7(因为递归调用是先进后出)。但题目要求经过7个村子后剩2只,所以倒推时,我们首先得到的是第1个村子出情况,最后得到第7个村子出情况。然而,用户可能希望按经过村子的顺序(从第1个村子到第7个村子)显示出情况。另一种方法:在递归调用前打印,但是这样打印顺序是倒序(从第7个村子到第1个村子)。我们可以调整村子编号,但题目中村子编号是固定的(1到7)。我们可以将递归函数的village参数理解为剩余要处理的村子数。初始调用时village=7,表示从第7个村子开始倒推,此时鸭子数为2。然后计算第7个村子之前(即经过第6个村子后)的鸭子数,并打印第7个村子出情况。然后递归调用village=6,计算第6个村子之前,并打印第6个村子出情况,直到village=0。这样打印顺序就是先打印第7个村子,再打印第6个,...,最后打印第1个村子。但是题目要求按顺序(从1到7)显示每个村子出情况。所以我们可以将打印的信息存储在一个列表中,最后再按顺序输出。或者,我们可以先计算初始鸭子总数,然后再正着计算每个村子出情况(不用递归,用循环)。为了简单,我们可以先用递归计算初始鸭子总数,然后用循环模拟经过每个村子的过程,计算每个村子出的鸭子数。步骤:1.用递归计算初始鸭子总数(从第7个村子倒推到第0个村子)。2.然后,从初始鸭子总数开始,正着经过7个村子,计算每个村子出的鸭子数和剩余鸭子数。但是这样需要计算两次。我们可以在递归过程中收集每个村子出情况(用列表存储),然后最后按顺序输出。修改递归函数,返回初始鸭子总数,并返回一个列表,记录每个村子出情况(列表索引0对应村子1,索引6对应村子7)。由于递归是倒推,我们可以在递归过程中将每个村子出情况按村子编号存储。但是递归过程中,村子编号是从7递减到1,所以我们可以用列表的插入操作(在头部插入)来保证顺序。具体实现:使用一个全局列表,或者在递归函数中传递一个列表。这里我们使用在递归函数中传递列表的方式,并在递归过程中将每个村子出情况添加到列表中(注意:由于倒推,我们先得到的是第7个村子出情况,然后第6,...,第1。所以最后需要反转列表,或者我们每次插入到列表头部)。另一种方法:在递归函数中,我们按照村子编号(从1到7)存储,所以我们可以用字典或一个固定大小的列表。这里,我们选择在递归返回时,将当前村子出情况添加到结果列表中(这样添加的顺序是从村子1到村子7,因为递归返回时先返回村子1,最后返回村子7)。但是注意,我们的递归调用是先计算村子7,然后村子6,...,村子1。在递归返回时,是先返回村子1的结果,然后村子2,...,村子7。所以添加顺序是村子1,2,...,7,正好是正序。所以,我们可以这样:```pythondefsellduck(duck,village,result_list):ifvillage==0:returnduck,result_listelse:before=(duck+1)*2sell=before//2+1#递归调用,village-1total,result_list=sellduck(before,village-1,result_list)#递归返回时,当前村子是village(从1到7),将当前村子出情况加入结果列表#注意:此时village是当前村子编号(1到7)#我们只记录村子1到7ifvillage>=1andvillage<=7:result_list.append((village,sell,duck))#村子编号,出数量,剩余数量(即当前村子之后的鸭子数)returntotal,result_list```但是这样,在递归返回时,我们先添加村子1,然后村子2,...,最后村子7。所以结果列表是[村子1,村子2,...,村子7]的顺序。然后,我们调用递归函数时,village=7,duck=2,result_list=[]。最后返回的total就是初始鸭子总数,result_list就是按村子顺序(1到7)的出情况列表。然后,我们可以按顺序输出每个村子出情况。但是注意:在递归返回时,我们添加的剩余鸭子数duck就是当前村子之后的剩余鸭子数(即出后剩下的),所以对于村子1,剩余鸭子数就是村子1出后剩下的(即村子2之前的鸭子数),而村子7的剩余鸭子数就是2(题目给出)。因此,我们可以这样输出:-村子1:出数量,剩余数量(即村子2之前的鸭子数)-村子2:出数量,剩余数量(即村子3之前的鸭子数)-...-村子7:出数量,剩余数量(2只)但是,题目要求输出每个村子出的鸭子数量,以及出发时的总数。另外,注意:递归函数中,对于村子1,我们计算出的剩余鸭子数(即村子1出后剩下的)就是进入村子2之前的鸭子数,也就是经过村子1后剩下的鸭子数。所以,输出格式可以设计为:经过第1个村子出X只鸭子,剩余Y只鸭子经过第2个村子出X只鸭子,剩余Y只鸭子...经过第7个村子出X只鸭子,剩余Y只鸭子出发时共赶Z只鸭子。现在,我们写代码实现。注意:题目要求鸭子数是整数,所以整除是安全的,因为每次倒推的before都是偶数?验证:从2开始倒推:村子7:before=(2+1)*2=6,是偶数;村子6:before=(6+1)*2=14,是偶数;村子5:14+1=15,15*2=30,是偶数?不对,注意:倒推公式为:before=(duck+1)*2,其中duck是上一个村子之后的鸭子数,所以每一步的duck都是整数,而before也是整数。但是,在出时,出的数量是before//2+1,而剩余的是duck=before//2-1,所以每一步都是整数。我们先用递归计算初始鸭子总数,并记录每个村子出情况。主程序:-初始化:当前鸭子数=2(经过7个村子后剩余),村子数=7-调用递归函数,返回初始鸭子总数和结果列表-输出初始鸭子总数-按顺序(村子1到7)输出每个村子出情况但是,注意:递归函数中,我们添加的顺序已经是村子1到村子7,所以直接遍历结果列表即可。然而,在递归函数中,我们添加的顺序是村子1到村子7,所以列表顺序就是村子1到村子7。代码实现:```pythondefsellduck(duck,village,result_list):ifvillage==0:returnduck,result_listelse:before=(duck+1)*2sell=before//2+1total,result_list=sellduck(before,village-1,result_list)#当前村子编号:village(范围1-7)ifvillage<=7andvillage>=1:#将当前村子的信息添加到结果列表:村子编号,出数量,剩余鸭子数(即当前村子之后的鸭子数)result_list.append((village,sell,duck))returntotal,result_list#初始化duck_after_7=2villages=7result_list=[]#用于记录每个村子出情况initial_ducks,result_list=sellduck(duck_after_7,villages,result_list)#注意:递归函数返回的result_list是从村子1到村子7的顺序(因为递归返回时先添加村子1,最后添加村子7,所以列表是[村子1,村子2,...,村子7])#但是,我们添加的顺序是村子1(在递归最深处)先添加,然后村子2,...,村子7最后添加,所以列表是[村子1,村子2,...,村子7]。#但是,我们输出时希望从村子1到村子7,所以直接顺序输出即可。print(f"出发时共赶{initial_ducks}只鸭子。")#输出每个村子的情况#由于result_list中存储的是按村子1到7的顺序,我们直接遍历forvillage,sell,remaininginresult_list:print(f"经过第{village}个村子出{sell}只鸭子,剩余{remaining}只鸭子")```但是,注意:在递归过程中,对于村子1,剩余鸭子数remaining就是村子1出后的鸭子数(即进入村子2之前的鸭子数),对于村子2,remaining是村子2出后的鸭子数(即进入村子3之前的鸭子数)...村子7的remaining是2(即最后剩余)。所以,输出是合理的。但是,我们也可以验证一下:初始鸭子总数=initial_ducks,然后经过村子1出后,剩余=initial_ducks-(initial_ducks//2+1)=initial_ducks//2-1,这个值应该等于进入村子2之前的鸭子数(即村子1之后的鸭子数),而这个值在递归过程中被记录为村子1的remaining。我们可以用循环验证:用初始鸭子数,正着计算7个村子,看最后是否剩余2只。现在,我们运行程序,看看结果。根据引用[2]中的C程序,出发时共赶510只鸭子。我们可以验证一下。引用[2]中,递归函数:fun(1)=2*(fun(2)+1),fun(2)=2*(fun(3)+1),...fun(8)=2所以fun(8)=2,fun(7)=2*(2+1)=6,fun(6)=2*(6+1)=14,fun(5)=2*(14+1)=30,fun(4)=2*(30+1)=62,fun(3)=2*(62+1)=126,fun(2)=2*(126+1)=254,fun(1)=2*(254+1)=510。所以出发时510只鸭子。然后每个村子出的鸭子数:fun(1)/2+1=510/2+1=256,然后剩余510-256=254(即fun(2));第2个村子:254/2+1=128,剩余254-128=126(fun(3))...第7个村子:6/2+1=4,剩余2。所以输出应该为:出发时共赶510只鸭子经过第1个村子出256只鸭子,剩余254只鸭子经过第2个村子出128只鸭子,剩余126只鸭子经过第3个村子出64只鸭子,剩余62只鸭子经过第4个村子出32只鸭子,剩余30只鸭子经过第5个村子出16只鸭子,剩余14只鸭子经过第6个村子出8只鸭子,剩余6只鸭子经过第7个村子出4只鸭子,剩余2只鸭子因此,我们按照上述思路编写代码。但是,上面的递归函数中,我们传递了result_list用于收集结果,也可以不使用参数,而通过返回值来收集。但这里为了简单,我们使用参数。另外,我们也可以不使用全局列表,而是将列表作为递归返回值的一部分。修改一下:掉参数中的result_list,改为在递归函数内部创建列表。```pythondefsellduck(duck,village):ifvillage==0:returnduck,[]#返回鸭子数和空列表else:before=(duck+1)*2sell=before//2+1total,result_list=sellduck(before,village-1)#当前村子编号village(1-7)ifvillage<=7andvillage>=1:#将当前村子的信息添加到结果列表result_list.append((village,sell,duck))returntotal,result_list```这样调用:initial_ducks,result_list=sellduck(2,7)然后输出同上。我们选择这种方式。最后,输出结果。完整代码:</think>### 解决赶鸭鸭问题的递归方法 根据问题描述,赶鸭经过每个村庄出所赶鸭子一半一只经过7个村子后剩下2只鸭子。这是一个典型的递归问题,可以通过逆推方式求解初始鸭子数和每个村子出数量。 #### 递归函数设计 1. **递归关系**: - 设经过第n个村子后剩余鸭子数为$f(n)$ - 出前鸭子数:$f(n-1) = 2 \times (f(n) + 1)$ - 出数量:$sell = \frac{f(n-1)}{2} + 1$ 2. **递归出口**: - 当村子数为0时(即出发时),返回初始鸭子数 - 当村子数为7时,剩余鸭子数为2(给定条件) 3. **递归体**: - 根据当前剩余鸭子数计算前一个村子鸭子数 - 记录当前村子出数量 #### Python代码实现 ```python def duck_sell(remaining, village, result): """ 递归计算鸭子数量和出情况 :param remaining: 当前剩余鸭子数 :param village: 当前村子编号 :param result: 存储结果的列表 :return: 初始鸭子总数 """ if village == 0: # 递归出口:到达出发时 return remaining # 计算前一个村子鸭子数 previous = (remaining + 1) * 2 # 计算当前村子出的数量 sold = previous // 2 + 1 # 存储当前村子的结果(村子编号,出数量,剩余数量) result.append((village, sold, remaining)) # 递归调用:处理前一个村子 return duck_sell(previous, village - 1, result) # 主程序 if __name__ == "__main__": result_list = [] # 存储每个村子出情况 total_ducks = duck_sell(2, 7, result_list) # 从第7个村子开始逆推 # 输出结果 print(f"出发时共赶{total_ducks}只鸭子") # 按村子顺序(1→7)输出结果 for village, sold, remaining in reversed(result_list): print(f"经过第{village}个村子出{sold}只鸭子,剩余{remaining}只鸭子") ``` #### 执行结果 ``` 出发时共赶510只鸭子 经过第1个村子出256只鸭子,剩余254只鸭子 经过第2个村子出128只鸭子,剩余126只鸭子 经过第3个村子出64只鸭子,剩余62只鸭子 经过第4个村子出32只鸭子,剩余30只鸭子 经过第5个村子出16只鸭子,剩余14只鸭子 经过第6个村子出8只鸭子,剩余6只鸭子 经过第7个村子出4只鸭子,剩余2只鸭子 ``` #### 算法说明 1. **递归过程**: - 从第7个村子(剩余2只)开始逆推 - 通过$f(n-1) = 2 \times (f(n) + 1)$计算前一个村子鸭子数 - 递归深度为7层(村子7→村子1) 2. **时间复杂度**:$O(n)$,其中n为村子数量 3. **空间复杂度**:$O(n)$,递归调用栈空间 #### 数学验证 设初始鸭子数为$x$: - 第1村后:$x - (\frac{x}{2} + 1) = \frac{x}{2} - 1$ - 第2村后:$(\frac{x}{2}-1) - (\frac{\frac{x}{2}-1}{2} + 1) = \frac{x}{4} - \frac{3}{2}$ - ... - 第7村后:$\frac{x}{128} - \frac{127}{64} = 2$ 解得:$x = 510$,与程序结果一致[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值