祝各位龙年龙行龘龘,前程朤朤,心想事成!
(并不怎么华丽的的分割线)
开始!
一,递归乃何方神圣
递归是一种在数学和计算机科学中常用的算法设计技术,它允许函数在执行过程中调用自身。递归算法通过将问题分解为更小的子问题,并反复调用自身来解决这些子问题。递归函数必须有一个终止条件,以防止无限递归导致的栈溢出。(引自百度百科)
解释一下:
递归可以由一个函数完成,这个函数会不断调用自己,编写时要设置一个递归出口,不然调用栈就会溢出。在碰到递归出口时就出来。在递归中每一层执行相同的操作(可以这么说)。
......
这不俄罗斯套娃嘛!
没错就是这个逻辑,每一个套娃里都有一个更小的,最小的就是递归出口。
至于栈之前讲过了,但是就再唠两句吧。
栈的特性是先进后出。
单词是stack;类推可得头文件#include<stack>;定义(例)stack<int> s,stack<double> f;
栈顶 .top;弹出 .pop;存入 .push 。
既然都理解了基本概念就进入代码吧。
二,基本代码
这段代码就是帮助你理解递归。不需要自己写出来。
#include<iostream>
using namespace std;
int r,k;
int a[105],f[105];
void fun(int n)
{
int i,j,o;
if(n>k)
{
for(o=1;o<=k;o++)
{
cout<<f[o]<<" ";
}
cout<<endl;
return;
}
for(i=1;i<=r;i++)
{
f[n]=i;
fun(n+1);
f[n]=0;
}
}
int main()
{
cin>>r>>k;
fun(1);
}
这段代码运行出来绝对特别特别无厘头(因为是我乱写的大家莫动怒),但绝对不是错误代码,它能输出:
从1到r中选k个数字的所有可能性,所以请你耐心等待运行结束,它绝对不是无限输出。
让我们分析一下:
递归出口:n(函数的参数即递归次数)大于k,说明要输出了,那就输出吧,输出完就返回(这里是返回因为有下一组数据等着你呢)。
每一次递归要执行相同的操作:遍历r个数,存起来,进入下一个递归。返回回来了,就把数组清零。
递归就这么easy!!!!
三,刷刷题
其实啊本人认为本人目前做过(限制条件哈哈哈)纯粹的递归题目不多:
汉诺塔(太经典了必须算一个),洛谷的P1057和P1706。
...........................................................................................................................................................
一,汉诺塔
(其实这道题单独写一篇题解绰绰有余)
既然这样,咱们就长话短说。
汉诺塔(Tower of Hanoi),又称河内塔,是一个源于印度古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。(引自百度百科)
先拿三个为例:
画的乱见谅
我们再次简单分析一下:
要把第n个盘子移到第三根柱子上,就要先把前n-1个盘子移到第二个柱子上,要把前n-1个盘子移到第二根柱子上,就要把前n-2个盘子移到第三个柱子上......
把大问题转化成小问题,有终点,执行相同的操作:这不就是递归吗?!
太经典的递归。
代码如下:
(温馨提示:千万别太关注函数内部是怎么运行的,了解逻辑即可)
#include<iostream>
using namespace std;
void f(int n,int a,int c,int b) //将n个盘子从a移到c,辅助是b
{
if(n==0)
{
return;
}
f(n-1,a,b,c);
cout<<n<<":"<<a<<"->"<<c<<endl;
f(n-1,b,c,a);
}
int main()
{
int n;
cin>>n;
f(n,1,3,2);
}
二,P1706 全排列问题
题目描述
按照字典序输出自然数 1 到 n 所有不重复的排列,即 n 的全排列,要求所产生的任一数字序列中不允许出现重复的数字。
输入格式
一个整数 n。
输出格式
由 1∼n 组成的所有不重复的数字序列,每行一个序列。
每个数字保留 5 个场宽。
咦,和前面的基础代码好像啊!
没错!!!改一改就对了!!!
#include<iostream>
#include<iomanip>
using namespace std;
int k,o;
int f[15],a[15];
void fun(int n)
{
int i,j,m;
if(n>k) //递归出口,输出
{
for(j=1;j<=k;j++)
{
cout << setw(5) << f[j];
}
cout<<endl;
return;
}
for(i=1;i<=k;i++)
{
if(a[i]==0) //是否有标记
{
a[i]=1; //打标记
f[n]=i; //数组储存
fun(n+1); //下一层递归
f[n]=0; //储存清零
a[i]=0; //去标记
}
}
return;
}
int main()
{
cin>>k;
fun(1);
}
三,P1157 组合的输出
# 组合的输出
## 题目描述
排列与组合是常用的数学方法,其中组合就是从 n 个元素中抽出 r 个元素(不分顺序且 r 大于或等于 n),我们可以简单地将 $n$ 个元素理解为自然数 1,2,......,n,从中任取 r 个数。
现要求你输出所有组合。
例如 n=5,r=3,所有组合为:
123,124,125,134,135,145,234,235,245,345。
输入格式
一行两个自然数 n,r(1<n<21,0小于等于r和n)。
输出格式
所有的组合,每一个组合占一行且其中的元素按由小到大的顺序排列,每个元素占三个字符的位置,所有的组合也按字典顺序。
注意哦!输出时,每个数字需要 3 个场宽。以 C++ 为例,你可以使用下列代码:
cout << setw(3) << x;
输出占 3个场宽的数 x。注意你需要头文件 `iomanip`。
样例 1
样例输入 1
5 3
样例输出 1
1 2 3
1 2 4
1 2 5
1 3 4
1 3 5
1 4 5
2 3 4
2 3 5
2 4 5
3 4 5
其实就是上一道题的升级版。
#include<iostream>
#include<iomanip>
using namespace std;
int y[105],n,r,o[105];
void dfs(int u)
{
int i,j,k;
if(u>r)
{
for(k=1;k<=r;k++)
{
cout << setw(3) << o[k];
}
cout<<endl;
return;
}
for(i=1;i<=n;i++)
{
if(y[i]==0&&i>o[u-1])
{
y[i]=1;
o[u]=i;
dfs(u+1);
y[i]=0;
o[u]=0;
}
}
return;
}
int main()
{
cin>>n>>r;
dfs(1);
}
仔细一瞧发现:
其实就比上一道题多了一个判断,当前的数不能比上一个数小
i>o[u-1]
还有3个场宽变成5个。
仅此而已。
递归本身其实真的不难(真心话),但是递归真的很伟大,因为它延伸出了很多很多知识点。