递归妙用
题目目录:
92. 递归实现指数型枚举
93. 递归实现组合型枚举
94. 递归实现排列型枚举
96. 奇怪的汉诺塔
(高中数学排列组合会了这两题就会了)
#include<iostream>
#include<algorithm>
using namespace std;
int n;
void dfs(int u,int state)// u是当前枚举到的数,state是二进制数记录哪些数被选
{
if(u == n)
{
for(int i = 0;i < n;i++)
if(state >> i & 1) //第几位就代表输出几,只不过不是从0开始,而是从1开始(题目明确是1~n)
cout<<i+1<<" ";
cout<<endl;
return;
}
dfs(u+1,state);
dfs(u+1,state+(1<<u));
}
int main()
{
cin>>n;
dfs(0,0);//当前枚举到0,没有数被选
return 0;
}
93. 递归实现组合型枚举
思路:二进制递归实现即可
由搜索树可以看出,只要我们按照升序枚举的话,所有的结果也是按照升序排序的,并且因为是与顺序无关,不需要去标记哪个数被用过,能不能枚举,只需要记录一下选择的数。
可以看出某一个点的子树其选过的数都是相同的,那么是不是可以记录选择的个数的同时记录一下从哪个数继续枚举下去。
纵观整棵树,不难发现根节点后面几颗子树会出现枚举不了的情况,并且随着n、m的增大,这种情况会越来越多。那有没有一种办法可以省去这些判断呢?
剪枝,dfs里非常常见,顾名思义就是将不需要的枝条剪去,也就是递归搜索树的不符合条件的子树或者分支。比如这题,如果当前选的数加上剩下可以选的数还是不足需要的数,那么直接结束这一分支的搜索。
#include<iostream>
#include<algorithm>
using namespace std;
int n,m;
void dfs(int u,int s,int state)
{
if(s == m)
{
for(int i = 0;i < n;i++)
if(state >> i & 1) cout<<i+1<<" ";
cout<<endl;
return;
}
if(u == n)return;
for(int i = u;i < n;i++)
{
dfs(i+1,s+1,state + (1 << i));
}
}
int main()
{
cin>>n>>m;
dfs(0,0,0);//分别表示 枚举到第几个数,当前选了多少个数,当前选了哪些数(二进制,0没选 1选了)
return 0;
}
94. 递归实现排列型枚举
这题典型的dfs题,全排列最经典!!!
下面请看代码(代码有注释)
或者可以有STL做法(特殊做法)
C++STL中全排列函数next_permutation(在此没有写STL,大家可以自己写写)
PS:stl的函数经常是左闭右开的
#include<iostream>
using namespace std;
const int N = 10;
int path[N],stu[N];//path[N]用来说明存下的数,stu[N]表示标记这个数被用过了没有
int n;
void dfs(int u)
{
if(u > n)//数字填完了,输出
{
for(int i = 1;i <= n;i++)
cout<<path[i]<<" ";
cout<<endl;
}
for(int i = 1;i <= n;i++)//空位上可以选择的数字为:1 ~ n
{
if(!stu[i])//如果数字 i 没有被用过
{
path[u] = i;//将所选择的数字放进去
stu[i] = 1;//标记这个数已经放进去过了
dfs(u+1);//开始放下一个数
stu[i] = 0;//下一个数是没有被标记过的,所以回溯状态
}
}
}
int main()
{
cin>>n;
dfs(1);
return 0;
}
96. 奇怪的汉诺塔
思路:设d[n]表示求解n盘3塔问题的最小步数
递推式:d[n] = 2 * d[n-1] + 1//为什么要乘2呢(那是因为我们移东西需要移两次,第一次移到一边去还得把他移回来)
即把前n-1个盘子从A柱移到B柱,然后把A柱上剩的那一个盘子移动到C柱,最后把B柱上的那n-1个盘子移动到C柱上
设f[n]表示求解n盘4塔问题的最小步数
递推式:f[n] = min{2 * f[i] + d[n - i]}
初始化:f[0] = 0
先把i个盘子在4塔模式下移动到B柱,
然后把n-i个盘子在3塔模式下移动到D柱(因为不能覆盖到B柱上,就等于只剩下A、C、D柱可以用)
最后把i个盘子在4塔模式下移动到D柱
考虑所有可能的i取最小值,即得到上述递推公式
代码如下:
#include<iostream>
#include<cstring>//memset需要
using namespace std;
const int N = 20;
int main()
{
int d[N],f[N];
memset(f,0x3f,sizeof f);
f[0] = 0;
for(int i = 1;i <= 12;i++) d[i] = (1<<i)-1;//位运算哦
for(int i = 1;i <= 12;i++)
for(int j = 0;j < i;j++)
f[i] = min(f[i],f[j]*2+d[i-j]);
for(int i = 1;i <= 12;i++)cout<<f[i]<<endl;
return 0;
}