【模板】各类递归方式

一、递归实现指数型枚举

在这里插入图片描述

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 12i,取 | 后,相当与将 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值