Java教程

递推与递归(一)

本文主要是介绍递推与递归(一),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

递推与递归(一)

递归与递推是最基础也是最常用的算法,在很多地方都能见到他们的影子,下面就让我来带领大家一起学习一下。本篇文章讲究实用,会以习题的方式进行展开。废话不多说,我们开始吧!

递归——DFS:

1.递归实现指数型枚举

从 1∼n 这 n 个整数中随机选取任意多个,输出所有可能的选择方案。

输入格式
输入一个整数 n。

输出格式
每行输出一种方案。

同一行内的数必须升序排列,相邻两个数用恰好 1 个空格隔开。

对于没有选任何数的方案,输出空行。

本题有自定义校验器(SPJ),各行(不同方案)之间的顺序任意。

数据范围
1≤n≤15
输入样例:
3
输出样例:

3
2
2 3
1
1 3
1 2
1 2 3

解题思路:

​ 首先要知道,dfs()里的参数是从1~n,传递到当前第几位。这样也不行,我们想要直到每个位置是否已经或者还没有枚举过,因此要开一个数组记st[]录每个位置当前的状态。
在这里插入图片描述

代码:

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

const int N=20;
int st[N];//状态数组,1表示选择当前的数,2表示不选,0表示还没枚举到
int n;

void dfs(int u)
{
    if(u>n)//递归都要有一个递归边界,否则将永远递归下去,这里的边界是枚举完所有的点
    {
        for(int i=1;i<=n;i++) 
            if(st[i]==1)
                cout<<i<<" ";
        cout<<endl;
        return ;
    }
    
    st[u]=2;//1.不选当前的数,即不选u
    dfs(u+1);//递归进入下一个数
    st[u]=0;//恢复现场

    st[u]=1;//2、选择当前的数,即选择u
    dfs(u+1);//递归进入下一个数
    st[u]=0;//恢复现场
    
}

int main(){
    cin>>n;
    dfs(1);//传递到当前第1个数
    return 0;
}

2.递归实现排列型枚举

把 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

解题思路:

​ 首先要知道,dfs()里的参数是从1~n,当前应该传递第几位。确定递归边界:当把n个数全部枚举完之后,输出一个答案,这里记录答案的是st[]数组,里面存的是第1 ~n位选择的数。另外,还要开一个数组used[]表示当前数是否已经选择了

代码:

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

const int N=10;
int st[N];
bool used[N];
int n;

void dfs(int u)
{
    if(u>n)//递归都要有一个递归边界,否则将永远递归下去,这里的边界是枚举完所有的点
    {
        for(int i=1;i<=n;i++) cout<<st[i]<<" ";//st[]数组里存放着一行输出
        cout<<endl;
        return ;
    }
    
    for(int i=1;i<=n;i++) 
    {
        if(!used[i])
        {
            st[u]=i;
            used[i]=true;
            dfs(u+1);
            
            st[u]=0;//恢复现场
            used[i]=false;
        }
    }
}

int main(){
    cin>>n;
    dfs(1);
    return 0;
}

3.递归实现组合型枚举

从 1~n 这 n 个整数中随机选出 m 个,输出所有可能的选择方案。

输入格式
两个整数 n,m ,在同一行用空格隔开。

输出格式
按照从小到大的顺序输出所有方案,每行 1 个。

首先,同一行内的数升序排列,相邻两个数用一个空格隔开。

其次,对于两个不同的行,对应下标的数一一比较,字典序较小的排在前面(例如 1 3 5 7 排在 1 3 6 8 前面)。

数据范围
n>0 ,
0≤m≤n ,
n+(n−m)≤25
输入样例:
5 3
输出样例:
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
思考题:如果要求使用非递归方法,该怎么做呢?

解题思路:在排列问题中,是在n个数中选择m个数,题目中要求是按找字典序输出,因此在选择每一个数时,每次只要选择比某个数大1的数,再递归下去,即可按字典序输出,在DFS函数中,一共有两个参数,第一个参数表示当前应该选择第几个数(一共m个),第二个参数表示第一个比当前数大的数。在此过程中用st[]数组存储每一组组合数

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

const int N=30;
int st[N];
int n,m;

void dfs(int u,int start)
{
    if(u>m)//当前已经选择完所有数了
    {
        for(int i=1;i<=m;i++)
        cout<<st[i]<<" ";
        cout<<endl;
        return ;
    }
    for(int i=start;i<=n;i++)
    {
        st[u]=i;
        dfs(u+1,i+1);
        st[u]=0;//恢复原场
    }
}

int main(){
    cin>>n>>m;
    dfs(1,1);
    return 0;
}

4.递归实现排列类型枚举 II

给定一个长度为 n 的可包含重复数字的序列,请你求出其所有不重复的全排列。

输入格式
第一行包含整数 n。

第二行包含 n 个整数。

输出格式
输出所有的不同排列,每种排列占一行。

在确定每种排列的输出顺序时,第一个数较小的先输出,第一个数相同时,第二个数较小的先输出,以此类推。

数据范围
1≤n≤9,
数组中包含的元素的取值范围 [1,9]
输入样例:
3
1 1 2
输出样例:
1 1 2
1 2 1
2 1 1

解题思路:

和递归实现组合型枚举I思路相似,都是向dfs中传递参数u,表示当前应该传递第几个数(还没选择),只不过这里要再开一个数组value[]存储输入 的数

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

const int N=12;
int st[N],value[N];//st存储每一组要输出的全排列
bool used[N];
int n;

void dfs(int u)
{
    if(u>n)//当前已经选好所有的数
    {
        for(int i=1;i<=n;i++)cout<<st[i]<<" ";
        cout<<endl;
        return ;
    }
    
    for(int i=1;i<=n;i++)
    {
        if(!used[i])
        {
            st[u]=value[i];
            used[i]=true;
            dfs(u+1);
            used[i]=false;
            
            while(i+1<=n&&value[i]==value[i+1]) i++;//去重:去除排序后相邻的相同元素
        }
    }
}

int main(){
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>value[i];
    sort(value+1,value+1+n);
    dfs(1);
    return 0;
}

5.递归实现指数型枚举 II

给定一个长度为 n 的可包含重复数字的序列,从中随机选取任意多个数字,输出所有可能的选择方案。

输入格式
第一行包含一个整数 n,表示序列长度。

第二行包含 n 个正整数。

输出格式
每行输出一种方案。

同一行内的数必须升序排列,相邻两个数用恰好1个空格隔开。

对于没有选任何数的方案,输出空行。

本题有自定义校验器(SPJ),各行(不同方案)之间的顺序任意。

数据范围
1≤n≤15,
序列内所有元素均不大于 n。

输入样例:
3
1 2 2
输出样例:

1
2
1 2
2 2
1 2 2

解题思路:dfs函数传递的是当前要选择第u个数字,当u>n说明已经枚举完所有的数,可以输出了

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

const int N=20;
int st[N];
bool used[N];//标记是否选过
int n;

void dfs(int u)
{
    if(u>n)
    {
        for(int i=1;i<=n;i++)
        if(used[i])
            cout<<st[i]<<" ";
        cout<<endl;
        return ;
    }
    dfs(u+1);
    //防止出现重复情况
    if(u!=1&&!used[u-1]&&st[u]==st[u-1]) return;
    
    used[u]=true;
    dfs(u+1);
    used[u]=false;
}

int main(){
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>st[i];
    sort(st+1,st+n+1);//排序方便计算重复数字
    dfs(1);
    return 0;
}

6.递归实现组合型枚举 II

给定一个长度为 n 的可包含重复数字的序列,从中随机选取 m 个数字,输出所有可能的选择方案。

输入格式
第一行包含两个整数 n,m。

第二行包含 n 个正整数。

输出格式
按照从小到大的顺序输出所有方案,每行 1 个。

首先,同一行内的数升序排列,相邻两个数用一个空格隔开。

其次,对于两个不同的行,对应下标的数一一比较,字典序较小的排在前面(例如1 3 5 7排在1 3 6 8前面)。

数据范围
n>0,
0≤m≤n,
n+(n−m)≤25,
序列内所有元素均不大于 n。

输入样例:
5 3
1 2 2 3 3
输出样例:
1 2 2
1 2 3
1 3 3
2 2 3
2 3 3

解题思路:dfs里传入两个参数,第一个是要选择第u个数(一共m个数),第二个数是当前要选择st数组中下标为几的数,当u>m时返回一组答案,否则,从当前下标开始到n,若下标为start,且前一个数没有选择,且前一个数与之相等,则跳过,开始下一个数。直到递归到n

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

const int N=30;
int st[N];//存储输入值以及要输出的组合数
bool used[N];//记录是否已经使用过
int n,m;

void dfs(int u,int start)
{
    if(u>m)
    {
        for(int i=1;i<=n;i++)
            if(used[i]) cout<<st[i]<<" ";//若当前这个数没有选择则输出
        cout<<endl;
        return;
    }
    
    for(int i=start;i<=n;i++)
    {
        if(i!=1&&!used[i-1]&&st[i-1]==st[i])//若不选第一个数,且前一个数没有选择,且前一个数与之相等,则跳过
            continue;
        used[i]=true;
        dfs(u+1,i+1);
        used[i]=false;
    }
}

int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>st[i];
    sort(st+1,st+n+1);//记得排序!!
    dfs(1,1);//当前要选择第1个数,从st[1]开始选择
    return 0;
}
总结

大家可能会有这样的一种疑问:为什么本章例题中调用的函数名全部都是dfs?

因为dfs是递归的一种,大家发没发现这六个例题中都有要选择几个数每个数都有几种选法,这两个概念可以抽象为dfs深度搜索树的树深以及节点的分支。掌握这个概念相信大家就会对递归有了初步的认识,接下来我会推出实际应用型的递归。喜欢这篇文章的话点一波关注不迷路~ 后续会继续推出我的算法学习心得哦

这篇关于递推与递归(一)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!