算法设计与分析----递归算法

一、递归算法

1、定义

​ 在定义一个过程或函数时出现调用本过程或本函数的成分,称之为递归。若调用自身,称之为直接递归。若过程或函数p调用过程或函数q,而q又调用p,称之为间接递归

​ 任何间接递归都可以等价地转换为直接递归

如果一个递归过程或递归函数中递归调用语句是最后一条执行语句,则称这种递归调用为尾递归

2、用递归解决的问题应该满足以下三个条件:

  • 需要解决的问题可以转化为一个或多个子问题来求解,而这些子问题的求解方法与原问题完全相同,只是在数量规模上不同。
  • 递归调用的次数必须是有限的。
  • 必须有结束递归的条件来终止递归。

3何时使用递归

  • 定义是递归的
  • 数据结构是递归的
  • 问题的求解方法是递归的

4、递归模型

递归模型是递归算法的抽象,它反映一个递归问题的递归结构。

一般地,一个递归模型是由递归出口递归体两部分组成,前者确定递归到何时结束,后者确定递归求解时的递推关系。

递归的执行过程

  • 递推

  • 递归

5、递归算法设计示例

【问题描述】对于给定的含有n个元素的数组a,分别采用简单选择排序和冒泡排序方法对其按元素值递增排序。

  1. 简单选择排序

​ 设f(a,n,i)用于对a[i…n-1]元素序列(共n-i个元素)进行简单选择排序,是“大问题”, f(a,n,i+1)用于对a[i+1…n-1]元素序列(共n-i-1个元素)进行简单选择排序,是“小问题”。

​ 当i=n-1时所有元素有序,算法结束。

分析如下

f(a,n,i) = 不做任何事情,算法结束			当i=n-1
f(a,n,i) = 通过简单比较挑选a[i..n-1]中的最
		       小元素a[k]放在a[i]处;		否则
	           f(a,n,i+1);

代码如下

void SelectSort(int a[],int n,int i)
{   int j,k;
    if (i==n-1) return;	//满足递归出口条件
    else
  {	k=i;			//k记录a[i..n-1]中最小元素的下标
	for (j=i+1;j<n;j++)  	//在a[i..n-1]中找最小元素
	  if (a[j]<a[k])
		k=j;
	if (k!=i)		//若最小元素不是a[i]
	    swap(a[i],a[k]);	//a[i]和a[k]交换
	SelectSort(a,n,i+1);
  }
}
  1. 冒泡排序

​ 设f(a,n,i)用于对a[i…n-1]元素序列(共n-i个元素)进行冒泡排序,是“大问题”,则f(a,n,i+1)用于对a[i+1…n-1]元素序列(共n-i-1个元素)进行冒泡排序,是“小问题”。当i=n-1时所有元素有序,算法结束。

分析如下

f(a,n,i) = 不做任何事情,算法结束			当i=n-1
f(a,n,i) = 对a[i..n-1]元素序列,从a[n-1]开始
	         进行相邻元素比较;			   否则
	         若相邻两元素反序则将两者交换;
	         若没有交换则返回,否则执行f(a,n,i+1);

代码如下

void BubbleSort(int a[],int n,int i)
{   int  j;
    bool exchange;
    if (i==n-1) return;		//满足递归出口条件
  else
  {   exchange=false;		//置exchange为false
     for (j=n-1;j>i;j--)
      if (a[j]<a[j-1])		//当相邻元素反序时
      {	 swap(a[j],a[j-1]);
		 exchange=true;	//发生交换置exchange为true
      }
    if (exchange==false)		//未发生交换时直接返回   
      return;
    else				//发生交换时继续递归调用
      BubbleSort(a,n,i+1);
  }
}

二、递归算法实验

1、实验一 求解n阶螺旋矩阵问题

【问题描述】 创建n阶螺旋矩阵并输出。
输入描述:输入包含多个测试用例,每个测试用例为一行,包含一个正整数n(1≤n≤50),以输入0表示结束。
输出描述:每个测试用例输出n行,每行包括n个整数,整数之间用一个空格分隔。

输入样例:

4
0

输出样例:

1  2  3  4
12 13 14 5
11 16 15 6
10 9  7  8

分析:

通过分析可知“创建n阶螺旋矩阵并输出”当n>2以后可以用递归分为两部分来实现,一个是外层的实现,一个是内层的递归 参数: 1,n 2,num0 用来确定输出的数字 3,len 用来表示当前调用的是哪个递归 4, m 用来确定输出坐标

代码如下:

#include<iostream>
using namespace std;

const int nmax = 50;
int a[nmax][nmax];//定义一个二维数组来装数字后直接打印
int n;//参数n

void HelixMatrix(int n, int num0, int len, int m) {
	
	if (len == 1) {
		a[m][m] = num0;
		return;
	}
	if (len == 2) {
		a[m][m] = num0++;
		a[m][m + 1] = num0++;
		a[m + 1][m + 1] = num0++;
		a[m + 1][m] = num0;
		return;
	}

	int x = m;//坐标最小值
	int y = n + 1 - m;//坐标最大值(第一层的时候是n,第二层是n-1,第三层是n-2.......
	int tmp = num0;//不要随便改变传入参数的值
	
	if (len >= 3) {
		//上面
		for (int i = x; i <= y; i++) {
			a[x][i] = tmp;
			tmp++;
		}
		//右边
		for (int i = x + 1; i <= y; i++) {
			a[i][y] = tmp;
			tmp++;
		}
		//下面
		for (int i = y - 1; i >= x; i--) {
			a[y][i] = tmp;
			tmp++;
		}
		//左边
		for (int i = y - 1; i >= x + 1; i--) {
			a[i][x] = tmp;
			tmp++;
		}
		HelixMatrix(n, tmp, len-2, m+1);
	}
}

int main()
{
	while (1) {
		cin >> n;//输入n

		if (n == 0) {//输入0结束
			printf("请输入有效值\n");
			break;
		}
		//调用函数
		HelixMatrix(n, 1, n, 1);
		//打印
		for (int i = 1; i <= n; i++)
		{
			for (int j = 1; j <= n; j++)
			{
				printf("%5d", a[i][j]);			//输出右对齐
			}
			cout << endl;
		}
		printf("\n");
	}
	system("pause");
	return 0;
}

2、实验二 求解幸运数问题

【问题描述】小明同学学习了不同的进制之后,拿起了一些数字做起了游戏。小明同学知道,在日常生活中我们最常用的是十进制数,而在计算机中,二进制数也很常用。现在对于一个数字x,小明同学定义出了两个函数f(x)和g(x)。 f(x)表示把x这个数用十进制写出后各个数位上的数字之和。如f(123)=1+2+3=6。 g(x)表示把x这个数用二进制写出后各个数位上的数字之和。如123的二进制表示为1111011,那么,g(123)=1+1+1+1+0+1+1=6。 小明同学发现对于一些正整数x满足f(x)=g(x),他把这种数称为幸运数,现在他想知道,小于等于n的幸运数有多少个?

输入描述:每组数据输入一个数n(n≤10)。

输出描述:每组数据输出一行,小于等于n的幸运数个数。

输入样列:

21

样列输出:

3

使用递归分别计算出二进制和十进制各位数上的数字之和,然后比较两个数字之和是否相同。若相同,幸运数个数加一;否则,幸运数个数不变。

代码如下:

#include<stdio.h>
int n;
int solve(int n,int r){
	if(n<r) return n;
	return n%r+solve(n/r,r);
}

int main(){
	scanf("%d",&n);
	int ans=0;
	for(int i=1;i<=n;i++)
		if(solve(i,10)==solve(i,2))
		    ans++;
		 printf("%d\n",ans);
		 return 0;   
}

3、实验三 求解回文序列问题

【问题描述】如果一个数字序列逆置之后跟原序列是一样的就称这样的数字序列为回文序列。例如:{1, 2, 1}, {15, 78, 78, 15} , {112} 是回文序列, {1, 2, 2}, {15, 78, 87, 51} ,{112, 2, 11} 不是回文序列。现在给出一个数字序列,允许使用一种转换操作:选择任意两个相邻的数,然后从序列移除这两个数,并用这两个数字的和插入到这两个数之前的位置(只插入一个和)。
对于所给序列要求出最少需要多少次操作可以将其变成回文序列。
输入描述:输入为两行,第一行为序列长度n ( 1 ≤ n ≤ 50),第二行为序列中的n个整数item[i] (1 ≤ iteam[i] ≤ 1000),以空格分隔。
输出描述:输出一个数,表示最少需要的转换次数

输入样例:

4
1 1 1 3

输出样例:

2

分析:

用 item[low. .high] 表示判断的区间,ie表示前端的数(初始时 ie=item[low]),je表示后端的数(初始时 je=item[high]),ans记录转换操作次数(初始为0)。设 f(low,high) 返回 item[low. .high] 变为回文的操作次数,对应递归模型如下:

f(low,high)=ans						 当item[low..high]区间只有一个数或者为空
f(low,high)=ans+f(++low,--high)  	 当ie=je时
f(low,high)=(ans++)+f(++low,high)	 当ie<je,ie=item[low]+item[low+1]时 
f(low,high)=(ans++)+f(low,--high)	 当ie>=je,je=item[high]+item[high-1]

需要注意的是,后面两种情况可能会重复出现,采用while循环判断,但每次循环是ie总是为item[low. .high]区间首元素,je总是位item[low. .high]区间尾元素

代码如下:

#include <iostream>
using namespace std;

int huiwen(int item[],int low, int high)
{
	int ans = 0;
	int ie = item[low];
	int je = item[high];
	while (low < high && ie!=je){
		if (ie < je){
			low++;
			ie += item[low];
			++ans;
		}
		else{
			high--;
			je += item[high];
			++ans;
		}
	}
	if (low >= high){
		return ans;
	}else{
		low++;
		high--;
		return ans + huiwen(item, low, high);
	}
}

int main()
{
	int *item,n;
	cout << "输入n的值:" << endl;
	cin >> n;
	item = new int[n];
	cout<<"输入i的值:"<< endl;
	for (int i = 0; i < n; i++)
	{
		cin >> item[i];
	}
	cout <<"输出结果为:" <<huiwen(item, 0, n - 1) << endl;
}
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

拾亿-唯一

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值