一、递归实现指数型枚举
1.1 y 总讲解的方式
注意: 同一行内的数必须升序排列
#include <bits/stdc++.h>
using namespace std;
const int N = 20;
int n;
bool vis[N]; //判断选还是不选
void dfs(int u) //第几层就是筛选第几个数字
{
if(u>n) //不可以有等号,如果有等号会少一层递归,即最后一层无法递归
{
for(int i=1;i<=n;i++)//从1到n选择
if(vis[i]) //把选择的数打印出来
cout<<i<<" ";
cout<<endl;
return ;
}
else {
vis[u]=true;//选这个数字
dfs(u+1);
vis[u]=false;//不选这个数字
dfs(u+1);
}
}
int main() {
cin>>n;
dfs(1); //从1开始选择,到n结束,所以不能从0开始;
return 0;
}
记录下方案数:
#include <bits/stdc++.h>
using namespace std;
const int N = 25;
int n, cnt;
int st[N];
int ways[1<<15][15]; // 每个数字均有选与不选两种情况
void dfs(int u) {
if (u > n) {
for (int i = 1; i <=n; i ++ ) {
if (st[i]) {
cout << i << " ";
ways[cnt][i] = i; // 选了则填上相应数字
cnt ++;
}
}
cout << endl;
return ;
}
st[u] = 1; // 选当前数
dfs(u + 1);
st[u] = 0; // st 为全局变量,所以一定要恢复现场
dfs(u + 1);
}
int main() {
cin >> n;
dfs(1);
return 0;
}
当输入为 3 时:
1 2 3
1 2
1 3
1
2 3
2
3
1.2 如果题目不要求输出方案必须升序
填坑,从填1个坑到填n个坑。坑可以随便填,比如第1个坑选了2之后,第2个坑可以填1(非升序),也可以填3(升序)
实际上是:n 个数不要求升序的方案数,为 1 的排列枚举 + 2 的排列枚举 + 3 的排列枚举 + … + n 的排列枚举
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n;
int a[20];
bool vis[20];
/* 一共tar个坑,当前枚举到第pos个坑
dfs(1, i) 表示 n 个数中不考虑升序的选 i 个数的方案
当 i = n时,为排列型枚举方案数
*/
void dfs(int pos, int tar) {
if (pos == tar + 1) {
for (int i = 1; i <= tar; i ++ ) cout << a[i] << " ";
cout << endl;
return ;
}
// 选数填坑,选择的数范围是1~n
for (int i = 1; i <= n; i ++) {
if (!vis[i]) {
vis[i] = true; a[pos] = i;
dfs (pos + 1, tar);
vis[i] = false;
}
}
}
int main() {
cout << endl; // 不取
cin >> n;
for (int i = 1; i <= n; i ++ )
dfs(1, i); // n 个数中不考虑升序的选 i 个数的方案,当 i = n,为排列型枚举
return 0;
}
当输入为 3 时:
1
2
3
1 2
1 3
2 1
2 3
3 1
3 2
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
1.3 所有升序方案(同 1.1 的目的)
依旧是填坑,从填 1 个坑到填 n 个坑。
和上面不同的是,上面是第 1 个坑选了 2 之后,第 2 个坑还可以从 2 之前的数开始填坑,现在是第1个坑选了2之后,第2个坑只能从大于2的数里选了。
即,当前的坑pos处填了num,则填下一个坑 pos+1 时,只能从大于num的数里选择填坑。
解决办法: dfs 里加一个 start,选数的时候,只能从 start 之后的数里面选择
总结: dfs 需要四个变量记录当前状态:
当前位于的坑pos,当前可以选的最小数字start,当前的目标总坑数tar,当前已经填的坑数组a[]。
#include <bits/stdc++.h>
using namespace std;
int n;
int a[20];
bool vis[20];
// 当前枚举到第pos个坑, 上一个坑填的是start-1,这次只能从start开始找数填, 一共要填tar个坑
void dfs(int pos, int start, int tar) {
if (pos == tar + 1) {
for (int i = 1; i <= tar; i ++ ) cout << a[i] << " ";
cout << endl;
return ;
}
// 选数填坑,选择的数范围是start~n
for (int i = start; i <= n; i ++) {
if (!vis[i]) {
vis[i] = true; a[pos] = i;
dfs (pos + 1, i + 1, tar);
vis[i] = false;
}
}
}
int main() {
cout << endl;
cin >> n;
for (int i = 1; i <= n; i ++ )
dfs(1, 1, i);
return 0;
}
1.4 二进制优化 1
用一个二进制数表示选了哪些数,替代上面代码的 a[20]
数组
其中 state |= 1 << i
代表状态的改变,选了 i
这个数
state ^= 1 << i
代表状态的还原,还原没选 i
这个数的状态
x << y, x 左移 y 位,相当于 x 乘以 2 的 y 次方
x >> y, x 右移 y 位,相当于 x 除以 2 的 y 次方
^ 位异或运算符
0^0=0;
0^1=1;
1^0=1;
1^1=0;
state |= 1 << i
:表示将 1 左移 i 位,相当于
1
∗
2
i
1 * 2^i
1∗2i,取 | 后,相当与将 state 从右往左的第 i 位 置为 1
state ^= 1 << i
:表示从右往左,如果 state 的第 i 位为 1,则变为 0,若为 0,则变为 1
#include <bits/stdc++.h>
using namespace std;
int n;
bool vis[20];
void dfs(int pos, int start, int tar, int state) {
if (pos == tar + 1) {
for (int j = 1; j <= n; j ++ ) {
if (state >> j & 1) cout << j << " ";
}
cout << endl;
return ;
}
for (int i = start; i <= n; i ++) {
if (!vis[i]) {
vis[i] = true;
state |= 1 << i; // 将第 i 位变为 1
dfs (pos + 1, i + 1, tar, state);
vis[i] = false;
state ^= 1 << i; // 异或运算,由于前面选择的原因,这里只有可能遇到 1 1 的情况,即将其变为 0
}
}
}
int main() {
cout << endl;
cin >> n;
for (int i = 1; i <= n; i ++ )
dfs(1, 1, i, 0);
return 0;
}
1.5 状态压缩非递归
状态压缩的特性:可以枚举所有选与不选的情况
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
// state 是每一个状态
for (int state = 0; state < 1 << n; state ++ ) {
// 用指针j遍历二进制数state中的每一位
for (int j = 0; j < n; j ++ ) {
if (state >> j & 1) cout << j + 1 << " ";
}
cout << endl;
}
return 0;
}
1.6 状态压缩递归(模拟二进制,搜索每个位置,选与不选)
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 16;
int n;
int st[N]; // 状态,记录每个位置当前的状态:0表示还没考虑,1表示选它,2表示不选它
void dfs(int u)
{
if (u > n)
{
for (int i = 1; i <= n; i ++ )
if (st[i] == 1)
printf("%d ", i);
printf("\n");
return;
}
st[u] = 2;
dfs(u + 1); // 第一个分支:不选
st[u] = 0; // 恢复现场
st[u] = 1;
dfs(u + 1); // 第二个分支:选
st[u] = 0;
}
int main()
{
cin >> n;
dfs(1);
return 0;
}
二、递归实现排列型枚举
#include <bits/stdc++.h>
using namespace std;
const int N = 10;
int n;
int state[N]; // 0 表示还没放数,1~n表示放了哪个数
bool used[N]; // true表示用过,false表示还未用过
void dfs(int u)
{
if (u > n) // 边界
{
for (int i = 1; i <= n; i ++ ) printf("%d ", state[i]); // 打印方案
puts("");
return;
}
// 依次枚举每个分支,即当前位置可以填哪些数
for (int i = 1; i <= n; i ++ )
if (!used[i])
{
state[u] = i;
used[i] = true;
dfs(u + 1);
// 恢复现场
state[u] = 0;
used[i] = false;
}
}
int main()
{
scanf("%d", &n);
dfs(1);
return 0;
}
94. 递归实现排列型枚举
STL 解法:
#include <bits/stdc++.h>
using namespace std;
const int N=11;
int n,a[N];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
a[i]=i;
do
{
for(int i=1;i<=n;i++)
cout<<a[i]<<" ";
cout<<endl;
}while(next_permutation(a+1,a+1+n));
return 0;
}
正规解法:
#include <bits/stdc++.h>
using namespace std;
const int N = 100;
int state[N];//0表示没用过
bool vis[N]; //用于记录用过还是没用过
int n;
void dfs(int u) {
if(u > n) {
for(int i=1; i<=n; i++) {
printf("%d ", state[i]);
}
puts("");
return ;
}
//一次填n个数,即n个分支
for(int i=1; i<=n; i++) {
if(!vis[i]) {
vis[i] = true; //这里表示要使用i
state[u] = i;//将i赋给当前
dfs(u+1);
//恢复现场
state[u] = 0;
vis[i] = false;
}
}
}
int main() {
scanf("%d", &n);
dfs(1);
return 0;
另一种写法,实际上和上面一样的,下面只是把总位置传进来了,上面则直接使用全局变量 n
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n;
int a[20];
bool vis[20];
// 一共tar个坑,当前枚举到第pos个坑
void dfs(int pos, int tar) {
if (pos == tar + 1) {
for (int i = 1; i <= tar; i ++ ) cout << a[i] << " ";
cout << endl;
return ;
}
// 选数填坑,选择的数范围是1~n
for (int i = 1; i <= n; i ++) {
if (!vis[i]) {
vis[i] = true;
a[pos] = i;
dfs (pos + 1, tar);
vis[i] = false;
}
}
}
int main() {
cin >> n;
dfs(1, n);
return 0;
}
三、递归实现组合型枚举
#include <bits/stdc++.h>
using namespace std;
const int N = 30;
int n, m;
int way[N];
void dfs(int u, int start)
{
if (u + n - start < m) return; // 剪枝
if (u == m + 1)
{
for (int i = 1; i <= m; i ++ ) printf("%d ", way[i]);
puts("");
return;
}
for (int i = start; i <= n; i ++ )
{
way[u] = i;
dfs(u + 1, i + 1);
way[u] = 0; // 恢复现场
}
}
int main()
{
scanf("%d%d", &n, &m);
dfs(1, 1);
return 0;
}
或者使用下面方式:利用 vis 数组,查看那些被选上了
#include <bits/stdc++.h>
using namespace std;
const int N = 26;
int n, m;
bool vis[N];
void dfs(int u, int start) {
if (u == m + 1) {
for (int i = 1; i <= n; i++ ) {
if (vis[i]) cout << i << " ";
}
cout << endl;
return ;
}
for (int i = start; i <= n; i ++ ) {
if (!vis[i]) {
vis[i] = 1;
dfs(u + 1, i + 1);
vis[i] = 0;
}
}
}
int main() {
cin >> n >> m;
dfs(1, 1);
return 0;
}