学习目标:
第十二届蓝桥杯C++第四次培训记录:递归与分治的四道思考题整理总结
学习内容:
问题1:双色汉诺塔整体移动分析过程
1、最简单情况(n==2)
a->b
a->c
b->c
2、递推公式:(将原问题缩减成规模为n-2的子问题)
第一步: n-2层 a->c
第二步: a->b
第三步: n-2层 c->b
第四步: a->c
第五步: n-2层 b->a
第六步: b->c
第七步: n-2层 a->c
- 双色汉诺塔整体移动要求n为偶数,所以递推公式中通过缩减成规模为n-2来控制每次递归的子问题中n也为偶数。
过程如图示
代码如下
#include <iostream>
using namespace std;
//双色汉诺塔整体移动
void hanoiTower2(int n,char A,char B,char C)
{
if(n==2)
{
//a->b
cout<<A<<"---->"<<B<<endl;
//a->c
cout<<A<<"---->"<<C<<endl;
//b->c
cout<<B<<"---->"<<C<<endl;
}
else
{
//第一步: n-2 层 a->c
hanoiTower2(n-2,A,B,C);
//第二步: a->b
cout<<A<<"---->"<<B<<endl;
//第三步: n-2层 c->b
hanoiTower2(n-2,C,A,B);
//第四步: a->c
cout<<A<<"---->"<<C<<endl;
//第五步: n-2层 b->a
hanoiTower2(n-2,B,C,A);
//第六步: b->c
cout<<B<<"---->"<<C<<endl;
//第七步: n-2层 a->c
hanoiTower2(n-2,A,B,C);
}
}
int main()
{
int n;
cin>>n;
hanoiTower2(n,'A','B','C');
return 0;
}
经过观察,发现双色汉诺塔整体移动与基本的单色汉诺塔移动完全一致,以n=4的情况为例:
使用数学归纳法,可以证明双色汉诺塔整体移动与基本的单色汉诺塔移动完全一致。
问题2:双色汉诺塔分色移动分析过程
1、最简单情况(n==2)
指定上层分到B柱,下层分到C柱
a->b
a->c
2、递推公式:(将原问题缩减成规模为n-2的子问题)
第一步:把n-2层 整体从A移动到C
第二步:a->b
第三步:把n-2层 整体从C移动到B
第四步:a->c
第五步:把n-2层 整体从B移动到A
第六步:对(n-2)层分色
- 前五歩做的都是移动操作,直接调用双色汉诺塔整体移动的函数即可。
- 关键点是第五歩,要将n-2层从c移回a,因为要保持子问题与原问题在形式上一致性(从a分色到b、c),方便递归调用。
- 第六歩是调用分色函数自身实现递归。
过程如图示
代码如下:
#include <iostream>
using namespace std;
//双色汉诺塔整体移动
void hanoiTower2(int n,char A,char B,char C)
{
if(n==2)
{
cout<<A<<"---->"<<B<<endl;
cout<<A<<"---->"<<C<<endl;
cout<<B<<"---->"<<C<<endl;
}
else
{
hanoiTower2(n-2,A,B,C);
cout<<A<<"---->"<<B<<endl;
hanoiTower2(n-2,C,A,B);
cout<<A<<"---->"<<C<<endl;
hanoiTower2(n-2,B,C,A);
cout<<B<<"---->"<<C<<endl;
hanoiTower2(n-2,A,B,C);
}
}
//分色
void hanoiTower2Separate(int n,char A,char B,char C)
{
if(n==2)
{
cout<<A<<"---->"<<B<<endl;
cout<<A<<"---->"<<C<<endl;
}
else
{
hanoiTower2(n-2,A,B,C);
cout<<A<<"---->"<<B<<endl;
hanoiTower2(n-2,C,A,B);
cout<<A<<"---->"<<C<<endl;
hanoiTower2(n-2,B,C,A);
hanoiTower2Separate(n-2,A,B,C);
}
}
int main()
{
int n;
cin>>n;
hanoiTower2Separate(n,'A','B','C');
return 0;
}
问题3:用分治算法在一个元素集合中寻找最大元素和最小元素的问题。
递归是一种技术,分治是一种思想。
使用分而治之的方法,一共三步,分、治、合。
分:每一次都把区间以(i+j)/2为分界,把集合分成两个子区间。
治:最小的子问题为,经过划分,该区间只剩下一个元素,那么这个元素同时是该区间的最大值与最小值;该区间剩下两个元素,那么较大的为该区间的最大值,较小的为该区间的最小值。(经验证,最小子问题可以缩减为只有一个元素,两个个元素的情况可以删去)
合:用max1和min1来保存前半区的最大最小值,用max2和min2来保存后半区的最大最小值,比较两个子区间内的最大值与最小值,选出两个区间内较大的局部最大值与较小的局部最小值,最终可以得到全局的最大值和最小值。注意到此函数使用了引用的方法,将函数内的局部变量传递了出来。
#include <iostream>
using namespace std;
#include<cmath>
int l[10]= {26,64,85,4,14,35,94,7,5,69};
int mini,maxi;
void maxmin(int i,int j,int &max1,int &min1)
{
int min2,max2;
if(i==j)
max1=min1=l[i];
else if(i==j-1)
{
if(l[i]<l[j])
{
max1=l[j];
min1=l[i];
}
else
{
max1=l[i];
min1=l[j];
}
}
else
{
int m=(i+j)/2;
maxmin(i,m,max1,min1);
maxmin(m+1,j,max2,min2);
if(max1<max2)
max1=max2;
if(min1>min2)
min1=min2;
}
}
int main()
{
maxmin(0,9,maxi,mini);
cout<<maxi<<endl;
cout<<mini<<endl;
return 0;
}
问题4:分治法青蛙过河
一条小溪尺寸不大,青蛙可以从左岸跳到右岸,在左岸有一石柱L,面积只容得下一只青蛙落脚,同样右岸也有一石柱R,面积也只容得下一只青蛙落脚。有一队青蛙从尺寸上一个比一个小。我们将青蛙从小到大,用1,2,…,n编号。规定初始时这队青蛙只能趴在左岸的石头L上,按编号一个落一个,小的落在大的上面。不允许大的在小的上面。在小溪中有S根石柱,有y片荷叶,规定溪中的柱子上允许一只青蛙落脚,如有多只同样要求按编号一个落一个,大的在下,小的在上,而且必须编号相邻。对于荷叶只允许一只青蛙落脚,不允许多只在其上。对于右岸的石柱R,与左岸的石柱L一样允许多个青蛙落脚,但须一个落一个,小的在上,大的在下,且编号相邻。当青蛙从左岸的L上跳走后就不允许再跳回来;同样,从左岸L上跳至右岸R,或从溪中荷叶或溪中石柱跳至右岸R上的青蛙也不允许再离开。问在已知溪中有S根石柱和y片荷叶的情况下,最多能跳过多少只青蛙?
分析:
本题属于分析推理递推式复杂,而程序编写简单的类型。对于多个变量的问题,首先考虑孤立某个变量,分析其对问题解的影响。
- 定义出最多跳过青蛙数量以柱子数量和荷叶数量为变量的函数 Jump(s,y) 。其中: S 为河中柱子数 ;y为荷叶数。
- 因为荷叶上仅能停留一只青蛙,柱字上可以停留多只青蛙,所以先考虑孤立出荷叶的变量进行分析,令柱子数量为0。可以较为简单地归纳出Jump(0,y)=y+1。
- 接下来考虑河中有1根柱子s1的情况Jump(1,y),前y+1只通过y片荷叶先堆在河中的柱子s1上,后y+1只通过y片荷叶直接堆到对岸的柱子上,最后前y+1只从河中的柱子s1,通过y片荷叶堆到对岸的柱子上,所以最多能跳过2*(y+1)只青蛙,即Jump(1,y)=2*(y+1)。
- 暂时没有观察出什么,继续考虑河中有2根柱子s1和s2的情况Jump(2,y),根据前文分析,前2*(y+1)只青蛙可以借助柱子s1与y片荷叶先堆在柱子s2上,后2*(y+1)只青蛙可以借助柱子s1与y片荷叶直接堆在对岸的柱子上,最后前2*(y+1)只青蛙从柱子s2借助s1与y片荷叶堆到对岸的柱子上。综上最多能跳过4*(y+1)只青蛙,即Jump(2,y)=4*(y+1)。
- 至此,可以归纳得出,每多一根柱子,最多能跳过的青蛙数量是原来的两倍,最终得出递推式Jump(s,y)=2*(s-1,y)。边界条件为s=0时,Jump(0,y)=y+1。
经过上述分析,编写代码异常简单,代码如下:
#include<stdio.h>
int Jump(int S, int y)
{
if(S == 0) return( y + 1 );
return( 2 * Jump(S-1, y));
}
int main( )
{
int S, y;
printf("请输入石柱和荷叶的数目:");
scanf("%d %d", &S, &y);
printf("最多有 %d 只青蛙过河\n", Jump(S, y));
return 0;
}