pascal中级教程第二章递归与递推
算法总结之递推与递归

算法总结之递推与递归递推算法递归算法⼤致包括两⽅⾯的内容:1)递归起点; 2)递归关系递推起点递归起点⼀般由题⽬或者实际情况确定,不由递归关系推出。
如果⽆法确定递归起点,那么递归算法就⽆法实现。
可见,递归起点是递归算法中的重要⼀笔。
递推关系递归关系是递归算法的核⼼。
常见的递归关系有以下⼏项:1)⼀阶递推;2)多阶递推;3)间接递推;4)逆向递推;5)多维递推。
下⾯通过栗⼦来详细介绍⼀下上述类别的递推关系。
1. ⼀阶递推在计算f(i)时,只⽤到前⾯项中的⼀项,如等差数列。
公差为3的等差数列,其递推关系为:f(i)=f(i-1)+3eg. 平⾯上10条直线最多能把平⾯分成⼏部分?分析:以直线数⽬为递推变量,假定i条直线把平⾯最多分成f(i)部分,则f(i-1)表⽰i-1条直线把平⾯分成的最多部分。
在i-1条直线的平⾯上增加直线i,易得i与平⾯上已经存在了的i-1条直线最多各有⼀个交点,即直线i最多被分成i段,⽽这i段将会依次将平⾯⼀分为⼆,即直线i将最多使平⾯多增加i部分。
所以,递推关系可表⽰为:f(i)=f(i-1)+i易得当0条直线时,平⾯为1部分。
所以f(0)=1为递推起点。
上述分析可⽤下⾯代码表⽰(c++):#define MAX 100int f[MAX] //存放f(i)int lines(int n){//输⼊n为直线数⽬//输出最多部分数int i;f(0)=1;for(i=1;i<=n;i++){f[i]=f[i-1]+3;}return f[i];}2. 多阶递推在计算f(i)时,要⽤到前⾯计算过的多项,如Fibonacci数列。
eg.求Fibonacci的第10项。
分析:总所周知,Fibonacci数列中的第n项等于第n-1项加上n-2项。
所以递推关系为f(i)=f(i-1)+f(i-2);且f[0]=f[1]=1。
C++代码如下:#define MAX 100int f[MAX];int fib(int n){//输⼊n为项数//输出第n个fib数int i;f[0]=0;f[1]=1;for(i=2;i<=n;i++){f[i]=f[i-1]+f[i-2];}return f[n]}3. 间接递推在计算f[i]时需要中间量,⽽计算中间量要⽤到之前计算过的项。
递归递推

第二章递规与递推2.1 遍历问题源程序名travel.???(pas, c, cpp)可执行文件名travel.exe输入文件名travel.in输出文件名travel.out【问题描述】我们都很熟悉二叉树的前序、中序、后序遍历,在数据结构中常提出这样的问题:已知一棵二叉树的前序和中序遍历,求它的后序遍历,相应的,已知一棵二叉树的后序遍历和中序遍历序列你也能求出它的前序遍历。
然而给定一棵二叉树的前序和后序遍历,你却不能确定其中序遍历序列,考虑如下图中的几棵二叉树:a a a a/ / \ \b b b b/ \ / \c c c c所有这些二叉树都有着相同的前序遍历和后序遍历,但中序遍历却不相同。
【输入】输A数据共两行,第一行表示该二叉树的前序遍历结果s1,第二行表示该二叉树的后序遍历结果s2。
【输出】输出可能的中序遍历序列的总数,结果不超过长整型数。
【样例】trave1.in trave1.outabc 1bca【算法分析】在肯定有解的情况下,上述算法最终可以递归调用到0、1个结点,如果有多组解,那么调用到两个结点时,如先序为ab、后序为ba,此时有可能有如下两种结构:a a/ \b b这两种结构的中序遍历结果分别为:ba、ab,有两种。
根据分步相乘的原理,对比两个字符串,每出现一次如上的情况,可能有的结构数目(结构不同,中序遍历结果也不同,因此可能有的二叉树结构的数目就是可能有的中序遍历结果数目)就乘以2一次,最终得到总的数目。
这也可以理解为一种递推的方法。
从这里可以看到,在肯定有解的情况下,给定先序遍历的结果和后序遍历的结果,可能有2n种可能的结构,也就是中序遍历可能得到2n种不同的结果,其中n>=0。
那么这里的n最大可能是多少呢?可以证明n的最大值为字符串的长度加1整除2。
【参考程序】program fgdjfk;var i,j,m,sum:longint;s1,s2:string;beginassign(input,'travel.in');reset(input);assign(output,'travel.out'); rewrite(output);readln(s1);readln(s2);sum:=1;for i:=1 to length(s1)-1 do beginm:=pos(s1[i],s2);if m>1 thenif s1[i+1]=s2[m-1] thensum:=sum*2; end;writeln(sum);close(input);close(output);end.2.2 产生数源程序名build.???(pas, c, cpp)可执行文件名build.exe输入文件名build.in输出文件名build.out【问题描述】给出一个整数n(n<1030)和m个变换规则(m≤20)。
Pascal_讲课稿(02)

Pascal语言上课笔记(二)2012-1临沂专用(勿外传)第十三节递推与递归递推和递归是编程中常用的基本算法,前面我们已经在个别程序中接触过,下面我们专门详细地研究一下递推与递归的算法。
一、递推1、递推是计算机数值计算中的一个重要算法,其思想是通过数学推导,将复杂的运算化解为若干重复的简单运算,以充分发挥计算机善长于重复处理的特点。
我们先看一道例题:例题1:切煎饼:王小二自夸刀工不错,有人放一张大的煎饼在砧板上,问他:“饼不许离开砧板,切100刀最多能分成多少块?”找规律,如下图:令q(n)为切n刀能分成的块数,从上图中可见:q(1)=1+1=2q(2)=1+1+2=4q(3)=1+1+2+3=7q(4)=1+1+2+3+4=11在切法上让每两条线都有交点,则有q(n)=q(n-1)+nq(0)=1有了递推方程,程序就很简单。
程序如下:var //递推例子程序名:0001089ditui.pasi,n:integer;knife:array[0..100] of integer;beginreadln(n);knife[0]:=1;for i:=1 to n dobeginknife[i]:=knife[i-1]+i;writeln(i:3,knife[i]:5);end;end.例题2:植树节那天,有五位同学参加了植树活动,他们完成植树的棵数都不相同。
问第一位同学植了多少棵时,他指着旁边的第二位同学说比他多植了两棵;追问第二位同学,他又说比第三位同学多植了两棵;…如此,都说比另一位同学多植两棵。
最后问到第五位同学时,他说自己植了10棵。
到底第一位同学植了多少棵树?分析:设第一位同学植树的棵数为a1,欲求a1,需从第五位同学植树的棵数a5入手,根据“多两棵”这个规律,按照一定顺序逐步进行推算:①、a5=10;②、a4=a5+2=12;③、a3=a4+2=14;④、a2=a3+2=16;⑤、a1=a2+2=18;程序如下:var //递推(植树)程序名:0001090dituizhishu.pasi,a:byte;begina:=10;for i:=1 to 4 do a:=a+2;writeln('The num of tree is : ',a);readln;end.2、递推算法以初始{起点}值为基础,用相同的运算规律,逐次重复运算,直至运算结束。
第二讲 递归与递推

江 苏 省 青 少 年 信 息 学 奥 林 匹 克 夏 令 营 C 层 次 教 学
例5、由m个A,n个B组成若干个排列。从某个排列 的位置1开始数,数到任意位置时都能保证A的个数 不少于B的个数,则称该排列为合理排列。例如: 当m=2,n=2时排列有 A A B B(合理) A B A B(合 理)A B B A(不合理) B B A A(不合理) 合理排列数有2 种 输入:只有一行两个整数m,n(1≤n≤m≤12)(用 空格分隔) 输出:一个整数(所有的合理排列数) 【样例】 输入 输出 32 5
递归过程例析
先以三个盘的移动为例,看一下移动过程。
江 苏 省 青 少 年 信 息 学 奥 林 匹 克 夏 令 营 C 层 次 教 学
2008年冬令营
递归过程例析
分析 1、当n=1时,只需要移动一次A---C; 2、当n=2时,需要移动三次; A---1---B; A---2---C; B---1---C; 3、当n>=3时,先将前n-1个盘子以C为 中介移到B柱子;再将第n个盘子移到 C柱子;最后将n-1个盘子以A为中介 从B柱移到C柱。
递归关系式——如何求? 运用函数的前驱值来计算函数当前值的关系式
江 苏 省 青 少 年 信 息 学 奥 林 匹 克 夏 令 营 C 层 次 教 学
2008年冬令营
递归
例3、用递归方法求两个正整数m和n的最大公 约数。
分析:求两个数的最大公约数可以用辗转 相除法,即求m与n的最大公约数等价于求(m mod n)的值与n的最大公约数,此时的n可以当 作新的m ,而(m mod n)的值当作新的n ,所 以原问题的求解又变成求新的m与n的最大公约 数问题,继续下去,直至(m mod n)为0,最大 公约数就是最终存放在n中的值。
递归递推区别分析与例题总结

递归递推区别分析与例题总结递归与递推⽂章⽬录特点递归(recursive)运⾏过程中⾃我调⽤,求解过程分为回溯和递推两个过程,占⽤内存多(栈数先积累后收缩,有可能爆栈),代码简洁但低效。
尾递归和递归区别⬇function story() {从前有座⼭,⼭上有座庙,庙⾥有个⽼和尚,⼀天⽼和尚对⼩和尚讲故事:story() // 尾递归,进⼊下⼀个函数不再需要上⼀个函数的环境了,得出结果以后直接返回。
}function story() {从前有座⼭,⼭上有座庙,庙⾥有个⽼和尚,⼀天⽼和尚对⼩和尚讲故事:story(),⼩和尚听了,找了块⾖腐撞死了 // ⾮尾递归,下⼀个函数结束以后此函数还有后续,所以必须保存本⾝的环境以供处理返回值。
}尾递归省内存、⾼效(相当于(或者说递推?))Python⽆法在语⾔中实现尾递归优化(Tail Call Optimization, TCO),所以采⽤了for, while等特殊结构代替recursive的表述(优化是编译器⼲的,发现尾递归结构就给优化了)。
递推(iterative)效率⽐递归⾼,尽量递推,除⾮只能递归。
例题递推例⼦⼀般都是数学题。
重点是找递推公式(也太好偷懒了吧)平⾯分割问题直线分割平⾯(基本结论)如果当前有 n 条直线,新增加⼀条直线(第 n+1 条直线),可以多出来 n 个交点(新的直线和之前所有的直线都有交点),⽽多出来 n 个交点对应到可以多出 n+1 个平⾯(⽐如从两条线,⼜新增⼀条线时,新的线和两条线都相交,作⽤在三个区域上,对这三个区域切分,增加三个平⾯)。
也即:S n+1=S n+(n+1)=1+(n+1)(n+2)2当平⾯上已有n-1条曲线将平⾯分割成a n-1个区域后,第n-1条曲线每与曲线相交⼀次,就会增加⼀个区域,因为平⾯上已有了n-1条封闭曲线,且第n条曲线与已有的每⼀条闭曲线恰好相交于两点,且不会与任两条曲线交于同⼀点,故平⾯上⼀共增加2(n-1)个区域,加上已有的a n-1个区域,⼀共有a n-1+2(n-1)个区域。
递推与递归

{2,4}∪{1}∪{3}
{3,4}∪{1}∪{2}
递归的应用举例
考虑一般情况,对于任意的含有n个元素 1、数据元素可以用抽象的公式表示
再者,把n个元素放进一个集合或把n个元素放进n个集合,方案数显然都是1,即k=1或k=n时,s(n,k)=1。
a ,a ,……,a 的集合s,放入k个无标号的 【讨论1】平面分割问题
{1,2,3,4,5}的情况:
{1},{2},{3},{4},{5},{1,3},{1,4},{1,5},
{2,4},{2,5},{3,5},{1,3,5}
--->f(5)
递推
f(1)=12=1 f(2)=12+22=5 f(3)=f(2)+f(1)*32+32=23 f(4)=f(3)+f(2)*42+42=119 ……… f(n)=f(n-1)+f(n-2)*n2+n2
递推
begin readln(n,m,x,y); fillchar(f,sizeof(f),0); fillchar(g,sizeof(g),true); g[x,y]:=false; for i:=1 to 8 do g[x+dx[i],y+dy[i]]:=false;
递推
if g[0,0] then f[0,0]:=1; for i:=1 to m do
{ g[i,j] = 1 } { j>0,g[0,j] = 0 } { i>0,g[i,0] = 0 } { i>0,j>0,g[i,j] = 0 }
递推
program ex2; const
maxn=20; maxm=20; dx:array[1..8] of integer=(2,1,-1,-2,-2,-1, 1, 2); dy:array[1..8] of integer=(1,2, 2, 1,-1,-2,-2,-1); var f:array[0..maxn,0..maxm] of int64; g:array[-2..maxn+2,-2..maxm+2] of boolean; n,m,x,y:integer; i,j:integer;
递推与递归
递归【例】编一个函数factorial 计算阶乘n!按过去程序设计思想,该函数应该写成:int factorial ( int n ) {int i,p;p=1;;for ( i=1 ; i<=n ; i=i+1 )p=p*i;return p ;}现在换一个角度考虑问题,n! 不仅是1*2*3* ... *n还可以定义成按照该定义,n! 就是一个简单的条件语句和表达式计算,可以编出如下函数:int factorial ( int n ) {if ( n==0 )return 1;elsereturn n*factorial(n-1);}问题是该函数对不对?在函数factorial 内又调用函数factorial 本身,行吗?回答是肯定的。
首先按作用域规则,在函数factorial 内又调用函数factorial 本身是合法的;其次C 系统保证上述调用过程执行的正确性,这就是递归。
从静态行文角度看,在定义一个函数时,若在定义它的内部,又出现对它本身的调用,则称该函数是递归的,或递归定义的。
从动态执行角度看,当调用一个函数时,在进入相应函数,还没退出(返回)之前,又再一次的调用它本身,而再一次进入相应函数,则称之为递归,或称之为对相应函数的递归调用。
称递归定义的函数为递归函数。
上述函数factorial 就是递归函数。
若计算5! ,使用函数调用factorial(5) ,计算过程如图10.1 。
实际问题有许多可以递归定义,采用递归方法来编程序十分方便和简单。
【例】X 的n 次幂,可以定义为计算它的递归函数是:float power ( float x, int n ) {if ( n==0 )return 1 ;elsereturn x * power(x,n-1) ;}递归程序设计的思想体现在:用逐步求精原则,首先把一个问题分解成若干子问题,这些子问题中有问题的与原始问题具有相同的特征属性,至多不过是某些参数不同,规模比原来小了。
递归与递推——精选推荐
递归与递推递推递归递归:从已知问题的结果出发,⽤迭代表达式逐步推算出问题的开始的条件,即顺推法的逆过程,称为递归。
递推:从已知道的若⼲项出发,利⽤递推关系依次推算出后⾯的未知项的⽅法,我们称为递推算法。
递推与递归不同:递归是从未知到已知逐步接近解决问题的过程,⽽递推从已知到未知。
递推算法是⼀种⽤若⼲步可重复运算来描述复杂问题的⽅法。
递推是序列计算中的⼀种常⽤算法。
通常是通过计算前⾯的⼀些项来得出序列中的指定项的值。
递推的关系式可以暴枚找规律,也可以化繁为简,例如铺砖问题,最后⼀列砖铺与不铺,以及最后两列砖铺与不铺的情况相加即可求出关系式。
⽽关于递归,就是函数中再次调取函数,从⽽使困难的问题化为“1+1”类型简单的问题,得出结果再还原,操作过程类似于“U”型。
递归的重点是找到递归关系和递归出⼝。
……(概念太多,直接摆题)经典例题统计奇数和偶数个3内存限制:128 MiB时间限制:1000 ms标准输⼊输出题⽬类型:传统评测⽅式:⽂本⽐较题⽬描述在所有的N位正整数中,有多少个数中有偶数个数字3?⼜有多少个数有奇数个3?由于结果可能很⼤,你只需要输出这个答案对12345取余的值。
输⼊格式输⼊⼀个数N(1<=N<=1000),输⼊形式为⽂件输⼊,以读到0或⽂件末尾结束。
输出格式对于每⼀个N位正整数,输出有多少偶数个3以及多少奇数个3,中间⽤空格隔开。
样例样例输⼊2样例输出73 17数据范围与提⽰分别找出奇数偶数的递推式样例说明:在所有的2位数字,包含0个3的数有72个,包含2个3的数有1个,共73个对于⼀位数,除3外,其他全为含有偶数个三(数组元素初始化),紧接着,对于两位数,13,23,30~39(除33外)【这⾥有9个数,也可以进⾏思考】,43,53,63,73,83,93含有奇数个三,再看三位数(差不多就可以找到规律……)。
声明:a数组存储含奇数个三的个数,b数组⽤于存储偶数i的数值 1 2 3 ……a[i] 1 17 674 ……b[i] 8 73 226 ……So,a[i]=(b[i-1]+a[i-1]*9)%12345,b[i]=(a[i-1]+b[i-1]*9)%12345;#include<cstdio>using namespace std;int main(){int n,a[10005],b[10005];a[1]=1,b[1]=8;for(int i=2;i<=1001;i++){a[i]=(b[i-1]+a[i-1]*9)%12345;b[i]=(a[i-1]+b[i-1]*9)%12345;}while(scanf("%d",&n)!=EOF){if(n==0)return 0;printf("%d %d\n",b[n],a[n]);}return 0;}Hanoi塔内存限制:128 MiB时间限制:1000 ms标准输⼊输出题⽬类型:传统评测⽅式:⽂本⽐较题⽬描述问题的提出:Hanoi塔由n个⼤⼩不同的圆盘和三根⽊柱a,b,c组成。
递归与递推公式的掌握技巧
递归与递推公式的掌握技巧掌握递归与递推公式的技巧在计算机科学领域中,递归和递推公式是重要的概念和工具。
递归是指在一个函数或过程内部调用自己,直到满足某个条件才返回结果。
递推公式是指通过前面的结果推导出后面的结果的公式,也称为递归公式。
递归和递推公式在算法设计、数据结构、数学等领域都得到了广泛应用。
递归和递推公式的掌握是编程和数学学习的重点之一。
在实际应用中,掌握递归和递推公式对程序的正确性、效率和可读性都是至关重要的。
本文将介绍如何掌握递归和递推公式的技巧,帮助读者更好地应用它们解决问题。
第一部分:递归的设计和实现递归的设计和实现是一项重要的技巧。
递归函数的设计和实现需要考虑以下几个方面:1.确定递归的终止条件递归的过程必须能够终止,否则会造成死循环或栈溢出等问题。
因此,在设计递归函数时必须确定递归的终止条件。
例如,计算斐波那契数列的递归函数可以定义如下:```int fibonacci(int n) {if (n <= 1) {return n;}else {return fibonacci(n - 1) + fibonacci(n - 2);}}```在这个例子中,当n小于或等于1时,递归终止,返回n的值。
如果n大于1时,递归调用fibonacci函数求解f(n-1)和f(n-2)的和。
递归终止条件的确定是函数正确实现的关键之一。
2.转化问题的规模递归通常是为了解决类似于“分而治之”的问题。
通过递归调用将原问题拆分成子问题,并在子问题得到解后合并得到原问题的解。
例如,快速排序算法就是一种递归的排序算法,它的实现如下:```void quick_sort(int arr[], int left, int right) {if (left < right) {int pivot = partition(arr, left, right);quick_sort(arr, left, pivot - 1);quick_sort(arr, pivot + 1, right);}}int partition(int arr[], int left, int right) {int pivot = arr[right];int i = left - 1;for (int j = left; j < right; j++) {if (arr[j] <= pivot) {i++;swap(&arr[i], &arr[j]);}}swap(&arr[i + 1], &arr[right]);return i + 1;}```在这个例子中,快速排序通过递归调用将原问题拆分为更小的子问题,并在子问题解决后进行合并。
递归与递推
1.递归与递推的区别:2.今天在学习递归和动态规划时有点迷糊了,两者无法区别,在网上差了下,总接如下:首先要清楚,递推就是迭代。
3. 1.递归其实就是利用系统堆栈,实现函数自身调用,或者是相互调用的过程.在通往边界的过程中,都会把单步地址保存下来,知道等出边界,再按照先进后出的进行运算,这正如我们装木桶一样,每一次都只能把东西方在最上面,而取得时候,先放进取的反而最后取出.递归的数据传送也类似.但是递归不能无限的进行下去,必须在一定条件下停止自身调用,因此它的边界值应是明确的.就向我们装木桶一样,我们不能总是无限制的往里装,必须在一定的时候把东取出来.比较简单的递归过程是阶乘函数,你可以去看一下.但是递归的运算方法,往往决定了它的效率很低,因为数据要不断的进栈出栈.这时递推便表现出它的作用了,所谓递推,就是免除了数据进出栈的过程.也就是说,不需要函数不断的向边界值靠拢,而直接从边界出发,直到求出函数值.比如,阶乘函数中,递归的数据流动过程如下:f(3){f(i)=f(i-1)*i}–>f(2)–>f(1)–>f(0){f(0)=1}–>f(1)–>f(2)–f(3){f(3)=6} 而递推如下:f(0)–>f(1)–>f(2)–>f(3)由此可见,递推的效率要高一些,在可能的情况下应尽量使用递推.但是递归作为比较基础的算法,它的作用不能忽视.所以,在把握这两种算法的时候应该特别注意.4. 2.递归是自顶向下逐步拓展需求,最后自下向顶运算。
即由f(n)拓展到f(1),再由f(1)逐步算回f(n)迭代是直接自下向顶运算,由f(1)算到f(n)。
2.爱拉托逊斯筛选法思想:对于不超过n的每个非负整数P,删除2*P, 3*P…,当处理完所有数之后,还没有被删除的就是素数。
int m =sqrt(double(n+0.5));for(int i =2; i <= m; i++)if(!vis[i]){for(int j = i*i; j <= n; j += i){vis[j]=1;}}#include <stdio.h>#include <string.h>#include <math.h>int vis[100];int n;int main(){scanf("%d", &n);int cnt =1;memset(vis, 0, sizeof(vis));int m =sqrt(double(n+0.5));for(int i =2; i <= m; i++)if(!vis[i]){for(int j = i*i; j <= n; j += i){vis[j]=1;//printf("%d\n", j);}}for(int i =2; i < n; i++){if(vis[i]==0){printf("%d ", i);cnt++;if(cnt %10==0)printf("\n");}}printf("\n cnt = %d\n", cnt);return0;}。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
第二章递归与递推2.1 遍历问题我们都很熟悉二叉树的前序、中序、后序遍历,在数据结构中常提出这样的问题:已知一棵二叉树的前序和中序遍历,求它的后序遍历,相应的,已知一棵二叉树的后序遍历和中序遍历序列你也能求出它的前序遍历。
然而给定一棵二叉树的前序和后序遍历,你却不能确定其中序遍历序列,考虑如下图中的几棵二叉树:a a a a/ / \ \b b b b/ \ / \c c c c所有这些二叉树都有着相同的前序遍历和后序遍历,但中序遍历却不相同。
【输入】输A数据共两行,第一行表示该二叉树的前序遍历结果s1,第二行表示该二叉树的后序遍历结果s2。
【输出】输出可能的中序遍历序列的总数,结果不超过长整型数。
【样例】trave1.in trave1.outabc 4bca【算法分析】根据二叉树先序遍历和后序遍历的特点,可以知道,先序遍历的第一个结点是后序遍历的最后一个结点,对于中序遍历来说却是中间的一个结点,这里所说的中间也只是相对而言的中间。
如果一棵二叉树的根结点没有左子树,那么先序遍历的第一个结点也是中序遍历的第一个结点,如果一棵二叉树的根结点没有右子树,那么先序遍历的第一个结点是中序遍历的最后一个结点。
我们这里还认为就是中序遍历的中间结点,上面两种情况只是特殊的情况。
设二叉树的结点总数为n(对于输入的字符串来说是它的长度),对于先序遍历的结果,第一个结点为根结点,从第二个结点到最后一个结点分为n种情况:根结点的左子树结点个数为n-1,右子树结点的个数为0;根结点的左子树结点个数为n-2,右子树结点的个数为1;……根结点的左子树结点个数为n-i,右子树结点的个数为i-1;{0<=i<=n-1);……根结点的左子树结点个数为0,右子树结点的个数为n-1。
根据这n种情况,分别将二叉树拆分为左子树和右子树,左子树结点个数为n-i,右子树结点的个数为i-l(0<=i<=n-1),先序遍历的结果是从第二个结点(字符)开始取,而后序遍历的结果里是从第1个结点字符开始取。
也就是说对于每一种情况,分两步处理:第一步在先序遍历和后序遍历的结果里取左子树,看是否符合规则,统计这部分可能有的中序遍历的结果数目;第二步在先序遍历和后序遍历的结果里取右子树,看是否符合规则,统计这部分可能有的中序遍历的结果数目。
这两步都递归调用了统计过程,不再递归调用的条件就是当统计的是空树或只有一个结点的树,这时返回的值是可能有的中序遍历结果数目。
结合“分类相加原理”和“分步相乘原理”,可以得到下面的递归函数:【思考与提高】上面的算法通过递归,结合统计的基本原理“分步相乘,分类相加”,从而统计出所有可能解的个数,如果输入的两个字符串没有解,上述算法同样能得到结果。
在肯定有解的情况下,上述算法最终可以递归调用到0、1个结点,如果有多组解,那么调用到两个结点时,如先序为ab、后序为ba,此时有可能有如下两种结构:a a/ \b b这两种结构的中序遍历结果分别为:ba、ab,有两种。
根据分步相乘的原理,对比两个字符串,每出现一次如上的情况,可能有的结构数目(结构不同,中序遍历结果也不同,因此可能有的二叉树结构的数目就是可能有的中序遍历结果数目)就乘以2一次,最终得到总的数目。
这也可以理解为一种递推的方法。
从这里可以看到,在肯定有解的情况下,给定先序遍历的结果和后序遍历的结果,可能有2n种可能的结构,也就是中序遍历可能得到2n种不同的结果,其中n>=0。
那么这里的n最大可能是多少呢?可以证明n的最大值为字符串的长度加1整除2。
递推的程序如下:2.2 产生数【问题描述】给出一个整数n(n<1030)和m个变换规则(m≤20)。
约定:一个数字可以变换成另一个数字,规则的右部不能为零,即零不能由另一个数字变换而成。
而这里所说的一个数字就是指一个一位数。
现在给出一个整数n和m个规则,要你求出对n的每一位数字经过任意次的变换(0次或多次),能产生出多少个不同的整数。
【输入】共m+2行,第一行是一个不超过30位的整数n,第2行是一个正整数m,接下来的m 行是m个变换规则,每一规则是两个数字x、y,中间用一个空格间隔,表示x可以变换成y。
【输出】仅一行,表示可以产生的不同整数的个数。
【样例】build.in build.out1 2 3 621 22 3【算法分析】如果本题用搜索,搜索的范围会很大(因为n可能有30位!),显然无法在规定的时间内出解。
而我们注意到本题只需计数而不需要求出具体方案,所以我们稍加分析就会发现,可以用乘法原理直接进行计数。
设F[i]表示从数字i出发可以变换成的数字个数(这里的变换可以是直接变换,也可以是间接变换,比如样例中的1可以变换成2,而2又可以变换成3,所以1也可以变换成3;另外自己本身不变换也是一种情况)。
那么对于一个长度为m位的整数a,根据乘法原理,能产生的不同的整数的个数为:F[a[1]]*F[a[2]]*F[a[3]]*…*F[a[m]]。
下面的问题是如何求F[i]呢?由于这些变换规则都是反映的数字与数字之间的关系,所以定义一个布尔型的二维数组g[0..9,0..9]来表示每对数字之间是否可以变换,初始时都为False;根据输入的数据,如果数字i能直接变换成数字j,那么g[i,j]置为True,这是通过一次变换就能得到的;接下来考虑那些间接变换可得到的数字对,很明显:如果i可以变为k,k又可以变为j,那么i就可以变为j,即:for k:=0 to 9 dofor i:=0 to 9 dofor j:=0 to 9 dog[i,j]=g[i,j]or(g[i,k] and g[k,j]);最后还要注意,当n很大时,解的个数很大,所以要用高精度运算。
2.3 出栈序列统计【问题描述】栈是常用的一种数据结构,有n个元素在栈顶端一侧等待进栈,栈顶端另一侧是出栈序列。
你已经知道栈的操作有两种:push和pop,前者是将一个元素进栈,后者是将栈顶元素弹出。
现在要使用这两种操作,由一个操作序列可以得到一系列的输出序列。
请你编程求出对于给定的n,计算并输出由操作数序列1,2,…,n,经过一系列操作可能得到的输出序列总数。
【输入】【输出】就一个数n(1≤n≤1000)。
一个数,即可能输出序列的总数目。
【样例】stack.in stack.out3 5【算法分析】在第一章练习里,我们通过回溯的方法计算并输出不同的出栈序列,这里只要求输出不同的出栈序列总数目,所以我们希望能找出相应的递推公式进行处理。
从排列组合的数学知识可以对此类问题加以解决。
我们先对n个元素在出栈前可能的位置进行分析,它们有n个等待进栈的位置,全部进栈后在栈里也占n个位置,也就是说n个元素在出栈前最多可能分布在2*n位置上。
出栈序列其实是从这2n个位置上选择n个位置进行组合,根据组合的原理,从2n个位置选n个,有C(2n,n)个。
但是这里不同的是有许多情况是重复的,每次始终有n个连续的空位置,n个连续的空位置在2n个位置里有n+1种,所以重复了n+1次。
所以出栈序列的种类数目为:C(2n,n)/(n+1)=2n*(2n-1)*(2n-2)…*(n+1)/n!/(n+1)=2n*(2n-1)*(2n-2)*…*(n+2)/n!。
考虑到这个数据可能比较大,所以用高精度运算来计算这个结果。
本题实际是一个经典的Catalan数模型。
有关Catalan数的详细解释请参考《组合数学》等书。
【思考与提高】我们知道,在某个状态下,所能做的操作(移动方法)无非有两种:(1)将右方的等待进栈的第一个元素进栈;(2)将栈顶的元素出栈,进入左边的出栈序列。
设此时右方、栈、左方的元素个数分别为a,b,c。
我们就能用(a,b,c)表示出当前的状态。
显然n=a+b+c,则c=n-a-b。
即已知a和b,c就被确定,所以我们可以用(a,b)来作为状态的表示方法。
则起始状态为(n,0),目标状态为(0,0)。
又由上面的两种移动方法,我们可类似的得到两种状态转移方式:再设f (a,b )为从状态(a,b )通过移动火车变为状态(0,0)的所有移动方法。
类似于动态规划的状态转移方程,我们可写出以下递归式:⎪⎩⎪⎨⎧=-=>+->>-++-=≤+)(此时只能做出栈操作)(此时只能做进栈操作)0()1,()0,0()1,1()0,0()1,()1,1()(),(a b a f b a b a f b a b a f b a f n b a b a f边界值:f (0,0)=1。
有了这个递归公式后,再写程序就比较简单了,请读者自己写出递归程序。
2.4 计数器【问题描述】一本书的页数为N,页码从1开始编起,请你求出全部页码中,用了多少个0,1,2,…,9。
其中—个页码不含多余的0,如N=1234时第5页不是0005,只是5。
【输入】一个正整数N(N≤109),表示总的页码。
【输出】共十行:第k行为数字k-1的个数。
【样例】count.in count.out11 1411111111【算法分析】本题可以用一个循环从1到n,将其拆为一位一位的数字,并加到相应的变量里,如拆下来是1,则count[1]增加1。
这种方法最简单,但当n比较大时,程序运行的时间比较长。
这种方法的基本程序段如下:for i:=1 to n do beginj:=i;while j>0 do begincount[j mod 10]:=count[j mod 10]+1;j:=j div 10;end;end;当n是整型数据时,程序执行的时间不会太长。
而n是长整型范围,就以n是一个9位数为例,当i执行到8位数时,每拆分一次内循环要执行8次,执行完8位数累计内循环执行的次数为:9*1+90*2+900*3+9000*4+90000*5+900000*6+9000000*7+90000000*8时间上让人不能忍受。
可以从连续数字本身的规律出发来进行统计,这样速度比较快,先不考虑多余的0的情况,假设从0000~9999,这一万个连续的数,0到9用到的次数都是相同的,一万个四位数,0到9这十个数字共用了40000次,每个数字使用了4000次。
进一步推广,考虑所有的n位数情况,从n个0到n个9,共10n个n位数,0到9十个数字平均使用,每个数字共用了n*10n-1次。
有了这样的规律后,可以从高位向低位进行统计,最后再减去多余的0的个数。
以n=3657为例:(用count数组来存放0到9各个数字的使用次数)最高位(千位)为3,从0千、1千到2千,000~999重复了3次,每一次从000到999,每个基本数字都使用了3*102=300次,重复3次,所以count[0]~count[9]各增加3*300;另外最高位的0到2作为千位又重复了1000次,count[0]~count[2]各增加1000,3作为千位用了657次(=n mod 100),因此count[3]增加657;接下来对百位6再进行类似的处理,0到9在个位和十位平均重复使用6*20次,所以count[0]~count[9]先各增加6*20,0到5作为百位重复使用了100次,所以count[0]~count[5]再各增加100,6作为百位在这里重复用了57次(=n mod 100);因此count[6]增加57;对十位和个位也进行相同的处理,得出count[0]~count[9]的值;最后再减去多算的0的个数。