《算法设计与分析实用教程》参考解答
1-1 加减得1的数学游戏
西西很喜欢数字游戏,今天他看到两个数,就想能否通过简单的加减,使最终答案等于1。而他又比较厌烦计算,所以他还想知道最少经过多少次才能得到1。
例如,给出16,9:16-9+16-9+16-9-9-9+16-9-9=1,需要做10次加减法计算。 设计算法,输入两个不同的正整数,输出得到1的最少计算次数。(如果无法得到1,则输出-1)。
(1) 若输入两个不同的正整数a,b均为偶数,显然不可能得到1。
设x*a与y*b之差为“1”或“-1”,则对于正整数a,b经n=x+y-1次加减可得到1。 为了求n的最小值,令n从1开始递增,x在1——n中取值,y=n+1-x: 检测d=x*a+y*b,若d=1或-1,则n=x+y-1为所求的最少次数。 (2) 算法描述
// 两数若干次加减结果为1的数学游戏 #include {long a,b,d,n,x,y; printf(\" 请输入整数a,b: \"); scanf(\"%ld,%ld\ if(a%2==0 && b%2==0) { printf(\" -1\\n\");return;} n=0; while(1) { n++; for(x=1;x<=n;x++) { y=n+1-x;d=x*a-y*b; if(d==1 || d==-1) // 满足加减结果为1 { printf(\" n=%ld\\n\} } } 请输入整数a,b: 2012,19 961 请输入整数a,b: 101,2013 606 1-2 埃及分数式算法描述 分母为整数分子为“1”的分数称埃及分数,试把真分数a/b分解为若干个分母不为b的埃及分数之和。 (1) 寻找并输出小于a/b的最大埃及分数1/c; (2) 若c>900000000,则退出; (3) 若c≤900000000,把差a/b-1/c整理为分数a/b,若a/b为埃及分数,则输出后结束。 (4) 若a/b不为埃及分数,则继续(1)、(2)、(3)。 试描述以上算法。 解:设dint(b) (这里int(x)表示取正数x的整数),注意到dbd1,有 aa a1a(d1)bbd1b(d1) 算法描述:令c=d+1,则 input (a,b) while(1) {c=int(b/a)+1; if(c>900000000) return; else { print(1/c+); a=a*c-b; b=b*c; // a,b迭代,为选择下一个分母作准备 if(a==1) { print(1/b);return;} } } 1-3 求解时间复杂度 求出以下程序段所代表算法的时间复杂度。 (1)m=0; for(k=1;k<=n;k++) for(j=k;j>=1;j--) m=m+j; 解:因s=1+2+…+n=n(n+1)/2 2 时间复杂度为O(n)。 (2)m=0; for(k=1;k<=n;k++) for(j=1;j<=k/2;j++) m=m+j; 解:设n=2u+1,语句m=m+1的执行频数为 s=1+1+2+2+3+3+…+u+u=u(u+1)=(n−1)(n+1)/4 设n=2u,语句m=m+1的执行频数为 22 s=1+1+2+2+3+3+…+u=u=n/4 2 时间复杂度为O(n)。 (3)t=1;m=0; for(k=1;k<=n;k++) {t=t*k; for(j=1;j<=k*t;j++) m=m+j; } 解:因s=1+2×2!+ 3×3!+…+ n×n!=(n+1)!−1 时间复杂度为O((n+1)!). (4)for(a=1;a<=n;a++) {s=0; for(b=a*100−1;b>=a*100−99;b−=2) {for(x=0,k=1;k<=sqrt(b);k+=2) if(b%k==0) {x=1;break;} s=s+x; } if(s==50) printf(\"%ld \\n\} 解:因a循环n次;对每一个a,b循环50次;对每一个b,k循环b/2次。因而k循环体的执行次数s满足 s<25(99199L100n1)<250(12Ln)4n3<250n<250nn6 算法的时间复杂度为O(nn)。 1-4 时间复杂度的一个性质 若p(n)是n的多项式,证明:O(log(p(n)))=O(logn)。 mm-1 证:设m为正整数,p(n)=a1×n+a2×n+…+am×n, 取常数c>ma1+(m-1)a2+…+am, 则 log(p(n))=ma1×logn+(m-1)a2×logn+…=(ma1+(m-1)a2+…)×logn 1-5 统计n!中数字“0”的个数 修改1.3.2计算n!的算法,统计并输出n!中数字“0”的个数及其尾部连续“0”的个数(n<10000)。 解:计算n!完成后,在j(1——m)循环过 if(a[j]==0) p++; 统计n!中数字“0”的个数p。 应用q=1; while(a[q]==0) q++;统计尾部连续“0”的个数q-1。 // 统计n!中0的个数及尾部连续0的个数(n<10000) #include { int g,j,k,m,n,p,q,t,a[40000];double s; printf(\" 请输入正整数n(n<10000): \"); scanf(\"%d\ // 输入n s=0; for(k=2;k<=n;k++) s+=log10(k); // 对数累加确定n!的位数m m=(int)s+1; for(k=1;k<=m;k++) a[k]=0; // 数组清零 a[1]=1;g=0; for(k=2;k<=n;k++) for(j=1;j<=m;j++) { t=a[j]*k+g; // 数组累乘并进位 a[j]=t%10; g=t/10; } p=0; for(j=m;j>=1;j--) if(a[j]==0) p++; // p统计n!中0的个数 q=1; while(a[q]==0) q++; // q尾部连续0的个数 printf(\" p=%d,q=%d\\n\输出结果 } 数据测试: 请输入正整数n(n<10000): 1000 p=472,q=249 请输入正整数n(n<10000): 2013 p=1032,q=501 1-6 构建斜折对称方阵 图1-4是一个7阶斜折对称方阵,试观察斜折对称方阵的构造特点,总结归纳其构造规律,设计并输出n(奇数)阶斜折对称方阵。 图1-4 7阶斜折对称方阵 (1) 构造规律与赋值要点 对n阶方阵中的每一个元素都必须赋值,但不可能逐行逐列地一个个赋值,有必要分析方阵的构造特点,分块或分片实施。 斜折对称方阵的构造特点:两对角线上均为“0”,依两对角线把方阵分为4个区域,每一区域表现为同数字依附两对角线折叠对称,至上下左右正中元素为n/2。 同样设置2维a[n][n]数组存储方阵中元素,行号为i,列号为j,a[i][j]为第i行第j列元素。 令m=(n+1)/2, 按m把方阵分成的4个小矩形区如图1-5所示。 图1-5 按m分成的4个小矩形 注意到方阵的主对角线(从左上至右下)上元素为:i=j,则左上区与右下区依主对角线赋值:a[i][j]=abs(i-j); 注意到方阵的次对角线(从右上至左下)上元素为:i+j=n+1,则右上区与左下区依次对角线赋值:a[i][j]=abs(i+j-n-1); (2) 程序设计 // 斜折对称方阵 #include {int i,j,m,n,a[30][30]; printf(\" 请确定方阵阶数(奇数)n: \"); scanf(\"%d\if(n%2==0) { printf(\" 请输入奇数!\");return;} m=(n+1)/2; for(i=1;i<=n;i++) for(j=1;j<=n;j++) { if(i<=m && j<=m || i>m && j>m) a[i][j]=abs(i-j); // 方阵左上部与右下部元素赋值 if(i<=m && j>m || i>m && j<=m) a[i][j]=abs(i+j-n-1); // 方阵右上部与左下部元素赋值 } printf(\" %d阶对称方阵为:\\n\ for(i=1;i<=n;i++) { for(j=1;j<=n;j++) // 输出对称方阵 printf(\"%3d\ printf(\"\\n\"); } } 1-7 构建横竖折对称方阵 试观察图1-6所示的横竖折对称方阵的构造特点,总结归纳其构造规律,设计并输出n(奇数)阶横竖折对称方阵。 图1-6 7阶横竖折对称方阵 (1) 构造规律与赋值要点 观察横竖折对称方阵的构造特点,方阵横向与纵向正中有一对称轴。两对称轴所分4个小矩形区域表现为同数字横竖折递减,至4顶角元素为1。 设阶数n(奇数)从键盘输入,对称轴为m=(n+1)/2。 设置2维a数组存储方阵中元素,行号为i,列号为j,a[i][j]为第i行第j列元素。 可知主对角线(从左上至右下)有:i=j; 次对角线(从右上至左下)有:i+j=n+1。 按两条对角线把方阵分成上部、左部、右部与下部4个区,如图1-7所示。 图1-7 对角线分成的4个区 对角线上元素可归纳到上、下部,即上、下部区域带等号即可。 上、下部按列号j的函数m-abs(m-j)赋值: if(i+j<=n+1 && i<=j || i+j>=n+1 && i>=j) a[i][j]=m-abs(m-j); 左、右部按行号i的函数m-abs(m-i)赋值: if(i+j #include #include {int i,j,m,n,a[30][30]; // 定义数据结构 printf(\" 请确定方阵阶数(奇数)n: \"); scanf(\"%d\ if(n%2==0) {printf(\" 请输入奇数!\");return;} m=(n+1)/2; for(i=1;i<=n;i++) for(j=1;j<=n;j++) { if(i+j<=n+1 && i<=j || i+j>=n+1 && i>=j) a[i][j]=m-abs(m-j); // 方阵上、下部元素赋值 if(i+j printf(\" %d阶对称方阵为:\\n\ for(i=1;i<=n;i++) { for(j=1;j<=n;j++) // 输出对称方阵 printf(\"%3d\ printf(\"\\n\"); } } 1-8 应用定义求最大公约与最小公倍数 应用定义求n个正整数的最大公约数与最小公倍数, 给出算法设计。 为方便表述,记 (a1,a2,...,an)为n个正整数a1,a2,...,an的最大公约数。 {a1,a2,...,an}为n个正整数a1,a2,...,an的最小公倍数。 (1)设计要点 求3个或3个以上正整数的最大公约数与最小公倍数,可通过反复运用求两个正整数的最大公约数与最小公倍数的方法来实现。 对于3个或3个以上正整数,最大公约数与最小公倍数有以下性质: (a1,a2,a3)=((a1,a2),a3) (a1,a2,a3,a4)=((a1,a2,a3),a4),... {a1,a2,a3}={{a1,a2},a3} {a1,a2,a3,a4}={{a1,a2,a3},a4},... 应用这一性质,要求n个数的最大公约数,先求出前n-1个数的最大公约数b,再求第n个数与b的最大公约数。要求n个数的最小公倍数,先求出前n-1个数的最小公倍数b,再 求第n个数与b的最小公倍数。 程序设计求最大公约数时变量b的值:开始b=m[0],即输入的第一个数;进入循环后,每次把输入的数赋值给a,求得a、b的最大公约数c,同时赋值给b,为下一轮循环作准备。 求最小公倍数时变量b的值:开始b=m[0],即输入的第一个数;进入循环后,每次把输入的数赋值给a,求得a、b的最小公倍数c,同时赋值给b,为下一轮循环作准备。 (2)算法描述 // 求n个正整数的最大公约数与最小公倍数 #include long a,b,c,m[100]; printf(\"请输入正整数个数n: \"); scanf(\"%d\ printf(\"请依次输入%d个正整数: \ for(k=0;k<=n-1;k++) { printf(\"\\n请输入第%d个正整数: \ scanf(\"%ld\// 输入原始数据 } b=m[0]; for(k=1;k<=n-1;k++) {a=m[k]; if(a{c=a;a=b;b=c;} // 交换a、b,确保a>b for(c=b;c>=1;c--) if(a%c==0 && b%c==0) // 计算最大公约数 break; b=c; } printf(\"(%ld\ // 输出最大公约数结果 for(k=1;k<=n-1;k++) printf(\ printf(\")=%ld\\n\ b=m[0]; for(k=1;k<=n-1;k++) { a=m[k]; if(a{c=a;a=b;b=c;} for(c=a;c<=a*b;c=c+a) if(c%b==0) // 计算最小公倍数 break; b=c; } printf(\"{%ld\ // 输出最小公倍数结果 for(k=1;k<=n-1;k++) printf(\ printf(\")=%ld\\n\} (3)数据测试 请输入正整数个数n:3 请依次输入3个正整数: 请输入第1个正整数: 238 请输入第2个正整数: 782 请输入第3个正整数: 646 (238,782,646)=34 {238,782,646}=104006 2-1 广义同码小数之和 s(d,n)=0.d+0.dd+0.ddd+...+0.dd...d (n个d) 其中正整数d,n从键盘输入(1 // 求和s(d,n)=0.d+0.dd+0.ddd+...+0.dd...d (n个d) #include { int a,d,i,j,k,m,n,p[10];long s[50000]; printf(\" 请输入整数d,n: \"); scanf(\"%d,%d\ a=d;k=0; while(a>0) { k++;p[k]=a%10;a=a/10;} for(j=0;j<=k*n;j++) s[j]=0; // s[0]为整数部分 for(i=0;i<=n-1;i++) for(j=1;j<=k;j++) s[i*k+j]=(n-i)*p[k+1-j]; // 小数点后各位求和 for(j=k*n;j>=1;j--) // 加完n个数从后往前统一进位 { s[j-1]=s[j-1]+s[j]/10;s[j]=s[j]%10;} printf(\" s(%d,%d)=%ld.\ m=k*n; if(m>10) m=10; for(j=1;j<=m;j++) printf(\"%ld\} 数据测试: 输入整数d,n: 2014,2013 s(2014,2013)=405.4587257305 2-2 解不等式 设n为正整数,解不等式 201311112014 11/211/21/311/21/n解:上下限一般为键盘输入的a,b。 分两段求和:应用条件s #include { long a,b,c,d,i; double ts,s; printf(\" 请输入a,b: \"); scanf(\"%d,%d\ i=0;ts=0;s=0; while(sts=ts+(double)1/i; s=s+1/ts; } c=i; while(sts=ts+(double)1/i; s=s+1/ts; } d=i-1; printf(\"\\n 满足不等式的正整数n为: %ld≤n≤%ld \\n\} 数据测试: 请输入a,b: 2013,2014 满足不等式的正整数n为: 18642≤n≤18652 2-3 解不定方程 试求解三元不定方程 111abcabc 满足条件 xa,b,cy,bc 的所有正整数解。 (1)枚举设计要点 1) 为了扩大求解围,所涉变量设置为double型。 2) 注意a,b,c的取值围,在区间[x,y]中取值: a与b,c没有限制,只限制b (abc)bca((abc)bc) 在枚举循环中应用以上整式进行筛选是可行的。 (2) 枚举描述 // 解不定方程 #include { double a,b,c,s,x,y,m; printf(\" 请输入正整数x,y: \"); scanf(\"%lf,%lf\ m=0; for(a=x;a<=y;a++) // 设计三重循环实现枚举 for(b=x;b<=y-1;b++) for(c=b+1;c<=y;c++) { s=a+b+c; if(b*c*s!=a*(b*c+s)) // 应用方程式实施筛选 continue; m++; // 统计解的个数并输出解 printf(\" %.0f: a=%.0f,b=%.0f,c=%.0f \\n\} } 数据测试: 请输入正整数x,y: 10,100 1: a=50,b=10,c=15 2: a=90,b=15,c=21 2-4 合数世纪探索 定义:一个世纪的100个年号中不存在一个素数,即100个年号全为合数的世纪称为合数世纪。 试探索第m(约定m<100)个合数世纪。 (1) 枚举要点 应用枚举搜索,设置a世纪的的50个奇数年号(偶数年号无疑均为合数)为b,用k试商判别b是否为素数,用变量s统计这50个奇数中的合数的个数。 对于a世纪,若s=50,即50个奇数都为合数,找到a世纪为合数世纪,用n统计合数世纪的个数。当n=m时打印输出a 世纪。 当n=m时退出循环结束。 (2) 枚举描述 // 探求第m个合数世纪 #include {long a,b,k; int m,n,s,x; printf(\" 请确定m: \"); scanf(\"%d\ a=1;n=0; while (1) {a++;s=0; // 检验a世纪 for(b=a*100-99;b<=a*100-1;b+=2) // 穷举a世纪奇数年号b {x=0; for(k=3;k<=sqrt(b);k+=2) if(b%k==0) {x=1;break;} if(x==0)break; // 当前为非合数世纪时,跳出循环进行下一个世纪的探求 s=s+x; // 年号b为合数时,x=1,s增1 } if(s==50) // s=50,即50个奇数均为合数 { n++; if(n==m) printf(\" 第%d个合数世纪为:%ld 世纪。\\n\ } if(n==m) break; } } 数据测试: 请确定m: 10 第10个合数世纪为:58453 世纪。 2-5 分解质因数 对给定区间[m,n]的正整数分解质因数,•每一整数表示为质因数从小到大顺序的乘积形式。如果被分解的数本身是素数,则注明为素数。 例如, 2012=2*2*503, 2011=(素数!)。 (1) 设计要点 对区间中的每一个整数i(b=i),用k(2——sqrt(i))试商: 若不能整除,说明该数k不是b的因数,k增1后继续试商。 若能整除,说明该数k是b的因数,打印输出\"k*\";b除以k的商赋给b(b=b/k)•后继续用k试商(注意,可能有多个k因数),直至不能整除,k增1后继续试商。 按上述从小至大试商确定的因数显然为质因数。 如果有大于sqrt(n)的因数(至多一个!)•,在试商循环结束后要注意补上,不要遗失。 如果整个试商后b的值没有任何缩减,仍为原待分解数n,说明n是素数,作素数说明标记。 若k是b的因数,按格式输出,然后b=b/k后继续试商k。 若k不是b的因数,则k增1后继续。 若上述试商完成后1// 质因数分解乘积形式 #include\"math.h\" #include {long int b,i,k,m,n,w=0; printf(\"[m,n]中整数分解质因数(乘积形式).\\n\"); printf(\"请输入m,n:\"); scanf(\"%ld,%ld\ for(i=m;i<=n;i++) // i为待分解的整数 { printf(\"%ld=\ b=i;k=2; while(k<=sqrt(i)) // k为试商因数 {if(b%k==0) {b=b/k; if(b>1) {printf(\"%ld*\ continue; // k为质因数,返回再试 } if(b==1) printf(\"%ld\\n\} k++; } if(b>1 && bprintf(\"%ld\\n\输出大于i平方根的因数 if(b==i) {printf(\"(素数!)\\n\");w++;} // b=i,表示i无质因数 } } 数据测试: [m,n]中整数分解质因数(乘积形式). 请输入m,n:5845201,5845300 5845201=593*9857 5845202=2*11*47*5653 …… 5845300=2*2*5*5*58453 以上测试可知没有一个素数,验证了上题的第10个合数世纪的100个合数年号。 2-6 因数比的最大值 设整数a的小于其本身的因数之和为s,定义 p(a)=s/a 为整数a的因数比。 事实上,a为完全数时,p(a)=1。例如,p(6)=1。 有些资料还介绍了因数之和为数本身2倍的整数,例如p(120)=2。 试搜索指定区间[x,y]中因数比最大的整数。 (1) 设计要点 设置循环枚举区间[x,y]中的整数a。 为了求整数a的因数和s,显然1是因数,设置因数k(2——sqrt(a))的枚举循环,如果 k是a的因数,则a/k也是a的因数,实施求和s=s+k+a/k。 有一点值得注意:如果a=b*b,显然k=b,a/k=b是同一个因数,为避免重复求和,所以此时必须从和s中减去一个b。 设置max存储因数比最大值。枚举区间每一整数a,求得其因数和s。通过比值s/a与max比较,求取因数比最大值。 (2) 枚举描述 // 求[x,y]围整数的因数比最大值 #include { double a,s,a1,s1,b,k,t,x,y,max=0; printf(\" 请输入整数x,y:\"); scanf(\"%lf,%lf\ for(a=x;a<=y;a++) // 枚举区间的所有整数a {s=1;b=sqrt(a); for(k=2;k<=b;k++) // 试商寻求a的因数k if(fmod(a,k)==0) s=s+k+a/k; // k与a/k是a的因数,求和 if(a==b*b) s=s-b; // 如果a=b^2,去掉重复因数b t=s/a; if(max printf(\" 整数%.0f的因数和为:%.0f ,\ printf(\"其因数比最大:%.5f \\n\} 数据测试: 请输入整数x,y:10000,100000 整数55440的因数和为:176688 ,其因数比最大:3.18701 算法中在区间枚举循环中含试商因数枚举的循环。设区间中整数个数为n,算法的时间复杂度为O(nn)。 思考:请探否存在因数比p(a)=s/a≥4的整数a?若存在,整数a至少为多大? 2-7 基于素数代数和的最值 定义和: s(n)13355779(2n1)(2n1) (和式中第k项±(2k-1)*(2k+1)的符号识别:当(2k-1)与(2k+1)中至少有一个素数,取“+”;其余取“-”。例如和式中第13项取“-”,即为-25*27。) 1) 求s(2013)。 2) 设1<=n<=2013,当n为多大时,s(n)最大。 3) 设1<=n<=2013,当n为多大时,s(n)最小。 (1) 设计要点 代数和式中各项的符号并不是简单的正负相间,而是随着构成素数而改变。因而在求和之前应用“试商判别法”对第k个奇数2k-1是否为素数进行标注: 若2k-1为素数,标注a[k]=1; 否则,若2k-1不是素数,a[k]=0。 设置k循环(1——n),循环中分别情况求和: 若a[k]+a[k+1]>=1,即(2k-1)与(2k+1)中至少有一个素数,实施“+”; 否则,若a[k]+a[k+1]==0,即(2k-1)与(2k+1)中没有素数,实施“-”。 同时,设置最大值变量smax,最小值变量smin。 在循环中,每计算一个和值s,与smax比较确定最大值,同时记录此时的项数k1;与smin比较确定最小值,同时记录此时的项数k2。 (2) 算法描述 // 基于素数的整数和 #include { int t,j,n,k,k1,k2,a[3000]; long s,smax,smin; printf(\" 请输入整数n: \"); scanf(\"%d\ for(k=1;k<=n+1;k++) a[k]=0; for(k=2;k<=n+1;k++) {for(t=0,j=3;j<=sqrt(2*k-1);j+=2) if((2*k-1)%j==0) {t=1;break;} if(t==0) a[k]=1; // 标记第k个奇数2k-1为素数 } s=3;smax=0;smin=s; for(k=2;k<=n;k++) {if(a[k]+a[k+1]>=1) s+=(2*k-1)*(2*k+1); // 实施代数和 else s-=(2*k-1)*(2*k+1); if(s>smax){smax=s;k1=k;} // 比较求最大值smax if(s printf(\"s(%d)=%ld \\n\ printf(\"当k=%d时s有最大值: %ld\\n\printf(\"当k=%d时s有最小值: %ld\\n\} 数据测试: 请输入整数n: 2013 s(2013)=-889733139 当k=814时s有最大值: 46296494 当k=1999时s有最小值: -1018390543 2-8 完美四则运算式 把数字1,2,...,9这9个数字填入以下含加减乘除的综合运算式中的9个□中,•使得该式成立 □□×□+□□□÷□-□□=0 要求数字1,2,...,9这9个数字在各式中都出现一次且只出现一次,且约定数字“1”不出现在数式的一位数中(即排除各式中的各个1位数为1这一平凡情形)。 (1) 设计要点 设式右的5个整数从左至右分别为a,b,c,d,e,其中a,e为二位整数,b,d为大于1的一位整数,c为三位整数。设置a,b,c,d循环,对每一组a,b,c,d,计算e=a*b+c/d。若其中的c/d非整数,或所得e非二位数,则返回。 然后分别对5个整数进行数字分离,设置f数组对5个整数分离的共9个数字进行统计,f(x)即为数字x(1—9)的个数。 若某一f(x)不为1,不满足数字1,2,...,9这九个数字都出现一次且只出现一次,标记t=1. 若所有f(x)全为1,满足数字1,2,...,9这九个数字都出现一次且只出现一次,保持标记t=0, 则输出所得的完美综合运算式。 设置n统计解的个数。 (2) 设计描述 // 四则运算式 #include {int x,y,t,k,a,b,c,d,e,n=0; int m[6],f[11]; for(a=12;a<=98;a++) for(b=2;b<=9;b++) for(c=123;c<=987;c++) // 对a,b,c,d 实施枚举 for(d=2;d<=9;d++) {x=c/d;e=a*b+x; if(c!=x*d || e>100) continue; m[1]=a;m[2]=c;m[3]=e;m[4]=b;m[5]=d; for(x=0;x<=9;x++) f[x]=0; for(k=1;k<=5;k++) {y=m[k]; while(y>0) {x=y%10;f[x]=f[x]+1; y=(y-x)/10; // 分离数字f数组统计 } } for(t=0,x=1;x<=9;x++) if(f[x]!=1) {t=1; break;} // 检验数字0--9各只出现一次 if(t==0) // 输出一个解,用n统计个数 {n++; printf(\"%2d: %2d*%1d+%3d/%1d-%2d=0 \\n\ } } printf(\" n=%d.\\n\ } 数据测试: 1: 12*4+376/8-95=0 2: 17*3+258/6-94=0 3: 35*2+168/7-94=0 n=3. 2-9 区间的最续合数区间 搜索指定区间[c,d]的最续合数区间。 输入: 键盘输入区间围c,d。 输出: 区间最多连续合数的个数,连续合数的起始与终止数。 例如输入 c,d:10,100 最多连续合数的个数为:7 连续合数区间为:[90,96] (1) 应用试商设计 // 求指定区间最多有多少个连续合数 #include #include { long c,d,i,j,f,t,f1,i1,max; printf(\" 请输入 c,d:\"); scanf(\"%ld,%ld\ f=c;max=0; if(c%2==0) c++; i=c; for(i=c;i<=d;i+=2) {t=0; for(j=3;j<=sqrt(i);j+=2) // 试商判别素数 if(i%j==0) {t=1;break;} if(t==0) // t为0表明i为素数 { if(i-f>max) {max=i-f;f1=f+1;i1=i-1;} f=i; // f为i的前一个素数 } } printf(\" 最多连续合数的个数为:%ld\\n\ printf(\" 连续合数区间为:[%ld,%ld]\\n\ } 数据测试: 请输入 c,d:10000,1000000 最多连续合数的个数为:113 连续合数区间为:[492114,492226] (2) 筛法设计 求出区间[c,d]的所有相邻素数对f,m,求取m-f的最大值max。 注意到本题的搜索围较大,采用效率较高的筛法求素数是适宜的。 求素数的筛法是公元前三世纪的厄拉多塞(Eratosthenes)提出来的:•对于一个大整数x,只要知道不超过sqrt(x)的所有素数p,划去所有p的倍数2p,3p,...,剩下的整数就是不超过x的全部素数。 应用筛法求素数,为了方便实施\"划去\"操作,设置数组。 考虑到有时待测区间[c,d]比较大,为不使数组下标超维,或把[c,d]分割为若干子区间[cs,ds],确保在子区间中操作不超维。• 每一数组元素对应一个待判别的奇数,并赋初值0。如果该奇数为p的倍数则应划去,•对应元素加一个划去标记,通常给该元素赋值-1。最后,打印元素值不是-1(即没有划去)•的元素对应的奇数即所求素数。 在实际应用筛法的过程中,p通常不限于取不超过sqrt(x)的素数,而是适当放宽取不超过sqrt(x)的奇数(从3开始)。这样做尽管多了一些重复划去操作,但程序实现要简便些。 在指定区间[cs,ds](约定cs为奇数)上所有奇数表示为j=cs+2k,(k=0,1,...,e,这里 e=(ds-cs)/2)。于是k=(j-cs)/2是奇数j在数组中的序号(下标)。如果j为奇数的倍数时,•对应数组元素作划去标记,即a[(j-cs)/2]=-1。 根据cs与奇数i,确定 g=2*int(cs/(2*i))+1,使得gi接近区间下限cs,从而使划去的gi,(g+2)i,...在[cs,ds]中,减少无效操作,以提高对大区间的筛选效率。 最后,凡数组元素a[k]≠-1,•对应的奇数j=cs+2k则为素数。 • // 求指定区间最多有多少个连续合数 #include { long c,d,cs,ds,ct,dt,f,g,i,j,k,m,max,a[11000]; int e,u,x,y; printf(\" 请输入 c,d:\"); scanf(\"%ld,%ld\ if(d-c<=20000) {cs=c;ds=d;x=0;} else {x=(d-c)/20000;cs=c;ds=d-20000*x;} f=cs;max=0; for(y=1;y<=x+1;y++) // 把[c,d]中分x+1个子区间筛选素数 { if(cs%2==0) cs++; for(i=0;i<=10999;i++) a[i]=0; e=(ds-cs)/2;i=1; while (i<=sqrt(ds)) {i=i+2;g=2*(cs/(2*i))+1; if(g*i>ds) continue; if(g==1) g=3; j=i*g; while (j<=ds) { if(j>=cs) a[(j-cs)/2]=-1; // 筛去标记-1 j=j+2*i; } } for(u=1,k=0;k<=e;k++) { if(a[k]!=-1) { m=cs+2*k; // m即筛选所得素数 if(m-f>max) // 寻求两相邻素数间距的最大值 { max=m-f;ct=f+1;dt=cs+2*k-1;} f=m; } } cs=ds+1;ds=ds+20000; // cs与ds增长后继续探求 } printf(\" 最多连续合数的个数为:%ld\\n\ printf(\" 连续合数区间为:[%ld,%ld]\\n\ } 数据测试: 请输入 c,d:10,10000000 最多连续合数的个数为:153 连续合数区间为:[4652354,4652506] 2-10 三角形双分线 设一块木板为非等腰ABC,其三边长分别为BC=a,CA=b,AB=c,寻求三角形木板上的分割线,把该三角形木板分割为周长相等且面积相等的两块。 试确定双分线的具体位置。 依次输入正整数a,b,c(约定a,b,c<100),输出各分割线的位置(四舍五入精确到小数点后第4位)。 (1) 设计要点 1) 设ABC的周长为p,AB边上的分割点为D,AC边上的分割点为E,BC边上的分割点为F(如图2-6所示)。显然,p=a+b+c。 若线段EF为双分线,则CEF的面积为ABC面积的一半,CE+CF=p/2(分割线段EF为两块公共)。 图2-6 ABC及其分割线示意图 2) 设CE=x, CF=p/2-x,则面积相等的条件为CE*CF=x(p/2-x)=ab/2 同样,设CF=x, CE=p/2-x,则面积相等的条件为CE*CF=x(p/2-x)=ab/2 整理为关于x的二次方程: 2 2x-(a+b+c)x+ab=0 可见若EF为双分线,则CE、CF须为以上二次方程的两个正根。 2 3) 若方程的判别式w=(a+b+c)-8*a*b≥0, 则方程的两个解为: x1=((a+b+c)-sqrt(w))/4 x2=((a+b+c)+sqrt(w))/4 显然,x1≤x2 4) 讨论方程的解 若x1>0 且x1<=a 且x2<=b,由取CF=x1, CE=x2为一条双分线。 若x1>0 且 x2<=a,则取CF=x2, CE=x1为另一条双分线。 2 5)若线段DF为双分线,判别式为w=(a+b+c)-8*a*c; 2 若线段DE为双分线,判别式为w=(a+b+c)-8*b*c; 其余求解与讨论类似。 (2) 算法描述 // 三角形双分线 #include \"math.h\" #include {double a,b,c,p,w,x1,x2; printf(\" 输入三角形三边长a,b,c: \"); scanf(\"%lf,%lf,%lf\ if(a>=b+c ||b>=c+a ||c>=a+b) { printf(\" 输入的三边长不能构成三角形!\"); return; } p=a+b+c;w=p*p-8*a*b; // 在CA,CB边上取分割点 if(w>=0) {x1=p/4-sqrt(w)/4;x2=p/4+sqrt(w)/4; if(0 w=p*p-8*c*b; // 在AB,AC边上取分割点 if(w>=0) {x1=p/4-sqrt(w)/4;x2=p/4+sqrt(w)/4; if(0 w=p*p-8*c*a; // 在BA,BC边上取分割点 if(w>=0) {x1=p/4-sqrt(w)/4;x2=p/4+sqrt(w)/4; if(0 数据测试: 输入三角形三边长a,b,c: 30,40,50 双分线在BC上取BF=17.7526, 在BA上取BD=42.2474 输入三角形三边长a,b,c: 57,78,92 双分线在BC上取BF=32.2845, 在BA上取BD=81.2155 3-1 递推b数列 定义b数列: b11,b22,bn3bn1bn2(n2) 递推求b数列的第20项与前20项之和。 (1) 递推描述 #include { int k,n; long b[3000],s; printf(\" 请输入n: \"); scanf(\"%d\ b[1]=1;b[2]=2;s=3; for(k=3;k<=n;k++) { b[k]=3*b[k-1]-b[k-2]; s+=b[k]; } printf(\" b(%d)=%ld \\n\ printf(\" s=%ld \\n\ } (2) 数据测试 请输入n: 20 b(20)=63245986 s=102334155 3-2 递推数列中的素数 已知数列a(1)=2,a(k)=a(k-1)+k (k>1).试求该数列的前m项中的素数个数及最大素数。 输入m,输出前m项中的素数个数及最大素数。 (1) 算法描述 // 数列中的素数 #define N 30000 #include {int m,n,k,j,t,a[N]; long s; printf(\" 请输入m: \"); scanf(\"%d\a[1]=2;s=1; for(k=2;k<=m;k++) { a[k]=a[k-1]+k; for(t=0,j=2;j<=sqrt(a[k]);j++) if(a[k]%j==0) {t=1;break;} if(t==0) {s++;n=a[k];} // a[k]为素数,用s统计个数 } printf(\" 前%d项中素数个数为:%ld \\n\ printf(\" 最大素数为:%d \\n\} (2) 数据测试 请输入m: 2014 前2014项中素数个数为:329 最大素数为:2023067 3-3 双幂序列 设x,y为非负整数,试计算集合 M{2x,3y|x0,y0} 的元素由小到大排列的双幂序列第n项与前n项之和。 (1) 递推设计要点 集合由2的幂与3的幂组成,实际上是给出两个递推关系。 设置一维f数组,f[k]存储双幂序列的第k项。 显然,第1项也是最小项为f[1]=1(当x=y=0时)。 从第2项开始,为了实现从小到大排序,设置a,b两个变量,a为2的幂,b为3的幂,显然a≠b。 设置k循环(k=2,3,…,n,其中n为键盘输入整数),在k循环外赋初值:a=2;b=3;在k循环过比较选择赋值: 当a当a>b时,由赋值f[k]=b确定为序列的第k项;然后b=b*3,即b按递推规律乘3,为后一轮比较作准备。 (2) 递推描述 a=2;b=3; // 为递推变量a,b赋初值 for(k=2;k<=n;k++) { if(a{ f[k]=a;a=a*2;} // 用a给f[k]赋值 else { f[k]=b;b=b*3;} // 用b给f[k]赋值 } 3-4 多幂序列 设x,y,z为非负整数,试计算集合 M{2x,3y,5z|x0,y0,z0} 的元素由小到大排列的多幂序列第n项与前n项之和。 (1) 递推设计 集合由2的幂、3的幂与5的幂组成,实际上给出的是3个递推关系。 显然,第1项也是最小项为1(当x=y=z=0时)。 从第2项开始,为了实现从小到大排列,设置3个变量a,b,c,a为2的幂,b为3的幂,c为5的幂,显然a,b,c互不相等。 设置k循环(k=2,3,…,n,其中n为键盘输入整数),在k循环外赋初值:a=2;b=3;c=5;s=1;在k循环过比较赋值: 当a当b当ca=2;b=3;c=5; // 为递推变量a,b,c赋初值 for(k=2;k<=n;k++) { if(a{ f[k]=a;a=a*2;} // 用a给f[k]赋值 else if(b{ f[k]=b;b=b*3;} // 用b给f[k]赋值 else { f[k]=c;c=c*5;} // 用c给f[k]赋值 } 3-5 双幂积序列的和 由集合M{23|x0,y0}元素组成的复合幂序列,求复合幂序列的指数和x+y≤n(正整数n≤50从键盘输入)的各项之和 xysxy023,x0,y0 xyn(1) 递推设计要点 当x+y=0时,s(1)=1; 当x+y=1时,s(1)=2+3; 222 当x+y=2时,s(2)=2+2×3+3=2*s(1)+ 3 32233 当x+y=3时,s(3)=2+2×3+2×3+3=2*s(2)+ 3 k 一般地,当x+y=k时,s(k)=2*s(k−1)+3 即有递推关系: s(k)=2*s(k)+3 k k 其中3可以通过变量迭代实现。这样可以省略数组,简化为一重循环实现复合幂序列求和。 (2) 递推描述 // 复合幂序列求和 #include {int k,n; long sum,t,s[100]; printf(\"请输入幂指数和至多为n:\"); scanf(\"%d\ t=1;s[0]=1; sum=1; for(k=1;k<=n;k++) {t=t*3; // 迭代得t=3^k s[k]=2*s[k-1]+t; // 实施递推 sum=sum+s[k]; } printf(\"幂指数和至多为%d的幂序列之和为:%ld\\n\} (3) 数据测试 请输入幂指数和至多为n:50 幂指数和至多为50的幂序列之和为:717652233 3-6 粒子裂变 核反应堆中有α和β两种粒子,每秒钟一个α粒子可以裂变为3个β粒子,而一个β粒子可以裂变为1个α粒子和2个β粒子。若在t=0时刻的反应堆中只有一个α粒子,求在t(t<20)秒时反应堆裂变产生的α粒子和β粒子数。 (1) 递推设计要点 设在t秒时α粒子数为f(t),β粒子数为g(t),依题可知: g(t)=3f(t-1)+2g(t-1) (1) f(t)=g(t-1) (2) g(0)=0,f(0)=1 由(2)得f(t-1)=g(t-2) (3) 将式(3)代入(1)得 g(t)=2g(t-1)+3g(t-2) (t≥2) (4) g(0)=0,g(1)=3 (5) 以递推关系(4)与初始条件(5)完成递推。 (2) 递推描述 // 粒子裂变 #include {int t,k;long g[100]; printf(\" input t:\"); scanf(\"%d\ g[0]=0; g[1]=3; // 确定初始条件 for(k=2;k<=t;k++) g[k]=2*g[k-1]+3*g[k-2]; // 完成递推 printf(\"%d 秒时反应堆中β粒子数为:%ld \\n\ printf(\"%d 秒时反应堆中α粒子数为:%ld \\n\} 数据测试: input t:18 18 秒时反应堆中β粒子数为:290565366 18 秒时反应堆中α粒子数为:96855123 3-7 猴子吃桃 有一猴子第1天摘下若干个桃子,当即吃了一半,还不过瘾,又多吃了1个。第2天早上又将剩下的桃子吃掉一半,又多吃了1个。以后每天早上都吃了前一天剩下的一半后又多吃1个。到第10天早上想再吃时,见只剩下1个桃子了。 求第1天共摘了多少个桃子。 (1) 递推要点 第1天的桃子数是第2天桃子数加1后的2倍,第2天的桃子数是第3天桃子数加1后的2倍,…,一般地,第k天的桃子数是第k+1天桃子数加1后的2倍。设第k天的桃子数是t(k),则有递推关系 t(k)=2*(t(k+1)+1) (k=1,2,…,9) 初始条件:t(10)=1 逆推求出t(1),即为所求的第一天所摘桃子数。 (2) 递推描述 // 猴子吃桃程序 #include { int k; long t[1000]; t[10]=1; // 确定初始条件 for(k=9;k>=1;k--) // 逆推计算t(1) t[k]=2*(t[k+1]+1); printf(\" 第 1 天摘桃%ld个。\\n\ for(k=1;k<=9;k++) { printf(\" 第 %d 天面临%4ld个桃,\ printf(\" 吃了%4ld+1=%4ld个,\ printf(\" 还剩%4ld个。\\n\ } printf(\" 第10天早上还剩1个。\"); } 数据测试: 第 1 天摘桃1534个。 第 1 天面临1534个桃, 吃了 767+1= 768个, 还剩 766个。 第 2 天面临 766个桃, 吃了 383+1= 384个, 还剩 382个。 第 3 天面临 382个桃, 吃了 191+1= 192个, 还剩 190个。 第 4 天面临 190个桃, 吃了 95+1= 96个, 还剩 94个。 第 5 天面临 94个桃, 吃了 47+1= 48个, 还剩 46个。 第 6 天面临 46个桃, 吃了 23+1= 24个, 还剩 22个。 第 7 天面临 22个桃, 吃了 11+1= 12个, 还剩 10个。 第 8 天面临 10个桃, 吃了 5+1= 6个, 还剩 4个。 第 9 天面临 4个桃, 吃了 2+1= 3个, 还剩 1个。 第10天早上还剩1个。 3-8 猴子吃桃引申 有一猴子第1天摘下若干个桃子,当即吃了一半,还不过瘾,又多吃了m个。第2天早上又将剩下的桃子吃掉一半,又多吃了m个。以后每天早上都吃了前一天剩下的一半后又多吃m个。到第n天早上想再吃时,见只剩下d个桃子了。 求第1天共摘了多少个桃子(m,n,d由键盘输入)? (1) 递推设计要点 建立递推关系: t(k)=2*(t(k+1)+m) (k=1,2,…,n-1) 初始条件:t(n)=d 逆推求出t(1),即为所求的第一天所摘桃子数。 (2) 递推描述 // 拓广猴子吃桃程序 #include void main() { int d,k,m,n; long t[1000]; printf(\" 请确定正整数m,n,d: \"); scanf(\"%d,%d,%d\ t[n]=d; // 确定初始条件 for(k=n-1;k>=1;k--) // 逆推计算t(1) t[k]=2*(t[k+1]+m); printf(\" 第 1 天摘桃%ld个。\\n\ for(k=1;k<=n-1;k++) { printf(\" 第 %d 天面临%4ld个桃,\ printf(\" 吃了%4ld+%d=%4ld个,\ printf(\" 还剩%4ld个。\\n\ } printf(\" 第%d天早上还剩%d个。\} 数据测试: 请确定正整数m,n,d: 3,20,2 第 1 天摘桃4194298个。 第 1 天面临4194298个桃, 吃了2097149+3=2097150个, 还剩2097146个。 第 2 天面临2097146个桃, 吃了1048573+3=1048574个, 还剩1048570个。 第 3 天面临1048570个桃, 吃了524285+3=524286个, 还剩524282个。 第 4 天面临524282个桃, 吃了262141+3=262142个, 还剩262138个。 第 5 天面临262138个桃, 吃了131069+3=131070个, 还剩131066个。 第 6 天面临131066个桃, 吃了65533+3=65534个, 还剩65530个。 第 7 天面临65530个桃, 吃了32765+3=32766个, 还剩32762个。 第 8 天面临32762个桃, 吃了16381+3=16382个, 还剩16378个。 第 9 天面临16378个桃, 吃了8189+3=8190个, 还剩8186个。 第 10 天面临8186个桃, 吃了4093+3=4094个, 还剩4090个。 第 11 天面临4090个桃, 吃了2045+3=2046个, 还剩2042个。 第 12 天面临2042个桃, 吃了1021+3=1022个, 还剩1018个。 第 13 天面临1018个桃, 吃了 509+3= 510个, 还剩 506个。 第 14 天面临 506个桃, 吃了 253+3= 254个, 还剩 250个。 第 15 天面临 250个桃, 吃了 125+3= 126个, 还剩 122个。 第 16 天面临 122个桃, 吃了 61+3= 62个, 还剩 58个。 第 17 天面临 58个桃, 吃了 29+3= 30个, 还剩 26个。 第 18 天面临 26个桃, 吃了 13+3= 14个, 还剩 10个。 第 19 天面临 10个桃, 吃了 5+3= 6个, 还剩 2个。 第20天早上还剩2个。 3-9 水手分椰子 五个水手来到一个岛上,采了一堆椰子后,因为疲劳都睡着了。一段时间后,第一个水手醒来,悄悄地将椰子等分成五份,多出一个椰子,便给了旁边的猴子,然后自己藏起一份,再将剩下的椰子重新合在一起,继续睡觉。不久,第二名水手醒来,同样将椰子等分成五份,恰好也多出一个,也给了猴子。然后自己也藏起一份,再将剩下的椰子重新合在一起。以后每个水手都如此分了一次并都藏起一份,也恰好都把多出的一个给了猴子。第二天,五个水手醒来,发现椰子少了许多,心照不宣,便把剩下的椰子分成五份,恰好又多出一个,给了猴子。 计算问原来这堆椰子至少有多少个? 试应用迭代与递推两种算法求解。 (1) 迭代求解 1) 确定迭代式 首先根据相邻两人所藏椰子数确定迭代方程 4*y=5*y+1 (i=1,2,…,5) (1) 迭代方程即相邻两水手所藏椰子数的关系,式左的y是相邻的前一个水手藏椰子数,式右的y是相邻后一个水手藏椰子数。 根据以上迭代方程变形,得从前往后迭代的迭代式为 y=(4*y-1)/5 (i=1,2,…,5) (2) 如果经5次迭代保持y都是整数,即完成迭代。 2) 迭代描述 // 迭代求解5个水手分椰子 #include {int i; double k,x,y; i=1;k=1.0;y=k; while(i<=5) { i++;y=(4*y-1)/5; // 递推求后一个水手时的椰子y if(y!=floor(y)) { k=k+1.0;y=k;i=1;} // 若y不是整数,k增1从头重试 } x=5*k+1; // 此处k为第1个水手所藏椰子数 printf(\"%d个水手分椰子,原有椰子至少有:%6.0f个.\\n\} (2) 递推求解 1) 确定递推关系 设置y数组,第i个水手藏椰子数为y(i)(i=1,2,…,5)个,第二天5个水手醒来后各分得椰子为y(6)个,依题意原来这堆椰子数为 x=5*y(1)+1 为了求取y(1),实施递推。相邻两人所藏椰子数y(i)与y(i+1)之间的关系为 4*y(i)=5*y(i+1)+1 (i=1,2,…,5) (3) 习惯按时间顺序递推,递推式变形为从y(i)推出y(i+1)的形式,即 y(i+1)=(4*y(i)-1)/5 (i=1,2,…,5) (4) 第二个问题,递推的初始(边界)值如何确定? 问题本身没有初始(边界)条件限制,只要求上面5个递推方程所涉及的6个量y(i)都是正整数。也就是说,若有6个整数y(i)满足5个方程4*y(i)=5*y(i+1)+1, (i=1,2,…,5)即为所求的一个解。 首先y(1)赋初值k(取值从1开始递增)后推出y(2),由y(2)推出y(3),…,依此经5次递推得y(6)。如果某一次推出的不是整数,则中止继续往后推,返回k增1后赋值给y(1),从头开始。如果5次递推都是整数,则输出原有椰子数5*y(1)+1后结束。 2) 递推描述 // 递推求解5个水手分椰子 #include {int i; double k,x,y[7]; i=1;k=1.0;y[1]=k; while(i<=5) { i++;y[i]=(4*y[i-1]-1)/5; // 递推求后一个水手时的椰子y(i) if(y[i]!=floor(y[i])) { k=k+1.0;y[1]=k;i=1;} // 若y(i)不是整数,k增1重试 } x=5*y[1]+1; printf(\"%d个水手分椰子,原有椰子至少有:%6.0f个.\\n\} 数据测试: 5个水手分椰子,原有椰子至少有: 15621个. 3-10 n个水手分椰子 有n个水手来到一个岛上,采了一堆椰子后,因为疲劳都睡着了。一段时间后,第一个水手醒来,悄悄地将椰子等分成n份,多出m个椰子,便给了旁边的猴子,然后自己藏起一份,再将剩下的椰子重新合在一起,继续睡觉。不久,第二名水手醒来,同样将椰子了等分 成n份,恰好也多出m个,也给了猴子。然而自己也藏起一份,再将剩下的椰子重新合在一起。以后每个水手都如此分了一次并都藏起一份,也恰好都把多出的m个给了猴子。第二天,n个水手醒来,发现椰子少了许多,心照不宣,便把剩下的椰子分成n份,恰好又多出m个,给了猴子。 对于给定的整数n,m(约定0 y(i)=(n*y(i+1)+m)/(n-1) (i=1,2,…,n) 为精简试验的次数,确保从y(n+1)递推出整数y(n),显然y(n+1)的试验取值k只能取k%(n-1)=n-m-1。因而在循环前k赋初值k=n-m-1,以后k按n-1增值。 (2) n个水手分椰子递推描述 // n个水手分椰子,每次给猴子m个椰子 #include double k,x,y[20]; printf(\"请输入人数n(1 y[i]=(y[i+1]*n+m)/(n-1); // 递推求前一个水手时的椰子 if(y[i]!=floor(y[i])) {k=k+n-1;y[n+1]=k;i=n+1;} // 若y(i)不是整数,k增n-1重试 } x=n*y[1]+m; printf(\"原有椰子至少为:%8.0f个.\\n\ for(i=1;i<=n;i++) {printf(\"第%2d个水手面临椰子:\ printf(\"%8.0f=%d*%8.0f+%d个,\ printf(\"藏%8.0f个.\\n\ } printf(\"最后一起分时有椰子:\"); printf(\"%8.0f=%d*%8.0f+%d个.\ printf(\"每人分得%8.0f个.\\n\ } 数据测试: 请输入人数n(1 最后一起分时有椰子: 5116=5* 1023+1个.每人分得 1023个. 4-1 阶乘的倒数和 阶乘n!定义: n!=1(n=1);n!=n*(n-1)! (n>1) 设计求n!的递归函数,调用该函数求 s1111 1!2!n!(1) 设计要点 定义n!的递归函数f(n),在求和的k(1——n)循环中实施求和 s+=(double)1/f(k); (2) 递归描述 #include if(n==1) g=1; else g=n*f(n-1); return(g); } void main() { int k,n; double s=1; printf(\" 请输入n: \");scanf(\"%d\ for(k=1;k<=n;k++) s+=(double)1/f(k); printf(\" s=%f \\n\ } 数据测试: 请输入n: 15 s=2.718282 4-2 递归求解裴波那契数列 已知裴波那契数列定义: f1f21,fnfn1fn2(n2) 建立f数列的递归函数,求f数列的第n项与前n项之和。 (1) 递归设计 解:定义f数列的递归函数f(n),在求和的k(1——n)循环中实施求和 s+=f(k)。 (2) 递归描述 #include long f(int n) { long g; if(n==1 || n==2) g=1; else g=f(n-1)+f(n-2); return g; } void main() { int k,n; long s=0; printf(\" 请输入n: \");scanf(\"%d\ for(k=1;k<=n;k++) s+=f(k); printf(\" f(%d)=%ld \\n\ printf(\" s=%ld \\n\ } 数据测试: 请输入n: 40 f(40)=102334155 s=267914295 4-3 递归求解b数列 已知b数列定义: b11,b22,bn3bn12bn2(n2) 建立b数列的递归函数,求b数列的第n(n≤30)项与前n项之和。 解:#include if(n==1) g=1; else if(n==2) g=2; else g=3*b(n-1)-2*b(n-2); return g; } void main() { int k,n; long s=0; printf(\" 请输入n: \");scanf(\"%d\ for(k=1;k<=n;k++) s+=b(k); printf(\" b(%d)=%ld \\n\ printf(\" s=%ld \\n\ } 数据测试: 请输入n: 30 b(30)=536870912 s=1073741823 4-4 递归求解双递推摆动数列 已知递推数列: a(1)=1,a(2i)=a(i)+1,a(2i+1)=a(i)+a(i+1),(i为正整数) 试建立递归,求该数列的第n(n<100000)项与前n项的和。 // 摆动数列 #include if(n==1) g=1; else if(n%2==0) g=a(n/2)+1; else g=a((n-1)/2)+a((n+1)/2); return g; } void main() { int k,n; long s=0; printf(\" 请输入n: \");scanf(\"%d\ for(k=1;k<=n;k++) s+=a(k); printf(\" a(%d)=%d \\n\ printf(\" s=%ld \\n\ } 数据测试: 请输入n: 2014 a(2014)=107 s=215629 4-5 辉三角的递归求解 应用递归设计输出n行辉三角。 // 辉三角递归设计 void c(int a[],int n) {int i; if(n==0) a[1]=1; else if(n==1) {a[1]=1;a[2]=1;} else {c(a,n-1); a[n+1]=1; for(i=n;i>=2;i--) a[i]=a[i]+a[i-1]; a[1]=1; } } #include { int i,j,k,n,a[100]; printf(\" 请输入辉三角的行数:\"); scanf(\"%d\for(j=0;j<=n;j++) {c(a,j); for(k=1;k<=30-2*j;k++) printf(\" \"); for(i=1;i<=j;i++) printf(\"%4d\ printf(\"%4d\\n\ } } 数据测试: 请输入辉三角的行数:10 即得10行辉三角(略)。 4-6 顺转矩阵的递归设计 当数阵的行数与列数不相等时,数阵称为矩阵。显然,顺转方阵是顺转矩阵的特例。图 4-8为5行6列的顺转矩阵。 试应用递归设计构造并输出任意指定m行×n列的顺转矩阵。 图4-8 5行6列顺转矩阵 (1) 递归设计要点 构建m行n列的旋转矩阵,设置二维数组a(m,n),a[h][v]存放矩阵中第h行第v列的整数。 1) 递归属性 把m行n列矩阵从外到分圈,外圈是一个m-2行n-2列阶顺转矩阵,具有与原问题相同的特性属性。 因此,设置旋转矩阵递归函数t(b,s,d),其中b为每个矩阵的起始位置;s=min(m,n)是矩阵行与列的最小值;d是为a数组赋值的整数。 b赋初值1(取数组下标从1开始,矩阵的起始位置为(1,1)。以后每一圈后进入下一矩阵,起始位置b需增1。 d从1开始递增1取值,分别赋值给数组的各元素,至m*n为止。 s从矩阵的阶数min(m,n)开始,每一圈后进入矩阵,s需减2。 s>1时,在函数t(b,s,d)中还需调用t(b+1,s-2,d)。 s=0时返回,作为递归的出口; s=1且m=n,即矩阵为一方阵且只有一个数,显然为a[b][b]=d,返回; s>1时,在函数t(b,s,d)中还需调用t(b+1,s-2,d)。 2) 一圈中各元素赋值 递归函数t(b,s,d)中对矩阵的一圈的各边的各个元素赋值: 1) 一圈的上行n+1-2*b个元素,从左至右递增 for(j=1;j<=n+1-2*b;j++) { a[h][v]=d;v++;d++;} // 行号h不变,列号v递增,数d递增 2) 一圈的右列m+1-2*b个元素,从上至下递增 for(j=1;j<=m+1-2*b;j++) { a[h][v]=d;h++;d++;} // 列号v不变,行号h递增,数d递增 3) 一圈的下行n+1-2*b个元素,从右至左递增 for(j=1;j<=n+1-2*b;j++) { a[h][v]=d;v--;d++; // 行号h不变,列号v递减,数d递增 if(d>m*n) break; } 4) 一圈的右列m+1-2*b个元素,从下至上递增 for(j=1;j<=m+1-2*b;j++) { a[h][v]=d;h--;d++; // 列号v不变,行号h递减,数d递增 if(d>m*n) break; } 经以上4步,完成一圈的赋值。 3) 完善递归出口 注意到当s=min(m,n)为奇数时,最后一圈(s=1)只有一半。为避免最后一圈的另一半再赋值可能因数据覆盖导致出错,当完成整数d=m*n赋值后,即返回,作为递归的出口。 主程序中,只要带实参调用递归函数t(1,min(m,n),1)即可。 按圈赋值完成,输出m行n列的顺转矩阵。 (2) 递归描述 // m×n顺转矩阵递归设计 #include { int h,v,b,s,d; printf(\" 数阵为m行n列,请确定m,n:\");scanf(\"%d,%d\s=m>n?n:m; b=1;d=1; void t(int b,int s,int d); // 递归函数说明 t(b,s,d); // 调用递归函数 printf(\" %d×%d顺转矩阵: \\n\ for(h=1;h<=m;h++) {for(v=1;v<=n;v++) printf(\" %3d\ printf(\"\\n\"); } return; } void t(int b,int s,int d) // 定义递归函数 { int j,h=b,v=b; if(s<=0) return; // 递归出口 if(s==1 && m==n) // n=m且n为奇数时的递归出口 { a[h][v]=d;return;} for(j=1;j<=n+1-2*b;j++) // 一圈的上行从左至右递增 { a[h][v]=d;v++;d++;} for(j=1;j<=m+1-2*b;j++) // 一圈的右列从上至下递增 { a[h][v]=d;h++;d++;} for(j=1;j<=n+1-2*b;j++) // 一圈的下行从右至左递增 { a[h][v]=d;v--;d++; if(d>m*n) break; // min(m,n)为奇数且n>m时停止循环 } for(j=1;j<=m+1-2*b;j++) // 一圈的左行从下至上递增 { a[h][v]=d;h--;d++; if(d>m*n) break; // min(m,n)为偶数或者n 数据测试: 数阵为m行n列,请确定m,n:7,10 7×10顺转矩阵: 1 2 3 4 5 6 7 8 9 10 30 31 32 33 34 35 36 37 38 11 29 52 53 54 55 56 57 58 39 12 28 51 66 67 68 69 70 59 40 13 27 50 65 64 63 62 61 60 41 14 26 49 48 47 46 45 44 43 42 15 25 24 23 22 21 20 19 18 17 16 4-7 顺转矩阵的递推设计 试把m×n顺转矩阵的递归设计转变为递推设计,并进行比较。 (1) 递推设计 对输入的m,n,取c=min(m,n),计算数字矩阵的圈数d=(c+1)/2。 设置i(1——d)循环,从外圈至圈,分4边进行递推赋值。 (2) 递推描述 // m×n数字旋转矩阵 #include {int i,j,c,d,h,v,m,n,s,a[30][30]; printf(\" m行n列矩阵,请确定m,n: \"); scanf(\"%d,%d\ c=n; if(m for(v=i;v<=n-i;v++) {s++;a[h][v]=s;} // d圈的首行从左至右赋值 for(h=i;h<=m-i;h++) {s++;a[h][v]=s;} // d圈的尾列从上至下赋值 for(v=n+1-i;v>=i+1;v--) { s++;a[h][v]=s; // d圈的尾行从右至左赋值 if(s==m*n) {i=d;break;} } // 赋值完成即行退出 for(h=m+1-i;h>=i+1;h--) { s++;a[h][v]=s; // d圈的首列从下至上赋值 if(s==m*n) {i=d;break;} } } printf(\" %d行%d列旋转矩阵为:\\n\ for(i=1;i<=m;i++) { for(j=1;j<=n;j++) // 按m行n列输出矩阵 printf(\"%4d\ printf(\"\\n\"); } } 数据测试: m行n列矩阵,请确定m,n: 8,9 8行9列旋转矩阵为: 1 2 3 4 5 6 7 8 9 30 31 32 33 34 35 36 37 10 29 52 53 54 55 56 57 38 11 28 51 66 67 68 69 58 39 12 27 50 65 72 71 70 59 40 13 26 49 64 63 62 61 60 41 14 25 48 47 46 45 44 43 42 15 24 23 22 21 20 19 18 17 16 4-8 复杂排列的递归实现 应用递归设计实现n个相同元素与另m个相同元素的所有排列。 (1) 设计要点 设置递归函数p(k),1≤k≤m+n,元素a[k]取值为0或1。 当k=m+n时,作变量h统计“0”的个数。若h=m则打印输出一排列,并用s统计排列个数。然后回溯返回,继续。 当k int m,n,r,a[30]; long s=0; void main() { int p(int k); printf(\" input n,m: \"); scanf(\"%d,%d\ printf(\" %d个1与%d个0的排列:\\n\ p(1); // 从第1个数开始 printf(\"\\n s=%ld \\n\输出排列的个数 } // 排列递归函数 #include { for(i=0;i<=1;i++) { a[k]=i; // 探索第k个数赋值i if(k==m+n) // 若已到m+n个数则检测0的个数h { for(h=0,j=1;j<=n+m;j++) if(a[j]==0) h++; if(h==m) // 若0的个数为m个,输出一排列 { s++; printf(\" \"); for(j=1;j<=n+m;j++) printf(\"%d\ if(s%10==0) printf(\"\\n\"); } } else p(k+1); // 若没到n+m个数,则调用p(k+1)探索下一个数 } } return s; } 数据测试: input n,m: 3,4 3个1与4个0的排列: 0000111 0001011 0001101 0001110 0010011 0010101 0010110 0011001 0011010 0011100 0100011 0100101 0100110 0101001 0101010 0101100 0110001 0110010 0110100 0111000 1000011 1000101 1000110 1001001 1001010 1001100 1010001 1010010 1010100 1011000 1100001 1100010 1100100 1101000 1110000 s=35 4-9 指定零数拆分的递归设计 我们探讨求解一般的整数拆分问题:把指定整数ss拆分为ms个指定互不相同的整数b1,b2,,bms之和,共有多少种不同的拆分法?展示出所有这些拆分式. (1) 递归设计要点 我们考虑从键盘输入的ms个数中取m(m (2) 递归设计描述 // 和数ss,零数取自指定数 #include int k,m,n,ms,ss,a[100],b[100]; void main() { int i,h,wmin,wmax; int c(int k); printf(\" 输入和为:\"); scanf(\"%d\ printf(\" 输入零数的个数:\");scanf(\"%d\ printf(\" 依次由小到大输入零数:\\n\"); for(i=1;i<=ms;i++) { printf(\" b[%d]=\ for(h=0,i=1;i<=ms;i++) { h=h+b[i]; if(h>ss) {wmax=i-1;break;} } if(i>ms) // 输入的零数组太小,程序返回 { printf(\" 输入的零数组太小!\\n\"); return; } for(h=0,i=ms;i>=1;i--) { h=h+b[i]; if(h>ss) {wmin=ms-i;break;} } for(m=wmin;m<=wmax;m++) // 从ms个数中取m个数 c(1); printf(\"n=%d\\n\输出拆分种数n } // 组合递归函数c(k) int c(int k) { int i,j,t; if(k<=m) { a[0]=0; for(i=a[k-1]+1;i<=ms+k-m;i++) { a[k]=i; // 探索第k个数赋值i { if(k==m) // 若已到m个数时,检测m个数之和 { for(t=0,j=m;j>0;j--) t=t+b[a[j]]; if(t==ss) // 若m个数之和为ss,输出一个拆分式 { n++;printf(\"%d=\ for(j=1;j else c(k+1); // 若没到m个数,则调用 c(k+1) } } } return n; } 数据测试: 输入和为:15 输入零数的个数:5 依次由小到大输入零数:1,3,4,7,8 15= 7+ 8 15= 3+ 4+ 8 15= 1+ 3+ 4+ 7 n=3 5-1 倒桥本分数式 把1,2,...,9•这9个数字填入下式的9个方格中,数字不得重复,要求1不得填在各分数的分母,且式中各分数的分子分母没有大于1的公因数,使下面的分数等式成立 □□ □□ □□ ── + ─── = ── □ □ □ 这一填数分数等式共有多少个解•? 解: 在桥本分数式回溯设计中修改 // 把1,2,...,9填入□□/□+□□/□=□□/□ #include {int g,i,k,u,t,a[10]; long m1,m2,m3; i=1;a[1]=1; while (1) {g=1; for(k=i-1;k>=1;k--) if(a[i]==a[k]) {g=0;break;} // 两数相同,标记g=0 if(i==9 && g==1 && a[1]1 && a[7]>1) {m1=a[2]*10+a[3]; m2=a[5]*10+a[6]; m3=a[8]*10+a[9]; for(t=0,u=2;u<=9;u++) {if(a[1]%u==0 && m1%u==0) t=1; if(a[4]%u==0 && m2%u==0) t=1; if(a[7]%u==0 && m3%u==0) t=1; } if(t==0 && m1*a[4]*a[7]+m2*a[1]*a[7]==m3*a[1]*a[4]) // 判断等式 {printf(\" %d/%ld+%d/%ld\ printf(\"=%d/%ld \\n\} } if(i<9 && g==1) {i++;a[i]=1;continue;} // 不到9个数,往后继续 while(a[i]==9 && i>1) i--; // 往前回溯 if(a[i]==9 && i==1) break; else a[i]++; // 至第1个数为9结束 } } 数据测试: 41/3+89/6=57/2 74/3+95/6=81/2 5-2 10数字分数式 把0,1,2,...,9•这10个数字填入下式的10个方格中,要求: □ □ □ ── + ─── = ── □□ □□□ □□ (1) 各数字不得重复; (2) 数字“0”不得填在各分数的分子或分母的首位; (3) 式中各分数为最简真分数,即分子分母没有大于1的公因数。 这一分数等式填数趣题究竟共有多少个解答•? 试应用回溯求出所有解答。 回溯设计: 设置a数组表示式中的10个数字,即 a(1)a(4)a(8) a(2)a(3)a(5)a(6)a(7)a(9)a(10)同时,记式中的3个分母分别为 m1=a(2)a(3)=a(2)*10+a(3) m2=a(5)a(6)a(7)=a(5)*100+a(6)*10+a(7) m3=a(9)a(10)=a(9)*10+a(10) 在上述回溯设计基础上修改若干参数: 数字从9个增加到10个,因而i<9改为i<10;i==9改为i==10; 数组元素取值修改为从“0”开始,即a(1)=0;a(i)=0; 数字“0”不得在各分数的分子与分母的首位,即“0”只能在a(3),a(6),a(7)与a(10)这4个数字中,因而在输出解的条件中增加a(3)*a(6)*a(7)*a(10)=0。 此外,需增加判断3个分数是否为真分数的测试循环。 10数字分数式回溯描述: // 10数字分数式 #include {int g,i,k,s,t,u,a[11]; long m1,m2,m3; i=1;a[1]=0;s=0; while (1) {g=1; for(k=i-1;k>=1;k--) if(a[i]==a[k]) {g=0;break;} // 两数相同,标记g=0 if(i==10 && g==1 && a[3]*a[6]*a[7]*a[10]==0) {m1=a[2]*10+a[3]; m2=a[5]*100+a[6]*10+a[7]; m3=a[9]*10+a[10]; if(a[1]*m2*m3+a[4]*m1*m3==a[8]*m1*m2) // 判断等式 {t=0; for(u=2;u<=9;u++) // 测试3个分数是否为真分数 {if(a[1]%u==0 && m1%u==0) {t=1;break;} if(a[4]%u==0 && m2%u==0) {t=1;break;} if(a[8]%u==0 && m3%u==0) {t=1;break;} } if(t==0) {printf(\" %d/%ld+%d/\ printf(\"%ld=%d/%ld\\n \ } } } if(i<10 && g==1) {i++;a[i]=0;continue;} // 不到10个数,往后继续 while(a[i]==9 && i>1) i--; // 往前回溯 if(a[i]==9 && i==1) break; else a[i]++; // 至第1个数为9结束 } } 数据测试: 4/19+5/608=7/32 5-3 回溯探索n数素数和环 把前n个正整数摆成一个环,如果环中所有相邻的两个数之和都是一个素数,该环称为一个n项素数和环。 对于指定的n,构造并输出所有不同的素数和环。 回溯设计: 设置a数组在前n个正整数中取值。为避免重复输出,约定第1个数a(1)=1。 设置b数组标记奇素数。对指定的正整数n,首先用试商判别法,把2n围的奇素数标记为“1”,例如,b(7)=1表明7为素数。 在永真循环中,i从2开始至n递增,a[i]从2开始至n递增取值。 (1) 元素a(i)的取值是否可行,赋值t=1;然后进行判断: 若a(j)==a(i)(j=1,2,…,i-1),即a(i)与前面的a(j)相同,a(i)取值不行,标注t=0; 若 b(a(i)+a(i-1))!=1,即所取a(i)与其前一项之和不是素数,标注t=0。 (2) 若判断后保持t=1;说明a(i)取值可行。 此时若i已取到n ,且 b(a(n)+1)=1(即首尾项之和也是素数),输出一个解。 若i 考虑到当n较大时,n项素数和环非常多,约定只输出5个解后提前结束。 回溯实现n项素数环描述: // 回溯求解n项素数和环 #include { int t,i,j,n,k,s,a[2000],b[1000]; printf(\" 前n个正整数组成素数和环,请输入整数n: \"); scanf(\"%d\ for(k=1;k<=2*n;k++) b[k]=0; for(k=3;k<=2*n;k+=2) {for(t=0,j=3;j<=sqrt(k);j+=2) if(k%j==0) {t=1;break;} if(t==0) b[k]=1; // 奇数k为素数的标记 } printf(\" 前%d个正整数组成素数和环,其中5个为:\\n\ a[1]=1;s=0; i=2;a[i]=2; while(1) {t=1; for(j=1;jif(a[j]==a[i] || b[a[i]+a[i-1]]!=1) // 出现相同元素或非素时返回 {t=0;break;} if(t && i==n && b[a[n]+1]==1) { s++; printf(\" %d: 1\ for(j=2;j<=n;j++) printf(\ printf(\"\\n\"); if(s==5) return; } if(t && i while(a[i]==n && i>1) i--; // 实施回溯 if(i>1) a[i]++; else break; } } 数据测试: 前n个正整数组成素数和环,请输入整数n: 10 前10个正整数组成素数和环,其中5个为: 1: 1,2,3,4,7,6,5,8,9,10 2: 1,2,3,4,7,10,9,8,5,6 3: 1,2,3,4,9,8,5,6,7,10 4: 1,2,3,8,5,6,7,4,9,10 5: 1,2,3,8,5,6,7,10,9,4 5-4 枚举求解8数素数和环 枚举求解8项素数和环,并与回溯结果进行比较。 设计要点: 为简化输出,环序列简化为一般序列输出,为避免重复,约定首项为“1”。 环中的每一项为一个数字,相连的8项构成一个8位数。因而设置a循环在没有重复数字数字且以“1”开头的8位数812345678——18765432中枚举。注意到所有1——8没有重复数字的8位数的数字和为9的倍数,该数也为9的倍数,为此,枚举循环步长可取9,以精简枚举次数。 为操作与判断方便,设置3个数组: f数组统计8位数a中各个数字的频数。如f(3)=2,即a中有2个数字“3”。 g数组表示8位数a中每位数的数字。如g(4)=6,即a的从高位开始第4位数为数字“6”。 b数组标记整数x是否为素数。如b(13)=1,标识“1”表示13为素数,标识“0”为非素数。 枚举实施: 1) 注意到8项中每相邻两项之和不超过15,对15以的5个素数用b数组标注“1”,其余均为“0”。 2) 在8位数的a 循环中,对a实施8次求余分离出各个数字x,应用f(x)++统计数字x的频数,应用g(9-k)=x记录a的各位数字。 3) 设置k(1——8)判断循环: 若f(k)!=1 ,表明数字k出现重复或遗漏,返回。 若 b(g(k)+g(k+1))!=1,表明相邻的第k项与第k+1项之和不是素数,返回。顺便说明, 为判断方便,首项“1”先行赋值给g(9),以与g(8)相邻,在k循环中一道进行判别。 4) 通过以上判断筛选的a,其各个数字即为所求的8项素数环的各项,打印输出。 枚举实现8项素数和环描述: // 8项素数和环枚举求解 #include { int t,k,s,x,g[10],f[10],b[18];long a,y; for(k=1;k<=15;k++) b[k]=0; g[9]=1;s=0; b[3]=b[5]=b[7]=b[11]=b[13]=1; // 5个奇素数标记 printf(\" 8项素数和环:\\n\"); for(a=12345678;a<=18765432;a+=9) // 步长为9枚举8位数 {t=0;y=a; for(k=0;k<=9;k++) f[k]=0; for(k=1;k<=8;k++) {x=y%10;f[x]++; // 分离a的8个数字,用f数组统计x的个数 g[9-k]=x; // 用g数组记录a的第k位数字 y=y/10; } for(k=1;k<=8;k++) if(f[k]!=1 || b[g[k]+g[k+1]]!=1) t=1; if(t==1) continue; // 有相同数字或相邻和非素,返回 s++; printf(\" %d: 1\输出8项素数和环 for(k=2;k<=8;k++) printf(\ printf(\"\\n\"); } } 数据测试: 8项素数和环: 1: 1,2,3,8,5,6,7,4 2: 1,2,5,8,3,4,7,6 3: 1,4,7,6,5,8,3,2 4: 1,6,7,4,3,8,5,2 5-5 递归求解20数素数和环 应用递归求解20项素数和环。 // 递归求解素数环问题 #include int n,a[2000],b[1000];long s=0; void main() { int t,j,k; int p(int k); printf(\" 前n个正整数组成素数环,请输入整数n: \"); scanf(\"%d\ for(k=1;k<=2*n;k++) b[k]=0; for(k=3;k<=2*n;k+=2) {for(t=0,j=3;j<=sqrt(k);j+=2) if(k%j==0) {t=1;break;} if(t==0) b[k]=1; // 奇数k为素数的标记 } a[1]=1;k=2; p(k); printf(\" 前%d个正整数组成素数环,以上是其中3个。\\n\} // 素数环递归函数p(k) #include { for(i=2;i<=n;i++) { a[k]=i; // 探索第k个数赋值i for(u=0,j=1;j<=k-1;j++) if(a[k]==a[j] || b[a[k]+a[k-1]]==0) // 若出现重复数字 u=1; // 若第k数不可置i,则u=1 if(u==0) // 若第k数可置i,则检测是否到n个数 { if(k==n && b[a[n]+a[1]]==1 && s<3) // 若已到n个数时打印出一个解 { s++; printf(\" %ld: 1\ for (j=2;j<=n;j++) printf(\ printf(\"\\n\"); } else p(k+1); // 若没到m个数,则探索下一个数 p(k+1) } } } return s; } 数据测试: 前n个正整数组成素数环,请输入整数n: 20 1: 1,2,3,4,7,6,5,8,9,10,13,16,15,14,17,20,11,12,19,18 2: 1,2,3,4,7,6,5,8,9,10,13,16,15,14,17,20,11,18,19,12 3: 1,2,3,4,7,6,5,8,9,10,13,18,19,12,11,20,17,14,15,16 前20个正整数组成素数环,以上是其中3个。 5-6 奇数位全错位问题 在1—n的全排列中,统计并展示偶数在其自然位而奇数全错位的所有排列。 (1) 回溯设计 在以上5.6回溯设计的基础上修改一个条件:把a[i]!=i修改为i%2=0 and a[i]==i or i%2!=0 and a[i]!=i,即只有偶数在自然位或奇数错位时才接着进行元素相等的判断,否则返回进行下一轮探索。 事实上可进一步简化,固定偶数位,例如2只能处于第2位,8只能处于第8位等,只对奇数进行探索和回溯,这样算法效率会大大增加。 (2) 算法描述 // 偶数不错位奇数全错位的所有排列 #include { int n,i,j,t,a[30];long s=0; printf(\" input n (2 printf(\"\\n s=%ld\\n\} (3)算法测试 input n (n<10):8 32147658 32547618 32741658 52147638 52741638 52743618 72143658 72541638 72543618 s=9 考察输出的解,所有偶数都在自然位,所有奇数都错位。 5-7 德布鲁金序列 由2个0或1组成的数环,形成2个由相连n个0,1数字组成的二进制数恰在环序列中出现一次。这个序列被称作n阶德布鲁金(Debrujin)序列(约定n阶德布鲁金序列由n个0开头)。 例如,n=3时,即3阶德布鲁金序列有00010111与00011101两个解: 解00010111中每相连3个数字组成的二进制数依次为000,001,010,101,011,111,110,100,这8个数恰各出现一次。 解00011101中每相连3个数字组成的二进制数依次为000,001,011,111,110,101,010,100,这8个数恰各出现一次。 以上两个解,事实上是互为顺时针方向与逆时针方向的关系,其中一个解为顺时针方向,则另一个解为逆时针方向。 输入正整数n,求解n阶德布鲁金序列。 1. 回溯设计要点 在n阶德布鲁金环序列中,共有m=2个二进制数字。设置一维a数组,约定前n个数字为0,即a(0)=a(1)=…=a(n-1)=0,a(n)=1,a(m-1)=1。 n n n 应用回溯法探求a(n+1)~a(m-2),这些元素取0或1。问题的解空间是由数字0或1组成的m-n-2位整数组,其约束条件是0的个数为m/2-n个,且没有相同的由相连n个数字组成的二进制数。 当i≤m-2时,a(i)从0取值; 当i>n+1且a(i)=1时回溯; 当i=n+1且a(i)=1时退出。 当a(n+1)~a(m-2)已取数字时,设置h统计其中0的个数,若h≠m/2-n,则返回; 若h=m/2-n,则进一步通过循环计算m1,m2,判断是否有相同的由n个数字组成的二进制数。 若存有相同的由n个数字组成的二进制数,标注t=1;若不存在有相同的由n个数字组成的二进制数,保持t=0,作打印输出。 按以上所描述的回溯的参量:n ,(计算m=2^n) 元素初值:a(n)=1,a(m-1)=1,其余数组元素初值取0。 取值点:a(i)=0,各数组元素从0开始取值。 回溯点:a(i)=1,各数组元素取值至1时回溯。 约束条件1:i=m-2 且 h=m/2-n (其中h为a(i)中0的个数) 约束条件2:m1≠m2,m1与m2分别为环序列中所有由相连的n个数字组成的前后的二进制数。 2. 回溯设计描述 // n阶德布鲁金环序列回溯设计 #include { int d,i,h,k,j,m,m1,m2,n,s,t,x,a[200]; printf(\"请输入(2 s=0; for(k=0;k<=m+n;k++) a[k]=0; a[n]=1;a[m-1]=1; i=n+1; while(1) {if(i==m-2) {for(h=0,j=n+1;j<=m-2;j++) if(a[j]==0) h++; if(h==m/2-n) // 判别是否有m/2-n个零 {for(t=0,k=0;k<=m-2;k++) for(j=k+1;j<=m-1;j++) {d=1;m1=0;m2=0; // 检验是否有相同的由n位二进制数 for(x=n-1;x>=0;x--) {m1=m1+a[k+x]*d; m2=m2+a[j+x]*d;d=d*2;} if(m1==m2) {t=1;break;} } if(t==0) { s++; // 统计解的个数并输出解 printf(\"NO(%4d): \ for(j=0;j<=m-1;j++) printf(\"%d\ printf(\"\\n\"); } } } if(i while(a[i]==1 && i>n+1) i--; // 向前回溯 if(a[i]==1 && i==n+1) break; else a[i]=1; } } 数据测试: 输入n=5,得5阶德布鲁金环序列的前3个解: NO( 1): 1111 NO( 2): 1111 NO( 3): 0111 …… NO(2048): 1001 当输入n=3或n=4时,可得相应的3阶或4阶环序列。 当n>5时,算法运行的时间迅速增加。解决5阶以上的德布鲁金环序列问题,算法有待进行优化与改进。 如果约定n阶德布鲁金环序列由n个1开头,算法应如果变通? 5-8 回溯实现复杂排列 应用回溯设计探索从n个不同元素中取m(约定1<m≤n)个元素与另外n-m个相同元素组成的复杂排列。 (1)回溯设计要点 引入变量k来控制0的个数,当k<n-m时,a(i)=0,元素需从0开始取值;否则,0的 个数已达n-m个,a(i)=1,即从1开始取值。这样处理,使0的个数不超过n-m,减少一些无效操作,提高了回溯效率。 按以上所描述的回溯的参量:n,m(m≤n) 元素初值:a(1)=0,数组元素取初值0。 取值点:当k<n-m时,a(i)=0,需从0开始取值;否则,a(i)=1,即从1开始取值。 回溯点:a(i)=n,各元素取值至n时回溯。 约束条件1:a(k)!=0 && a(i)=a(k) || a(i)*a(k)>0 && fabs(a(i)-a(k))=i-k, (其中i>k),排除同一列或同对角线上出现2个皇后。 约束条件2:i=n && k=n-m && i==n, 当取值达n个,其中n-m个零,且棋盘全控时输出一个解。 (2)回溯设计描述 #include {int i,j,k,n,m,t,a[N]; long s=0; printf(\" input n (n<10):\"); scanf(\"%d\ printf(\" input m(1 for(j=1;j<=n;j++) printf(\"%d\ printf(\" \"); if(s%10==0) printf(\"\\n\"); } if(t && (k while(a[i]==n) i--; // 调整或回溯或终止 if(i>0) {if(a[i]==0) k--; // 改变取值为0的元素值前先把0的个数k减1 a[i]++; } else break; } printf(\"\\n s=%ld\\n\} 数据测试: input n (n<10):5 input m(1 53400 54001 54002 54003 54010 54020 54030 54100 54200 54300 s=600 5-9 8对夫妇的特殊拍照 一对夫妇邀请了7对夫妇朋友来家餐聚,东道主夫妇编为0号,其他各对按先后分别编为1,2,…,7号。 餐聚后拍照,摄影师要求这8对夫妇男左女右站在一排,东道主夫妇相邻排位在横排的正中央,其他各对排位,1号夫妇中间安排1个人,2号夫妇中间安排2个人,依此类推。 共有多少种拍照排队方式? (1) 回溯设计要点 在n组每组2个相同元素(相当于n对情侣),a数组从0取到2n-1不重复,对n同余的两个数为一对编号:余数为0的为0号(即东道主),余数为1的为1号,…,余数为n-1的为n-1号。 例如,n=4,数组元素为0与4,对4同余,为一对“0”; 1与5对4同余,为一对“1”;一般地, i与4+i对4同余,为一对i,(i=0,1,2,3)。 返回条件修改为(当ja(j)=a(i) or a(j)%n=a(i)%n and (a(j)>a(i) or a(j)+1!=i-j) 其中a(j)=a(i),为使a数组的2n个元素不重复取值; a(j)%n=a(i)%n and a(j)>a(i),避免同一对取余相同的数左边大于右边,导致重复; a(j)%n=a(i)%n and a(j)+1!=i-j,避免同一对数位置相差不满足题意相间要求。 例如,a(j)=0时,此时a(i)=n,为0号情侣,位置应相差1(即中间没有人),即i-j=1。 a(j)=1时,此时a(i)=n+1,为1号情侣,位置应相差2(即中间有1人),即i-j=2。 这些都应满足条件a(j)+1=i-j。如果a(j)+1!=i-j,不满足要求,返回。 设m=2n,若满足条件(g>0 and i=m and a(1)%n#include {int i,j,g,n,m,s,a[20];