一:递推与递归的区别:
- 递归:从已知问题的结果出发,用迭代表达式逐步推算出问题的开始的条件,即顺推法的逆过程,称为递归。
- 递归的定义:在一个函数的定义中又直接或间接地调用本身。
- 递归思想: 把规模大的、较难解决的问题变成规模较小的、易解决的同一问题。规模较小的问题又变成规模更小的问题,并且小到一定程度可以直接得出它的解,从而得到原来问题的解。
- 递归的应用场合:
1、数据的定义形式是递归的,例如求Fibonacii数列的第n项 。
2、数据之间的逻辑关系(即数据结构)是递归的,如树、图等的定义和操作。
3、某些问题虽然没有明显的递归关系或结构,但问题的解法是不断重复执行一组操作,只是问题规模由大化小,直至某个原操作(基本操作)就结束。例如:汉诺塔问题。
- 递归设计的要素:
1、在函数中必须有直接或间接调用自身的语句;
2、在使用递归策略时,必须有一个明确的递归结束条件,称为递归出口(或递归边界)。
- 递推:递推算法是一种用若干步可重复运算来描述复杂问题的方法。递推是序列计算中的一种常用算法。通常是通过计算机前面的一些项来得出序列中的指定象的值。
- 递推:数学推导 发现规律 重复简单运算
递 推 小 结:
1、递推是从已知条件开始;
2、递推必须有明确的通用公式;
3、递推必须是有限次运算。
递 归 小 结:
1. 递归:未知的推到已知的,再由此返回。
2. 基本思想:将复杂的操作分解为若干重复的简单操作。
递归与递推区别:相对于递归算法,递推算法免除了数据进出栈的过程,也就是说,不需要函数不断的向边界值靠拢,而直接从边界出发,直到求出函数值。
二:递推与递归的简单应用
例(1):递归实现排列型枚举
问题描述:
把 1∼n 这 n 个整数排成一行后随机打乱顺序,输出所有可能的次序。
输入格式
一个整数 n。
输出格式
按照从小到大的顺序输出所有方案,每行 1 个。
首先,同一行相邻两个数用一个空格隔开。
其次,对于两个不同的行,对应下标的数一一比较,字典序较小的排在前面。
数据范围
1≤n≤9
输入样例:
3
输出样例:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
AC代码如下:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 10;
int n;
bool book[N];
vector<int> q;
void dfs(int x)
{
if(x == n + 1)
{
for(int i = 0; i < q.size(); i ++ )
{
cout << q[i] << " ";
}
puts("");
return;
}
for(int i = 1; i <= n; i ++ )
{
if(book[i] == false)
{
q.push_back(i);
book[i] = true;
dfs(x + 1);
//回溯
q.pop_back();
book[i] = false;
}
}
}
int main()
{
cin >> n;
dfs(1);
return 0;
}
例(2):费解的开关
问题描述:
你玩过“拉灯”游戏吗?
25 盏灯排成一个 5×5 的方形。
每一个灯都有一个开关,游戏者可以改变它的状态。
每一步,游戏者可以改变某一个灯的状态。
游戏者改变一个灯的状态会产生连锁反应:和这个灯上下左右相邻的灯也要相应地改变其状态。
我们用数字 1 表示一盏开着的灯,用数字 0 表示关着的灯。
下面这种状态
10111
01101
10111
10000
11011
在改变了最左上角的灯的状态后将变成:
01111
11101
10111
10000
11011
再改变它正中间的灯后状态将变成:
01111
11001
11001
10100
11011
给定一些游戏的初始状态,编写程序判断游戏者是否可能在 6 步以内使所有的灯都变亮。
输入格式
第一行输入正整数 n,代表数据中共有 n 个待解决的游戏初始状态。
以下若干行数据分为 n 组,每组数据有 5 行,每行 5 个字符。
每组数据描述了一个游戏的初始状态。
各组数据间用一个空行分隔。
输出格式
一共输出 n 行数据,每行有一个小于等于 6 的整数,它表示对于输入数据中对应的游戏状态最少需要几步才能使所有灯变亮。
对于某一个游戏初始状态,若 66 步以内无法使所有灯变亮,则输出 −1。
数据范围
0<n≤500
输入样例:
3
00111
01011
10001
11010
11100
11101
11101
11110
11111
11111
01111
11111
11111
11111
11111
输出样例:
3
2
-1
思路:
可以发现:如果固定了第一行,则满足题意的点击方案只有一种,所以可以枚举第一行的点击方案,注意是枚举的是按不按,而不是这个灯亮不亮;
比如说:
op 等于00001 表示的是第一个位置的灯被按了一下,而不是表示第一个位置的灯是亮着的
AC代码如下:
/*
费解的开关
二进制 + 递推
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 6;
int t;
char a[N][N], backup[N][N];
int dx[N] = {-1, 0, 1, 0, 0}, dy[N] = {0, 1, 0, -1, 0};
void turn(int x, int y)
{
for(int i = 0; i < 5; i ++ )
{
int x0 = x + dx[i], y0 = y + dy[i];
if(x0 < 0 || x0 >= 5 || y0 < 0 || y0 >= 5) continue;
a[x0][y0] ^= 1;
}
}
int main()
{
scanf("%d", &t);
while(t -- )
{
for(int i = 0; i < 5; i ++ ) cin >> a[i];
memcpy(backup, a, sizeof a);
/*
枚举的是按不按,而不是这个灯亮不亮;
比如说:
op 等于00001 表示的是第一个位置的灯被按了一下,而不是表示第一个位置的灯是亮着的
*/
int res = 10;
for(int op = 0; op < 1 << 5; op ++ )
{
int cnt = 0;
for(int k = 0; k < 5; k ++ )
{
if(op >> k & 1)
{
cnt ++;
turn(0, k);
}
}
for(int i = 0 ; i < 4; i ++ )
{
for(int j = 0; j < 5; j ++ )
{
if(a[i][j] == '0')
{
cnt ++;
turn(i + 1, j);
}
}
}
bool dark = false;
for(int i = 0; i < 5; i ++)
{
if(a[4][i] == '0')
{
dark = true;//不和题意,舍弃
break;
}
}
if(!dark) res = min(res, cnt);
memcpy(a, backup, sizeof a);
}
if(res > 6) res = -1;
cout << res << endl;
}
return 0;
}
例(3): 奇怪的汉诺塔
问题描述:
汉诺塔问题,条件如下:
1、这里有 A、B、C 和 DD 四座塔。
2、这里有 n 个圆盘,n 的数量是恒定的。
3、每个圆盘的尺寸都不相同。
4、所有的圆盘在开始时都堆叠在塔 A 上,且圆盘尺寸从塔顶到塔底逐渐增大。
5、我们需要将所有的圆盘都从塔 A 转移到塔 D 上。
6、每次可以移动一个圆盘,当塔为空塔或者塔顶圆盘尺寸大于被移动圆盘时,可将圆盘移至这座塔上。
请你求出将所有圆盘从塔 A 移动到塔 D,所需的最小移动次数是多少。
汉诺塔塔参考模型
输入格式
没有输入
输出格式
对于每一个整数 n,输出一个满足条件的最小移动次数,每个结果占一行。
数据范围
1≤n≤12
输入样例:
没有输入
输出样例:
参考输出格式
思路:
递归思想:
该题可理解为在四塔模式下先把 i 个盘子移到 B 塔,然后在三塔模式下将 n - i 个盘子移到 D 塔,最后在四塔模式下将 B 塔上的 i 个盘子移到 D 塔。则:
其中 d[ ] 表示的是在三塔模式下的最少步数。
AC代码如下:
#include <bits/stdc++.h>
using namespace std;
const int N = 15;
int n;
int d[N], f[N];
int main()
{
d[1] = 1;
for(int i = 2; i <= 12; i ++ )
{
d[i] = 2 * d[i - 1] + 1;
}
//f[n] = min(f[i], 2 * f[i] + d[n - i])
memset(f, 0x3f, sizeof(f));
f[1] = 1;
for(int i = 2; i <= 12; i ++ )
{
//一共有 i 个
for(int j = 1; j < i; j ++ )
{
f[i] = min(f[i], 2 * f[j] + d[i - j]);
}
}
for(int i = 1; i <= 12; i ++ )
cout << f[i] << endl;
return 0;
}