递推/递归既可以理解成是一种算法思想,也可以理解成是一种算法的实现方式。
分治算法策略主要通过递归实现,大规模问题分解成小规模问题;动态规划算法主要通过递推实现。
递归和递推的核心其实都是递推式。
递归:从上到下 从未知到已知解决问题。
递推:从下到上 从已知到未知解决问题。
第3章 递归与分治策略
分治法的设计思想:将一个难以直接解决的大问题,分解成一些规模较小的相同问题,以便各个击破,分而治之。
直接或间接地调用自身的算法/函数称为递归算法/函数。
用递归解决问题时,关键是确定递归体和递归出口。
分治法的基本思想是将一个规模为n的问题分解为k个规模较小的子问题,这些子问题互相独立且与原问题相同。
子问题的解可以合并为该问题的解。
递归程序的优点: 在计算机算法设计与分析中,使用递归技术往往使函数的定义和算法的描述简洁且易于理解。
递归程序的缺点: 递归算法解题的运行效率较低 在递归调用过程中,系统为每一层的返回点、局部变量等开辟了堆栈来存储。递归次数过多容易造成堆栈溢出等。
01:寻找中位数 (快速排序版)
总时间限制:
100ms
内存限制:
65535kB
描述
在N(1 <= N <= 100001 且N为奇数)个数中,找到中位数。
输入
第1行:N
第2行:N个整数
输出
输入的第2行N个整数的中位数。
样例输入
5 2 4 1 3 5
样例输出
3
#include <iostream>
using namespace std;
void quick_sort(int *a,int l,int r){
if(l<r){
int i=l;
int j=r;
int x=a[i];
while(i<j){
while(i<j&&a[j]>=x){
j--;
}
if(i<j){
a[i]=a[j];
}
while(i<j&&a[i]<x){
i++;
}
if(i<j){
a[j]=a[i];
}
}
a[i]=x;
quick_sort(a,l,i-1);
quick_sort(a,i+1,r);
}
}
int main()
{
int n;
cin>>n;
int a[n];
for(int i=0;i<n;i++)
{
cin>>a[i];
}
int mid=sizeof(a)/sizeof(a[0])/2;
quick_sort(a,0,n);
cout<<a[mid];
return 0;
}
02:半数集(分治法)
总时间限制:
65535ms
内存限制:
65535kB
描述
给定一个自然数n,由n 开始可以依次产生半数集set(n)中的数如下。
(1) n∈set(n);
(2) 在n 的左边加上一个自然数,但该自然数不能超过最近添加的数的一半;
(3) 按此规则进行处理,直到不能再添加自然数为止。
例如,set(6)={6,16,26,126,36,136}。半数集set(6)中有6 个元素。
输入
输入一个自然数n
输出
半数集中数的个数
样例输入
6
样例输出
6
#include <iostream>
using namespace std;
int a[1001];
int comp(int n)
{
int ans=1;
if(a[n]>0) return a[n];
for(int i=1;i<=n/2;i++)
ans+=comp(i);
a[n]=ans;
return ans;
}
int main()
{
int n;
cin>>n;
cout<<comp(n)<<endl;
return 0;
}
03:众数及重数
总时间限制:
1000ms
内存限制:
1000kB
描述
给定含有n个元素的多重集合S,每个元素在S中出现的次数称为该元素的重数。多重集S中重数最大的元素称为众数。例如,S={1,2,2,2,3,5}。多重集S的众数是2,其重数为3。对于给定的n个自然数组成的多重集S,计算S的众数及其重数 。
输入
输入集合大小n及n个数
输出
输出两行
第一行为众数
第二行为重数
样例输入
6 1 2 2 2 3 5
样例输出
2 3
#include <iostream>
using namespace std;
void find(int a[],int n){
int i;
int c[n];
for (i=0;i<n;i++){
c[i]=0;
}
for (i = 0; i < n;)
{
c[i]=1;
int j=i;
while(a[i]==a[j]){
if (a[i]==a[i+1])
{
i++;c[j]++;
}
else{
i++;
}
}
}
int max=c[0],k=0;
for (int i = 0; i < n; i++)
{
if(max<c[i]) {
max=c[i];
k=i;
}
}
cout<<a[k]<<endl;
cout<<max<<endl;
}
int main(int argc, char const *argv[])
{
int n;
cin>>n;
int a[n];
for(int i=0;i<n;i++)
cin>>a[i];
find(a,n);
return 0;
}
04:递归求平方和函数(递归法)
总时间限制:
1000ms
单个测试点时间限制:
100ms
内存限制:
65535kB
描述
用递归函数,求f(n),n的值由主函数输入。
输入
输入n的值
输出
输出1到n的平方和
样例输入
4
样例输出
30
#include <iostream>
using namespace std;
int main()
{
int n;
cin>>n;
int sum=0;
for(int i=1;i<=n;i++)
{
sum=sum+i*i;
}
cout<<sum<<endl;
return 0;
}
05:放苹果(盘子相同)
总时间限制:
65536ms
单个测试点时间限制:
65535ms
内存限制:
65535kB
描述
把M个同样的苹果放在N个同样的盘子里,允许有的盘子空着不放,问共有多少种不同的分法?(用K表示)5,1,1和1,5,1 是同一种分法。
输入
苹果个数m 和盘子个数n(0<=M,1<=N<=10)
输出
不同的放法数目
样例输入
7 3
样例输出
8
#include <iostream>
using namespace std;
int main()
{
int m,n,dp[15][15];
cin>>m>>n;
for(int i=0;i<=m;i++){
dp[i][0]=0;
dp[i][1]=1;
}
for(int i=1;i<=n;i++)
dp[0][i]=1;
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
if(i<j)
dp[i][j]=dp[i][i];
else
dp[i][j]=dp[i][j-1]+dp[i-j][j];
}
}
cout<<dp[m][n]<<endl;
return 0;
}
06:二路归并排序
总时间限制:
1000ms
内存限制:
65536kB
描述
给定两个n个有序数和m个有序数这样两组数,要求输出两组数合并后的有序数列。
输入
一行一个n,m分别代表接下俩会有n个有序数和m个有序数。
一行一组拥有n个数的从小到大的有序数列。
一行一组拥有m个数的从小到大的有序数列。
输出
这n+m个数的有序形式,要求从小到大。
样例输入
5 4 1 6 7 8 10 2 5 6 7
样例输出
1 2 5 6 6 7 7 8 10
#include <iostream>
using namespace std;
class Sort
{
public:
Sort(int r[ ], int n);
~Sort( );
void binarySearch(int first, int last);
void Print( );
private:
void binary(int first1, int last1, int last2);
int *data;
int length;
};
Sort :: Sort(int r[ ], int n)
{
data = new int[n];
for (int i = 0; i < n; i++)
data[i] = r[i];
length = n;
}
Sort :: ~Sort( )
{
delete[ ] data;
}
void Sort :: binary(int first1, int last1, int last2)
{
int *temp = new int[length];
int i = first1, j = last1 + 1, k = first1;
while (i <= last1 && j <= last2)
{
if (data[i] <= data[j])
temp[k++] = data[i++];
else
temp[k++] = data[j++];
}
while (i <= last1)
temp[k++] = data[i++];
while (j <= last2)
temp[k++] = data[j++];
for (i = first1; i <= last2; i++)
data[i] = temp[i];
delete[ ] temp;
}
void Sort ::binarySearch(int first, int last)
{
if (first == last)
return;
else
{
int mid = (first + last) / 2;
binarySearch(first, mid);
binarySearch(mid + 1, last);
binary(first, mid, last);
}
}
void Sort :: Print( )
{
for (int i = 0; i < length; i++)
cout << data[i] << " ";
cout << endl;
}
int main() {
int n1;
cin >> n1;
int n2;
cin >> n2;
int arr[n1+n2];
for(int i = 0; i < n1+n2; i ++ ){
cin >> arr[i];
}
Sort L(arr, n1+n2);
L.binarySearch(0, n1+n2 - 1);
L.Print( );
return 0;
}
08:排队购票
总时间限制:
10000ms
内存限制:
10000kB
描述
一场球赛开始前,售票工作正在紧张进行中。 每张球票为50元, 有m+n个人排队等待购票, 其中有m 个人手持50元的钞票, 另外n个人手持100元的钞票。 求出这m+n个人排队购票,使售票处不至出现找不开钱的局面的不同排队种数 。(约定:开始售票时售票处没有零钱;拿同样面值钞票的人对换位置为同一种排队。)
输入
输入 m,n(m 持有50元的人数,n持有100元的人数)
输出
m+n个人的排队种书
样例输入
1 1
样例输出
1
#include <iostream>
using namespace std;
int f(int m,int n)
{
if(n==0){
return 1;
}
else if(m<n){
return 0;
}
else{
return f(m,n-1)+f(m-1,n);
}
}
int main()
{
int m;int n;
cin>>m>>n;
cout<<f(m,n)<<endl;
return 0;
}
09:输出不重复的数
总时间限制:
10000ms
内存限制:
10000kB
描述
输入n个数,从小到大将他们输出,重复的数只输出一次
输入
输入两行。
第一行: n,代表要输入的数的个数
第二行: n个数
输出
从小到大输出不重复的数
样例输入
5 1 2 3 4 5
样例输出
1 2 3 4 5
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
int n;
cin>>n;
int a[n];
int i;
for(i=0;i<n;i++)
cin>>a[i];
sort(a,a+n);
cout<<a[0]<<" ";
for(i=1;i<n;i++)
if(a[i]!=a[i-1])
cout<<a[i]<<" ";
return 0;
}
10:全排列(递归法)
总时间限制:
65535ms
内存限制:
65535kB
描述
输入一个n,输出1~n之间数的全排列(n<=5)
输入
n
输出
全排列
样例输入
3
样例输出
1 2 3 1 3 2 2 1 3 2 3 1 3 2 1 3 1 2
#include <iostream>
using namespace std;
void Perm(int list[],int k,int m)
{
if(k==m)
{
for(int i=0;i<=m;i++)
cout<<list[i]<<" ";
cout<<endl;
}
else
for(int j=k;j<=m;j++)
{
swap(list[k],list[j]);
Perm(list,k+1,m);
swap(list[k],list[j]);
}
}
int main()
{
int n;
cin>>n;
int list[]={1,2,3,4,5};
Perm(list,0,n-1);
return 0;
}
11:求n!(递归法)
可用下面的递归公式表示:
#include <iostream>
using namespace std;
int fac(int n)
{
int f;
if(n<0)
cout<<"n<0,不满足阶乘存在条件";
else if(n==0||n==1)
f=1;
else f=fac(n-1)*n;
return(f);
}
int main()
{
int n,y;
cout<<"输入一个大于0的数n:";
cin>>n;
y=fac(n);
cout<<n<<"的阶乘为:"<<y<<endl;
return 0;
}
12:斐波那契数列(递归法)
用递归方法求菲波那契(Fibonacci)数列。
这个数列有如下特点:第1、2两个数为1、1。从第3个数开始,该数是其前面两个数之和。即:
#include <iostream>
using namespace std;
int fibonacci(int n)
{
if(n==0||n==1)
return 1;
else
return fibonacci(n-1)+fibonacci(n-2);
}
int main()
{
int n,y;
cout<<"输入一个大于等于0的数n:";
cin>>n;
y=fibonacci(n);
cout<<n<<"的斐波那契数列为:"<<y<<endl;
return 0;
}
13:猴子摘桃问题(递归法)
猴子第一天采摘了一些桃子,第二天吃了第一天的一半多一个,第三天吃了第二天的一半多一个...直到第十天就剩下一个。问:猴子第一天摘了多少桃子?
递推关系: f(n)=f(n-1)/2-1
f(n-1)=(f(n)+1)*2
边界条件:f(10)=1
#include <iostream>
using namespace std;
int func(int n)
{
if(n==10)
return 1;
else
return (func(n+1)+1)*2;
}
int main()
{
int n;
cout<<"输入摘桃的天数n:";
cin>>n;
cout<<"第"<<n<<"天有"<<func(n)<<"个桃子!"<<endl;
return 0;
}
14:十进制转换为二进制(递归法)
#include <iostream>
using namespace std;
void db(int n)
{
if(n==0)
return ;
else{
db(n/2);
cout<<n%2;
}
}
int main()
{
int n;
cout<<"输入一个十进制数:";
cin>>n;
if(n==0)
cout<<"0"<<endl;
else{
db(n);
}
cout<<endl;
return 0;
}
15:逆序输出一个正数中的每一位数(递归法)
#include <iostream>
using namespace std;
int shuchu(int n)
{
if(n/10==0){
cout<<n;
}
else{
cout<<n%10<<" ";
shuchu(n/10);
}
}
int main()
{
int n;
cout<<"输入一个正整数n:";
cin>>n;
shuchu(n);
cout<<endl;
return 0;
}
16:整数因子分解问题(分治法)
大于1的正整数n可以分解为: 当n=12时,共有8种不同的分解式:
对于给定的正整数n,编程计算n共有多少种不同的分解式。
#include <iostream>
using namespace std;
int total;
void solve(int n)
{
if(n==1) total++;
else
for(int i=2;i<=n;i++)
if(n%i==0)
solve(n/i);
}
int main()
{
int n;
while(cin>>n)
{
total=0;
solve(n);
cout<<total;
}
}
17:棋盘覆盖(分治法)
在一个2k×2k个方格组成的棋盘中,恰有一个方格与其他方格不同,称该方格为一特殊方格,且称该棋盘为一特殊棋盘。
在棋盘覆盖问题中,要用图示的4种不同形态的L型骨牌覆盖给定的特殊棋盘上除特殊方格以外的所有方格,且任何2个L型骨牌不得重叠覆盖。
#include <iostream>
using namespace std;
#define N 4
int tile=1;
int board[N][N];
void CB(int tr,int tc,int dr,int dc,int size)
{
if(size==1) return;
int t=tile++;//L型骨牌顺序号
int s=size/2;//分割棋盘
//处理左上角子棋盘
if(dr<tr+s&&dc<tc+s)//特殊方格在此棋盘中
CB(tr,tc,dr,dc,s);
else{//用t号L型骨牌覆盖右下角
board[tr+s-1][tc+s-1]=t;
//覆盖其余方格
CB(tr,tc,tr+s-1,tc+s-1,s);
}
//处理右上角子棋盘
if(dr<tr+s&&dc>=tc+s)//特殊方格在此棋盘中
CB(tr,tc+s,dr,dc,s);
else{//此棋盘中无特殊方格
//用t号L型骨牌覆盖左下角
board[tr+s-1][tc+s]=t;
//覆盖其余方格
CB(tr,tc+s,tr+s-1,tc+s,s);
}
// 处理左下角子棋盘
if (dr >= tr + s && dc < tc + s)// 特殊方格在此棋盘中
CB(tr+s, tc, dr, dc, s);
else {
board[tr + s][tc + s - 1] = t;
// 覆盖其余方格
CB(tr+s, tc, tr+s, tc+s-1, s);
}
// 处理右下角子棋盘
if (dr >= tr + s && dc >= tc + s)// 特殊方格在此棋盘中
CB(tr+s, tc+s, dr, dc, s);
else {
board[tr + s][tc + s] = t;
// 覆盖其余方格
CB(tr+s, tc+s, tr+s, tc+s, s);
}
}
int main()
{
cout<<"输入棋盘k值:";
int n,dr,dc;
cin>>n;
cout<<"构成一个2^"<<n<<"X2^"<<n<<"的矩阵";
cout<<"输入特殊方格的行号和列号:";
cin>>dr>>dc;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
board[i][j]=0;
CB(0,0,dr,dc,2^n);
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
cout<<board[i][j]<<" ";
cout<<endl;
}
}
18:找出n个元素中第k小的元素(分治法)
元素选择问题:给定线性序集中n个元素和一个整数k,1≤k≤n,要求找出这n个元素中第k小的元素。
对于给定的n个元素的数组a[0:n-1],要求从中找出第k小的元素。
输入:输入有多组测试例。 对每一个测试例有2行,第一行是整数n和k(1≤k<n≤1000),第二行是n个整数。
输出:第k小的元素。
#include<iostream>
#include<algorithm>
using namespace std;
int a[101];
int select(int left,int right,int k)
{
if(left>=right)//递归出口
return a[left];
int i=left;int j=right+1;
int p=a[left];//根据快速排序,最左边元素作为分界
while(true)
{
do
{
i=i+1;
}while(a[i]<p);//从左到右
do
{
j=j-1;
}while(a[j]>p);//从右到左
if(i>=j)
break;//一趟排序结束
swap(a[i],a[j]);
}
if(j-left+1==k)//j-left+1 :左区元素的个数
return p;
a[left]=a[j];
a[j]=p;
if(j-left+1<k)//在右区域找
return select(j+1,right,k-j+left-1);
else
return select(left,j-1,k);
}
int main()
{
int n,k;//n个元素,找第k小的元素
cin>>n>>k;
for(int i=1;i<=n;i++)
cin>>a[i];
//sort(a+1,a+n+1);
cout<<select(1,n,k)<<endl;
return 0;
}