《信息学竞赛指导》参考答案
第一章 信息技术基础
第一节 计算机与信息社会
选择题:
1、B 2、C 3、D 4、B 5、B 6、D 7、C
第二节 计算机系统组成原理
一、选择题:
1、A 2、A 3、B 4、C 5、D 6、C 7、D 8、B 9、C 10、A
11、D 12、B 13、A 14、D 15、C 16、B 17、B 18、D 19、BDE 20、AD
21、B 22、A 23、ACDE 24、C 25、ACD
二、思考题
1、计算机系统由硬件和软件两大部分的组成。
2、CAD是“计算机辅助设计”(Computer Aided Design)
CAI是“计算机辅助教学”(ComputerAssisted Instructing)
CAT 是“计算机辅助翻译”(Computer Aided Translation)
CAM是“计算机辅助制造”(computer Aided Manufacturing)
CMOS是“互补金属氧化物半导体(complementary metal-oxicle-semiconductor)一种大规模应用于集成电路芯片制造的原料是微机主板上的一块可读写的RAM芯 片,用来保存当前系统的硬件配置和用户对某些参数的设定。
第三节 信息的表示
一、选择题:
1、C 2、C 3、A 4、C 5、A 6、C 7、D 8、C 9、BD 10、D
11、A 12、C 13、B
第四节 网络常识
选择题:
1、C 2、B 3、C 4、C 5、D 6、A 7、D 8、C 9、D 10、A
11、C 12、A 13、B 14、C 15、B 16、C
第五节 操作系统
选择题:
1、E 2、A 3、软件系统和硬件系统
4、任务栏、开始、时钟、系统已经启动了的程序
5、B 6、C 7、D 8、C 9、B 10、B 11、CD
第二章 组合数学解析
第一节加法原理与乘法原理
思考与练习
1、求1000!的末尾其有多少个零?
1000\5+1000\25+1000\125+1000\625=249。
2、在所有的七位数中,至少有连续四个1的数据其有多少个?
分只有4个连续1、只有5个连续1、只有6个连续1、只有7个连续1函数进行讨论。
3、求比1000小的正整数中含有数字1的数的个数。
分仅含1个1、仅含2个1、仅含3个1等三种情况进行讨论。
4、有n个不同的整数,从中取出两组来,要求第一组数里的最小数大于第二组数据的最大数,问有多少种方案?
设取的第一组数有a个,第二组有b个,而要求第一组数中最小数大于第二组中最大的,即只要取出一组m个数(设m=a+b),从大到小取a个作为第一组,剩余的为第二组。此时方案数为C(n,m)。从m个数中取第一组数共有m-1种取法。
总的方案数为
第二节 排列与组合
思考与练习
1、在24名选手中进行淘汰赛,最后产生一个冠军,问一共要举行多少场比赛?
23场显然。
2、在一个凸n边形中,没有任意三条对角线在其内部共点,求其全部对角线在其内部的交点数。
根据题意,每4个顶点可得到两条对角线,1个对角线交点,从n个顶点任取4个的方案有 。
3、若一个凸十二边形,无三条对角线在其内部其点。这些对角线被它们的交点其分成多少条线段?
根据图论知识,每个对角线交点有4个度,每个顶点去掉与相邻两个顶点的连线还有9个度,可以得到(4* +12*9)/2条边。
4、求n个完全相同的骰子能掷出多少种不同的方案?
相当于把n个小球放入6个不同的盒子里,为可重组合,即共有 种方案,即 种方案。
5、正整数n的一个k拆分就是把n表示为k个正整数的和。如果拆分不仅与各项的数值相关,也与各项的次序相关,这样的拆分叫做有序拆分,否则叫做无序拆分。
例如4=2+1+1=1+2+1=1+1+2是4的所有3个有序3拆分。试求正整数n的k拆分的个数。F(n,k)=F(n-k,k)+F(n-1,k-1)。
6、在2n个球中有n个相同,求从这2n个球中选取n个球的方案数。
相当于从n个不同的小球中分别取出m个小球(0≤m≤n),再从n个相同的小球中取出n-m个小球。共有方案: + +…+ =2n种。
7、求从点(0,0)到点(n,n)的不穿过直线y=x的非降路径数。
为格路问题(弱领先条件),即从(0,0)到(n,n),只能从对角线上方走,可以碰到对角线,故方案数为 - 。
8、排列的生成算法有序数法、字典排序法和邻位互换掭算法等,用这些不同的方法写出前5个自然数的全排列。
略。
第三节递推关系
思考与练习
1、求n位十进制中出现偶数个5的数的个数。
从分析n位十进制数出现偶数个5的数的结构入手,p1p1...pn-1是n-1位十进制数,若含有偶数个5,则pn取5以外的0,1,2,3,4,6,7,8,9九个数中的一个,若p1p1...pn-1只有奇数个5,则取pn=5,使p1p1...pn-1pn成为出现偶数个5的十进制数。
令an位十进制数中出现5的数的个数,bn位十进制数中出现奇数个5的数的个数。 故有: an=9an-1+bn-1,bn=9bn-1+an-1,a1=8,b1=1。
2、平面上有n条直线,任意两条直线相交于不同的点,求这n条直线将平面分成的区域数f(n)。
an=an-1 +2(n-1),a1=2。
3、平面上有n条直线,任意两条直线相交于不同的点,求这n条直线的交点数f(n)。
A(n)=a(n-1)+n-1,a1=0。
4、N个不同的自然数顺序进栈,问有多少种不同的出栈方式?
B(n)= 。
5、求n位二进制数中最后三位数为010的数的个数。
an+an-2=2n-3,n≥5,a3=1,a4=2。
6.什么是常系数线性齐次方程和非齐次方程,其求解方法各是什么,请整理成一篇文章。
略。
第四节 生成函数
思考与练习
1、求序列5,6,7,……,n+5,……的生成函数。
G(X)=5+6x+7x2+8x3+……(n+5)xn+……
=5(1+x+x2+x3+……)+(x+2x2+3x3+……)
=5/(1-x)+x/(1-x)2
2、求用1角、2角和5角邮票贴出不同邮费的方案数。
G(x)=(1+x+x2+x3+……)(1+x2+x4++x6……)(1+x5+x10++x15……),展开。
3、若有1克砝码2枚,2克砝码1枚,3克砝码2枚,4克砝码2枚,各能称出哪些重量?分别有多少种方案?
(1+x+x2)(1+ x2)( 1+ x3+ x6)( 1+ x4+ x8),展开,项数即为可称出的方案数,系数即为各重量的方案数。
4、求不定方程x1+2x2=10 的非负整数解的个数。
相当于将10个相同的球放入两个不同的盒子。
G(x)=(1+x+x2+x3+……x10)(1+x+x2+x3+……x5)展开后指数为10的项的系数。
5、求S={2?a,3?b}的4可重排列数。
G(x)=(1+x/1!+x2/2!)(1+x/1!+x2/2!+x3/3!),展开后系数为4的项。
6、求n位二进制数中只有最后三位为010的不同三进制数的数目。
An= cos(n )- sin(n )+ 2n,n≥3。
第五节 组合问题设计
思考与练习
1、夫妇入座问题,n对夫妇出席宴会,围圆桌而坐,要求同一对夫妇不能相邻,问有多少种不同的入席方法?
先女人定位,园排列n!/n=(n-1)!;再男人定位,即二重圆错排问题;两数相乘
2、砝码问题,1624年,法国数学家德.梅齐里亚克(Meziriac,1581-1638)提出了著名的砝码问题。即一位商人有一个重40磅的砝码,一不小心跌落在地上摔成四块,称得每一小块砝码的重都是整磅数,且用这四块小砝码可以称出1到40磅之间任意重量的物品,问这四块小砝码的重量各为多少?
1,3,9,27。
3、骨牌连环阵,正方形的骨牌上刻有1到6个点,也有一种骨牌是空白的,即骨牌面上的点数有七种。对于这样两个点数相异的骨牌联在一起形成一个长2宽1的长方形骨牌对,称为多米诺骨牌对儿。如果把多米诺骨牌对儿摆成一圈,使得任意两个相相邻的骨牌对儿的两个靠近的骨牌有相同的点数,则称此圈为一个骨牌连环阵。如何才能构造一个最大的骨牌连环阵。
略。
4、三对夫妻同时来到一个渡口,都欲渡过河去。但渡口只有一条最多能载两人的小船,妻子在其丈夫不在场时也不能和另外的男子在一起,如何安排渡河方案才能使六人最快地渡到河的对岸去?
略。
5、某人给六人各写了一封信,当最后将这六封信分别放进六个信封时,结果都放错的可能有多少种?
略。
6、甲乙丙三人,甲能看到乙和丙,乙能看到丙,丙谁也看不到。有人在甲乙丙三人身后贴纸条,纸条是由三张白色的和两张红色的组成,他问甲身后贴的是什么颜色的纸条,甲不知。又问乙,乙也不知。最后又问丙,丙知道。丙为什么知道?丙身后贴的是什么颜色的纸条?
略。
第六节 逻辑代数初步
思考与练习
1、证明反演律。
列出真值表,逐项验证即可。
2、化简A + + D+C+BD。
A + + D+C+BD=A + + C+ D+C+BD
= + C+ D+C+BD
= +C+ D+BD
3、化简A + C+BC+ D。
A + C+BC+ D=A C+ A + BC+ C+ A BC+ BC+ D
= C+BC+ A + BC+ D
=C+ A + D
第三章 程序设计
第一节 概述
思考与练习:
1、 C语言的程序一般由哪些部分组成?
C语言一般由若干个源文件组成,一个源文件由若干个函数组成,而主函数有且只有一个。
2、 在什么情况下程序要加上头函数?
当函数中包含有库函数时,应该调用头文件
3、 如何编译、运行一个C程序?
当源程序写好后,应该对其进行编译操作(ALT+F9),以便找出其中的语法错误;当编译通过后,应该对其进行连接操作(Compile→Link exe file),以便将其他库函数“嵌入”到程序中,最后执行该程序(CTRL+F9)。
第二节 数据类型
思考与练习
1、什么是常量?什么是变量?
常量就是在程序执行过程中固定不变的数据。
变量就是在程序执行过程中,可以根据程序的要求而随时发生改变的数据存储单元。
2、整形变量和实型变量的取值范围各是多少?
整形变量可以再细分成多种类型,最大的取值范围是-2147483648—2147483647, 实型变量中单精度类型的取值范围是3.4E-38—3.4E38,双精度类型变量的取值范围是1.7E-308—1.7E+308
3、 常见有哪些运算符?
常见有算术运算符、关系运算符、逻辑运算符、位操作运算符、赋值运算符、条件运算符、逗号运算符、指针运算符、求字节运算符、分量运算符、下标运算符等。
4、C语言有哪些算术运算?
常见的算术运算有反运算;自加、自减运算;正、负号运算
5、 输入和输出函数各有何特点?
在C语言中,输入和输出函数较多,比较常用的输入函数是scanf,它从键盘中接受数据,然后将其存放到变量中。常用的输出函数是prinft,是向标准输出设备:显示器输出内存单元中的数据。
第三节思考与练习
3、写出以下程序的执行结果
x=5
x=5
x=3
x=7 z=0
x=3 z=1
第六节指针
思考与练习:
1、输入两个字符串,将它们拼接起来,放在一个新的数组中。
#include "stdio.h"
main()
{
static char st1[20]="abcdefg",st2[ ]="hijklmn",*p1,*p2;
p1=st1;
p2=st2;
while (*p1!='\0')
p1++;
while (*p2!='\0') {
*p1=*p2;
p1++;
p2++;
}
printf("%s",st1);
}
2、输入一个字符串,统计出其中数字的个数和e到k之间的字母的个数。
#include "stdio.h"
main()
{
static char st1[30]="abcde29fgsadf232332dx",*p1,*p2;
int a=0,b=0;
p1=st1;
while (*p1!='\0') {
if(*p1>='0' && *p1<='9') a++;
if(*p1>='e' && *p1<='k') b++;
p1++;
}
printf("a=%d,b=%d",a,b);
}
3、输入一个字符串,将其中的每一个连续的数字序列看作一个整数,将这些整数检索出来后依次放入一个long int 型数组中。
#include "stdio.h"
main()
{
static char st1[30]="332ds435dfsf322dd",*p1;
int state=0,a,b=0;
p1=st1;
while (*p1!='\0') {
if(*p1>='0' && *p1<='9' ) {
a=*p1-'0';
b=b*10+a;
state=1;
}
else if (state == 1) {
printf("%d\n",b);
b=0;
state=0;
} // else if
p1++;
} //while
if (state == 1) printf("%d\n",b);
}
你只需要定义一个long int 型数组,然后把输出语句改为赋值语句就可.
4、输入8个整数,使用指针以折半插入法对其进行排序(从小到大)。
本题你可以参照书上第十一章来完成.
5、输入一个2*3的整数矩阵和一个3*2的整数矩阵,请使用指针数组实现这两个矩阵的相乘。
a11 a12 a13 a21 a22 a23 |
b11 b12 × b21 b22 = b31 b32 |
a11*b11+ a12*b21+ a13*b31 a11*b12+ a12*b22+ a13*b32 a21*b11+ a22*b21+ a23*b31 a21*b12+ a22*b22+ a23*b32 |
本题主要是看你对指针的掌握情况,但如果用数组来做又简单又能反应数组的特点,用指针来做会比较繁杂,如果你有时间,可以按题目要求来完成。下面程序是其核心部份。
For(i=1; i<=2; i++)
For (j=1; j<=2; j++)
For(k=1; k<=3; k++)
c[i][j]=a[i][k]*b[k][j]+c[i][j];
6、以字符串的形式一次输入若干个变量标志符存入一个字符数组中,各标志符之间以空格隔开,输出其中非法标志符的个数。
#include "stdio.h"
main()
{
static char st1[30]="1eq_3F32 _ds 435'df DSf322",*p1;
int state=0,a=0,x=1,i;
p1=st1;
while (*p1!='\0') {
if( (*p1>='a' && *p1<='z') || (*p1>='A' && *p1<='Z') || (*p1=='_') ) {
while (*p1!=' ' && *p1!='\0') {
if( (*p1>='a' && *p1<='z') || (*p1>='0' && *p1<='9') || (*p1>='A' && *p1<='Z') || (*p1=='_') )
state=1;
else
x=0;
p1++;
} //while (*p1!=' ')
if (state==1 && x==1) a=a+1;
x=1;
}
else while (*p1!=' ' && *p1!='\0') p1++;
p1++;
}
printf("%d",a);
}
第七节结构体与共用体
思考与练习:
1、使用两个结构体变量,分别存入用户输入的两个日期(包括年、月、日),然后计算两日期之间相隔多少天。
#include "stdio.h"
main()
{
struct rain {
long int y, m ,d ;
}a,b,c;
long int ad,bd;
a.y=2006;a.m=2; a.d=6;
b.y=2006;b.m=6; b.d=8;
if(b.y*400+b.m*32+b.d < a.y*400+a.m*32+a.d) {
c=a;
a=b;
b=c;
}
switch (a.m) {
case 1 : ad=a.d; break;
case 2 : ad=a.d+31; break;
case 3 : ad=a.d+59; break;
case 4 : ad=a.d+90; break;
case 5: ad=a.d+120; break;
case 6: ad=a.d+151; break;
case 7: ad=a.d+181; break;
case 8: ad=a.d+212; break;
case 9: ad=a.d+243; break;
case 10:ad=a.d+273; break;
case 11:ad=a.d+304; break;
case 12:ad=a.d+334; break;
}
if( ((a.y%4==0) && (a.y%100!=0)) || (a.y%400==0) ) {
if (a.m>2) ad=ad+1;}
switch (b.m) {
case 1 : bd=b.d; break;
case 2 : bd=b.d+31; break;
case 3 : bd=b.d+59; break;
case 4 : bd=b.d+90; break;
case 5: bd=b.d+120; break;
case 6: bd=b.d+151; break;
case 7: bd=b.d+181; break;
case 8: bd=b.d+212; break;
case 9: bd=b.d+243; break;
case 10:bd=b.d+273; break;
case 11:bd=b.d+304; break;
case 12:bd=b.d+334; break;
}
if( ((b.y%4==0) && (b.y%100!=0)) || (b.y%400==0) ) {
if (b.m>2) bd=bd+1;}
bd=bd-ad;
ad=0;
while (a.y<b.y) {
if( ((b.y%4==0) && (b.y%100!=0)) || (b.y%400==0) ) bd=bd+366;
else bd=bd+365;
a.y=a.y+1;
}
printf("%ld",bd);
}
2、用户输入12个0~100之间的整数,统计出小于60、60~79、80~100三个范围的整数各有多少个,将统计的结果存放在一个结构体变量中,最后将此结构体变量传递给一个函数,此函数负责打印出结果。
3、用户输入两个字符串,分别统计出字符串的长度、空格个数、字母的个数和数字的个数并放入两个结构体变量中,然后调用一个函数,比较这两个结构体变量,判断4个统计项目中哪些相同哪些不同,输出判断的结果。
#include "stdio.h"
main()
{
static struct rain {
long int l, space ,num,abc ;
}a1,a2;
char a[30]="dsa 23 43 22 d",b[30]="2343 dsad 24 22";
int i, m, n, e, k;
a1.space=a1.num=a1.abc=0; a2=a1;
a1.l=strlen(a);
for(i=0; i<=a1.l ; i++) {
if (a[i]>='0' && a[i] <='9') a1.num=a1.num+1;
if ((a[i]>='a' && a[i] <='z') ||(a[i]>='A' && a[i] <='Z') )a1.abc=a1.abc+1;
if (a[i]==' ') a1.space=a1.space+1;
}
a2.l=strlen(b);
for(i=0; i<=a2.l ; i++) {
if (b[i]>='0' && b[i] <='9') a2.num=a2.num+1;
if ((b[i]>='a' && b[i] <='z') ||(b[i]>='A' && b[i] <='Z') )a2.abc=a2.abc+1;
if (b[i]==' ') a2.space=a2.space+1;
}
if (a1.l==a2.l)
printf("the length is same\n");
else
printf("the number of the length is NO\n");
if (a1.num==a2.num)
printf("the number of the number is same\n");
else
printf("the number of the number is NO\n");
if (a1.abc==a2.abc)
printf("the number of the letter is same\n");
else
printf("the number of the letter is NO\n");
if (a1.space==a2.space)
printf("the number of the space is same\n");
else
printf("the number of the space is NO\n");
}
第八节 文件
思考与练习:
1、将三个学生的数据(学号、姓名、年龄)从键盘输入,存入到一个新建的文本文件中去。
#include "stdio.h"
#include "stdlib.h"
main()
{
char name[20];
long int ID;
int age;
int i;
FILE *fp;
if((fp=fopen("test.txt","w"))==NULL) {
puts("\n This file can not be opened");
exit (0);
}
for (i=1;i<=3;i++){
scanf("%ld%s%d",&ID,name,&age);
fprintf(fp, "%ld %s%4d\n",ID,name,age);
}
fclose(fp);
}
2、有一文本文件,以‘\n’字符作为分行的标志,请编定程序指出其中第几行是最长的行,此行有多少个字符。
#include "stdio.h"
#include "stdlib.h"
main()
{
int line=0,number=0,temp=0,x=0;
char ch;
FILE *fp;
if((fp=fopen("test.txt","r"))==NULL) {
puts("\n This file can not be opened");
exit (0);
}
ch=fgetc(fp);
while(ch != EOF) {
line=line+1;
while (ch!='\n' && ch != EOF) {
temp=temp+1;
ch=fgetc(fp);
}
if (temp>number) { number=temp; x=line;}
if (ch!=EOF) ch=fgetc(fp);
temp=0;
}
printf("number=%d line=%d\n",number,x);
fclose(fp);
}
3、10个实型数据以二进制的形式存放在一个文件中,将它们逆序读出,取三位小数后以字符形式存入一个新的文件中。
例1.txt 1111.001 1111.011 1111.111 1111.1 |
例2.txt 100 111 1 110 111 1 111 111 1 111 11 |
#include "stdio.h"
#include "stdlib.h"
main()
{
char aa[40],a;
int line,i,number=0;
FILE *fp_1,*fp_2;
if((fp_1=fopen("1.txt","r"))==NULL) {
puts("\n This file can not be opened");
exit (0);
}
if((fp_2=fopen("2.txt","w"))==NULL) {
puts("\n This file can not be opened");
exit (0);
}
fscanf(fp_1,"%s",aa);
while(a!=EOF) {
line=strlen(aa);
for(i=line-1;i>=0;i--) {
if (aa[i]!='.') {fputc(aa[i],fp_2);number=number+1;}
if (number==3) {fputc(' ',fp_2);number=0;}
}
fputc('\n',fp_2);
number=0;
a=fgetc(fp_1);
fscanf(fp_1,"%s",aa);
}
fclose(fp_1);
fclose(fp_2);
}
注:本其实是一道变化很多的题,比如把逆序读出二进制数每八位转换成一个ASCII码来存储高位不足补零等。
4、将用输入的5个学生的学号和成绩以结构体的形式存入新建的文件,然后读取该文件,将成绩在80分段(大于等于80而小于90)的学生的学号和相应的成绩打印出来。
本题其实是第1题与前一节第2题的综合,是一道基本的语法练习题,请读者自行完成。
第四章 数据结构
第一节 线性表结构
1、 | [题目] | 一个向量第一个元素的存储地址是100,每个元素的长度为2,则第5个元素的地址是多少? |
[解答] | 第一个元素占地址100和101,依此类推第5个占地址108和109,所以答案为108 | |
2、 | [题目] | 已知一维数组a[4]的地址为1000,二维数组b[3,2]的地址为2000,数组只存储的数据类型为integer,问数组a[15]和b[5,3](按行方式储存, 二维数组为5行5列)的地址各为多少? |
[解答] | 一个integer数据(-32768~32767)需要16位二进制存储,所以a[15]的地址为1022。
| |
3、 | [题目] | 采用顺序查找方法查找长度为n的线性表时,每个元素的平均查找长度是多少? |
[解答] | 查找第一个元素的查找长度为1,第二个的长度为2…第n个的长度为n,所以总共长度为 ,平均长度为 。 | |
4、 | [题目] | 设有一个栈,元素进栈的次序为A,B,C,D,E。试问能否得到出栈序列: 1)C,E,A,B,D 2)C,B,A,D,E 3)D,C,A,B,E 4)A,C,B,E,D 5)A,B,C,D,E |
[解答] | ‘<’代表进栈,‘>’代表出栈,下同 1)不能 2)可以按<,<,<,>,>,>,<,<,>,>的顺序 3)不能 4)可以按<,>,<,<,>,>,<,<,>,>的顺序 5)可以按<,>,<,>,<,>,<,>,<,>的顺序 | |
5、 | [题目] | 设栈S和队列Q的初始状态为空,元素e1,e2,e3,e4,e5和e6依次通过栈S,一个元素出栈后即进入队列Q,若6个元素出队的序列是e2,e4,e3,e6,e5,e1,则栈S的容量至少应该是多少? |
[解答] | 根据栈先进后出以及队列先进先出的特征,可知进出栈的顺序为<,<,>,<,<,>,>,<,<,>,>,>。某一时刻,栈中最多有3个元素,所以栈S的容量至少为3 | |
6、 | [题目] | 设将整数1.2.3.4依次进栈,但只要出栈时栈非空,则可将出栈操作按任何次序加入其中,请回答下述问题: (1)若入、出栈次序为Push(1),Pop(),Push(2),Push(3),Pop(),Pop(),Push(4), Pop(),则出栈的数字序列为何(这里Push(i)表示i进栈,Pop()表示出栈)? (2)能否得到出栈序列1423和1432?并说明为什么不能得到或者如何得到。 (3)请分析1,2,3,4的24种排列中,哪些序列是可以通过相应的入出栈操作得到的。 |
[解答] | (1)1,3,2,4 (2)按照<,>,<,<,<,>的顺序可以得到1,4的输出,此时栈中还有2,3。接着只能输出3,2而不能输出2,3。 (3)经过观察可以得到如下结论:1.一旦栈中输出了元素n,那么1…n-1这些元素要么已经出栈,要么还按从小到大的顺序存在栈中;2.输出元素n后,比n小的未输出元素只能按从大到小的先后顺序输出。不难得出,可以输出以下序列: (4,3,2,1)(3,4,2,1)(3,2,4,1)(2,4,3,1)(2,3,4,1)(2,1,4,3)(1,4,3,2)(1,3,4,2) (1,3,2,4)(1,2,4,3)(1,2,3,4) | |
7、 | [题目] | 回文是指正读反读均相同的字符序列,如“abba”和“abdba”均是回文,但“good”不是回文。试写一个算法判定给定的字符向量是否为回文。 |
[解答] | 根据提示不难完成。 源程序:huiwen.pas, huiwen.exe 输入:huiwen.in 输出:huiwen.out 输入为一行待判断字符,若有数字仍按字符形式判断 | |
8、 | [题目] | 线性表用顺序储存,设计一个算法,用尽可能少的辅助存储空间将顺序表中前m个元素和后n个元素进行整体互换。即将线性表(a1,a2,…am,b1,2b,…bn)改变为:(b1,b2,…bn,a1,a2,…am)。 |
[解答] | 以字符串形式进行操作,直接交换。 源程序:huhuan.pas, huhuan.exe 输入:huhuan.in 输出:huhuan.out 输入为一行待判断字符,若有数字仍按字符形式判断 | |
9、 | [题目] | 写出下列中缀表达式的后缀形式: (1)A*B*C (2)-A+B-C+D (3) A* B + C (4)(A+B)*D+E/(F+A*D)+C |
[解答] | (1)A B * C * (2)A – B + C – D + (3) A B * C + (4)A B + D * E A D * F + / + |
第二节树和二叉树
1、 | [题目] | 对于三个结点A、B、C,有多少种不同的树? | |||||||||||||||||||||||||||||||||||||||||||||||||||
[解答] | 如图,直线型的树由于根结点中间结点和末结点的不同可以有3*2=6种不同的树 | 如图,满二叉树型由于根结点不同可以有3种不同的树(此处没有考虑左右孩子结点的不同)。 | |||||||||||||||||||||||||||||||||||||||||||||||||||
2、 | [题目] | 如果一棵树有n1个度为1的结点,由n2个度为2的结点,…,nm个度为m的结点,则该树共有多少个终端结点? | |||||||||||||||||||||||||||||||||||||||||||||||||||
[解答] | 经过观察,可得该树共有 | ||||||||||||||||||||||||||||||||||||||||||||||||||||
3、 | [题目] | 如下图所示,指出该树的叶结点、分支结点,各个结点的层次和树的高度,从结点A到结点H的路径长度是多少,有多少条长度为2的路径。 | |||||||||||||||||||||||||||||||||||||||||||||||||||
[解答] |
树的高度4,从A到H的路径长度是3,有8条长度为2的路径 | ||||||||||||||||||||||||||||||||||||||||||||||||||||
4、 | [题目] | 对如下一棵二叉树,其中序遍历的序列是什么? | |||||||||||||||||||||||||||||||||||||||||||||||||||
[解答] | 树的遍历规则:先序 中左右;中序 左中右;后序 左右中; 答案:D G B A C E F | ||||||||||||||||||||||||||||||||||||||||||||||||||||
5、 | [题目] | 已知一棵二叉树的前序遍历结果是ABECDFGHIJ,中序遍历的结果是EBCDAFHIGJ,试画出这棵二叉树。 | |||||||||||||||||||||||||||||||||||||||||||||||||||
[解答] | 由树的遍历特征求得,答案如图 | ||||||||||||||||||||||||||||||||||||||||||||||||||||
6、 | [题目] | 将下图所示的树转化为二叉树。将转化得到的二叉树,按前序、中序、后序进行遍历,写出遍历的结点序列。 | |||||||||||||||||||||||||||||||||||||||||||||||||||
[解答] | 根据树的转换规则(P155)可得如图 前序遍历:G H K I L E B C D F J A 中序遍历:K H L I G E D J F C B A 后序遍历:K L I H G E J F D C A B | ||||||||||||||||||||||||||||||||||||||||||||||||||||
7、 | [题目] | 写出一个将二叉树左右孩子交换的算法程序。 | |||||||||||||||||||||||||||||||||||||||||||||||||||
[解答] | 解法一 用顺序结构(P153,以层顺序)读取和储存二叉树,‘0’代表空结点,交换后以顺序结构输出。输入输出文件均为一行带空格的大写字母序列。 源程序:Program changetree(input,output); var ff:text; tree:array[1..100] of char; c:char; k,m,n:integer; Procedure init; begin assign(ff,'changetree.in'); reset(ff); fillchar(tree,sizeof(tree),'0'); n:=0; k:=0; while not(eoln(ff)) do begin inc(k); read(ff,tree[k],c); if tree[k]<>'0' then inc(n); end; close(ff); end; Procedure change(i:integer); var j:integer; l:char; begin j:=i*2; for k:=i to (i*3 div 2 - 1) do begin dec(j); l:=tree[k]; tree[k]:=tree[j]; tree[j]:=l; end; if i*2<=n then change(i*2); end; Procedure main; begin change(2); assign(ff,'changetree.out'); rewrite(ff); m:=0; k:=1; repeat begin write(ff,tree[k],' '); if tree[k]<>'0' then inc(m); inc(k); end until m>=n; close(ff); end; begin init; main; end. 解法二 用指针形式储存二叉树后,利用循环使每个分支结点的左右指针交换。
| ||||||||||||||||||||||||||||||||||||||||||||||||||||
8、 | [题目] | 什么是哈夫曼树?用给出的一组权值{4,2,3,5,7,8},建立一棵哈夫曼树。 | |||||||||||||||||||||||||||||||||||||||||||||||||||
[解答] | 哈夫曼树的定义:带权路径长度最短的树 哈夫曼树的建立规则: 现将数组从小到大排序,再将它们依次填充到哈夫曼树的叶结点上,而且越小的数深度约大。 源程序:Program build(input,output); var ff:text; i,j,m,n:integer; tree:array[1..100,1..2] of integer; Procedure init; begin assign(ff,'build.in'); reset(ff); n:=0; while not(eoln(ff)) do begin inc(n); read(ff,tree[n,1]); end; close(ff); for i:=1 to n-1 do for j:=i+1 to n do if tree[i,1]>tree[j,1] then begin m:=tree[i,1]; tree[i,1]:=tree[j,1]; tree[j,1]:=m; end; end; Procedure main; begin for i:=2 to n do begin tree[i-1,2]:=tree[i,1]; inc(tree[i,1],tree[i-1,1]); end; assign(ff,'build.out'); rewrite(ff); write(ff,tree[n,1],' '); for i:=n-1 downto 1 do write(ff,tree[i,2],' ',tree[i,1],' '); close(ff); end; begin init; main; end. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
第三节图
1、 | [题目] | 已知图的邻接矩阵表示,请画出它所对应的图。 | |||||||||
[解答] | |||||||||||
2、 | [题目] | 对如下图所示的有向图,请写出该图的邻接矩阵。 | |||||||||
[解答] |
| 0 代表没有距离 1 代表有通路 代表没有通路 | |||||||||
3、 | [题目] | 写出如下带权无向图的邻接矩阵,并求出其最小生成树。 | |||||||||
[解答] | |||||||||||
4、 | [题目] | 对于一个具有n个顶点和e条边的无向图,若采用邻接表表示,则表头向量的大小为多少;所有邻接表中的结点总数是多少? | |||||||||
[解答] | 由于每一个连接表结点数是其边数的二倍减一,所以所有邻接表中的结点总数是2e-n。 | ||||||||||
5、 | [题目] | 画出1个顶点2个顶点3个顶点4个顶点和5个顶点的无向完全图。试证明在n个顶点的无向完全图中,边的条数为n(n-1)/2。 | |||||||||
[解答] | n个顶点两两连接,任意选出两个顶点形成一条不同的边。所以边的总数是 | ||||||||||
6、 | [题目] | 对如下所示的图中,分别写出按深度优先搜索法和广度优先搜索法,从A点出发遍历的结果序列。 | |||||||||
[解答] | 深度优先:A B C D F E G H 广度优先:A B E F H C D G | ||||||||||
7、 | [题目] | 设无向图G如图所示,试给出 (1)图的邻接矩阵 (2)该图的邻接表 (3)该图的多重邻接表 (4)从V0出发的“深度优先”遍历序列 (5)从V0出发的“广度优先”遍历序列 | (1) | ||||||||
[解答] | (2)(3)略 (4)V0 V1 V3 V2 V4 V6 V5 (5)V0 V1 V2 V3 V4 V5 V6 | ||||||||||
第四节查找
1、 | [题目] | 对线性表进行二分查找时,要求线性表必须是: A.以顺序方式存储 B.以链接方式存储 C.以顺序方式存储,且结点按关键字有序排序 D.以链接方式存储,且结点按关键字有序排序 | ||||||||||||||||||||||||||||||||
[解答] | 答案:C | |||||||||||||||||||||||||||||||||
2、 | [题目] | 有一个有序表为{1,3,9,12,32,41,45,62,75,82,88,95,100},当二分查找值为82的结点时,多少次比较后查找成功? | ||||||||||||||||||||||||||||||||
[解答] | 根据二分查找的过程,比较的次序是:45 82 所以经过2次比较 | |||||||||||||||||||||||||||||||||
3、 | [题目] | 设哈希表长m=14,哈希函数H(key)=key%11,表中已有4个结点。 addr(15)=4 addr(38)=5 addr(61)=6 addr(84)=7 其余地址为空 如果用二次探测再散列处理冲突,关键字为49的结点的地址是多少? | ||||||||||||||||||||||||||||||||
[解答] | 哈希表如下,见P169开放定址法。 答案:8
| |||||||||||||||||||||||||||||||||
4、 | [题目] | 设哈希长度为10,哈希函数为H(K)=K mod 7,关键字集合为{15,10,12,20,25,35,31,15},请给出开放地址方法和拉链方法所构造得到的哈希表结构。 | ||||||||||||||||||||||||||||||||
[解答] |
| |||||||||||||||||||||||||||||||||
5、 | [题目] | 试设计递归二分(折半)查找算法。 | ||||||||||||||||||||||||||||||||
[解答] | 见P169,用开放地址法处理冲突。通过mod函数的运用,将longint型数据转换成相应的初始地址,再用一个过程来解决冲突。算法如下: Var n:longint; Hash:array[1..999] of longint;
|
递归二分(折半)查找算法。
int Bserach (elemtype a[],elemtype x,int low,int high)
{
int mid;
if (low>high) return -1;
mid=(low+high)/2;
if(x==a[mid]) return mid;
if (x<a[mid])
return (Bserach (a,x,low,mid-1));
else
return (Bserach (a,x,mid+1,high));
}
第五节、排序
1、什么是内排序? 什么是外排序? 什么排序方法是稳定的? 什么排序方法是不稳定的?
在排序过程中,所有需要排序的数都在内存,并在内存中调整它们的存储顺序,称为内排序;
在排序过程中,只有部分数被调入内存,并借助内存调整数在外存中的存放顺序排序方法称为外排序。
若对任意的数据元素序列,使用某个排序方法,对它按关键字进行排序,若相同关键字元素间的位置关系,排序前与排序后保持一致,称此排序方法是稳定的;反之,则称为不稳定的。
直接插入排序、冒泡排序、归并排序是稳定的排序方法;而简单选择排序、希尔排序、快速排序、堆排序是不稳定的排序方法。
2、希尔排序、简单选择排序、快速排序和堆排序是不稳定的排序方法, 试举例说明。
简单地说就是所有相等的数经过某种排序方法后,仍能保持它们在排序之前的相对次序,我们就说这种排序方法是稳定的。反之,就是非稳定的。 比如:一组数排序前是a1,a2,a3,a4,a5,其中a2=a4,经过某种排序后为a1,a2,a4,a3,a5,则我们说这种排序是稳定的,因为a2排序前在a4的前面,排序后它还是在a4的前面。假如变成a1,a4,a2,a3,a5就不是稳定的了。
3、已知序列{17,18,60,40,7,35,73,65,85},请给出采用冒泡排序法对该序列作升序排序时的每一趟的结果。
参照例4.18进行排序
4、已知序列{10,18,4,3,6,12,1,9,18,8},请给出采用希尔排序法对该序列作升序排序时的每一趟的结果(增量为5,2,1)。
参照例4.17进行排序
5、已知序列{10,18,4,3,6,12,1,9,18,8},请给出采用归并排序法对该序列作升序排序时的每一趟的结果。
参照例4.22进行排序
6、设待排序的排序码序列为{17,18,60,40,7,35,73,65,85}, 试分别写出使用以下排序方法每趟排序后的结果。并说明做了多少次排序码比较。
(1) 直接插入排序 (2)折半半插入排序 (3) 快速排序
(4) 直接选择排序 (5)堆排序
参照教材中示例
7、判定下列序列是否为堆。如果不是,则请把它们调整为堆。
(1){100,86,48,73,35,39,42,57,66,21}
(2){12,70,33,65,24,56,48,92,86,33}
略
8、对于冒泡排序算法,什么样的输入数据使得算法的执行时间最长?
若初始文件是反序的,需要进行n-1趟排序。每趟排序要进行n-i次关键字的比较(1≤i≤n-1),且每次比较都必须移动记录三次来达到交换记录位置。在这种情况下,比较和移动次数均达到最大值。
9、试写出折半排序的算法。
基本思想 设在顺序表中有一个对象序列 V[0], V[1], …, V[n-1]。其中, V[0], V[1], …, V[i-1] 是已经排好序的对象。在插入V[i] 时, 利用折半搜索法寻找V[i] 的插入位置。
折半插入排序的算法:
typedef int SortData;
void BinInsSort ( SortData V[ ], int n ) {
SortData temp; int Left, Right;
for ( int i = 1; i < n; i++) {
Left = 0; Right = i-1; temp = V[i];
while ( Left <= Right ) {
int mid = ( Left + Right )/2;
if ( temp < V[mid] ) Right = mid - 1;
else Left = mid + 1;
}
for ( int k = i-1; k >= Left; k-- ) V[k+1] = V[k];//
记录后移
V[Left] = temp; //插入
}
}
第五章 常用算法
第一节 算法评估
思考与练习P179:
1、O(log2n),O(nlog2n)
2、O(nlog2n), O(n2)
3、O(n2), O(n2), O(n2), O(nlog2n), O(nlog2n), O(nlog2n)
第二节 枚举法
思考与练习P182:
1、算法分析:本题可以枚举123到329之间的所有三位i,如果i、2*i、3*i这三个数中包含的9个数字刚好是1到9则输出i、2*i、3*i。为了记录1到9这9个数字在i、2*i、3*i中是否出现过,程序中用一个一维数组a。a[d]=1表示数字d在i、2*i、3*i这三个数中出现过,a[d]=0则表示未出现。
2、提示:如果采取逐条路线枚举的方法去试探,那么由于本题中的展室数目并不大,所以是可以行得通的。
第三节 递推算法
思考与练习P185:
1、楼梯有N级台阶,上楼可以一步上一阶,也可以一步上二阶。试编程序计算共有多少种不同走法?
算法分析:到第N级台阶时问题在第N-1级楼梯处上一级台阶或在第N-2级台阶楼梯处上两级台阶,由此可得出递推表达式a(n)=a(n-1)+a(n-2)。
程序代码:
#include <stdio.h>
main()
{
long f3,f2,f1;
int i,n;
printf("please input the steps:");
scanf("%d",&n);
f1=1;
f2=2;
for (i=3;i<=n;i++)
{
f3=f1+f2;
f1=f2;
f2=f3;
}
printf("%ld\n",f3);
}
2、宰相的麦子:相传古印度宰相达依尔,是国际象棋的发明者。有一次,国王因为他的贡献要奖励他,问他想要什么。达依尔说:“只要在国际象棋棋盘上(共64格)摆上这么些麦子就行了:第一格一粒,第二格两粒,……,后面一格的麦子总是前一格麦子数的两倍,摆满整个棋盘,我就感恩不尽了。”国王一想,这还不容易,刚想答应,如果你这时在国王旁边站着,你会不会劝国王别答应,为什么?请输出棋盘上摆放的麦子总数。假如一万颗麦子有一斤重,请问共有多少吨麦子?
算法分析:国际象棋棋盘每一格子上的麦子粒数都是上一格麦子粒数的两倍,递推式为a=2*a,总的麦子数为264-1粒,简单算出其结果为20位的数值,可用高精度加法来递推算出结果。
3、阶乘计算(用递推):写一个程序,对给定的n(n≤100),计算并输出n的阶乘n!的全部有效数字。
算法分析:
用递推法求解阶乘函数的思路是:先求fac(1),再求fac(2),…,直到求出fac(n),其程序代码可参考第四节高精度计算中思考练习的第2题。
4、一辆重型卡车欲穿过1000公里的沙漠,卡车耗油为1升/公里,卡车总载油能力为500公升,显然卡车装一次油是过不了沙漠的,因此司机必须设法在沿途建立几个储油点,使卡车顺利穿越沙漠,试问司机如何建立这些储油点?每一储油点应存多少油才能使卡车以消耗最少汽油代价通过沙漠?
算法分析:可用倒推法解决该问题,最后一个贮油点应离终点500公里,贮油量为500公升,这样卡车可以顺利到达终点,建立离终点的第二个贮油点时要为下一个贮油点往返运送500公升油,因此第二个贮油点应贮存1000公升油,有500公升油消耗在送油的路途中,第二个贮油点离终点的距离为S=500+500/3,如此得出离终点第N个贮油点的递推式S(N)=S(N-1)+500/(2N-1),如此倒推下去一直到起点。
#include <stdio.h>
main()
{
int k; /*贮油点位置序号*/
float d,d1; /*d:累计终点至当前贮油点的距离,d1:i=n至始点的距离*/
float oil[10],dis[10];
int i;
printf("NO. distance(k.m.)\toil(l.)\n");
k=1;
d=500; /*从i=1处开始向始点倒推*/
dis[1]=500;
oil[1]=500;
do{
k=k+1;
d=d+500/(2*k-1);
dis[k]=d;
oil[k]=oil[k-1]+500;
}while(!(d>=1000));
dis[k]=1000; /*置始点至终点的距离值*/
d1=1000-dis[k-1]; /*求i=n处至始点的距离*/
oil[k]=d1*(2*k+1)+oil[k-1]; /*求始点藏油量*/
for(i=0;i printf("%d\t%f\t%f\t\n",i,1000-dis[k-i],oil[k-i]);
}
5、有一堆桃子和8只猴子,第1只猴子把桃子平均分成3堆后,还剩1个,它吃了剩下的一个,并拿走一堆。第2、3只猴子和第1只一样。第4只猴子把桃子平均分成5堆后,还剩1个,它吃了剩下的一个,并拿走一堆。5、6、7、8和第4只一样。问:至少还剩多少个桃子?原来至少有多少个桃子?
算法分析:可采用枚举尝试与倒推的方法来解决该问题。从最后一只猴子起倒推能够分成5堆且还剩一个的桃子数,每次以5个递增枚举满足条件的桃子数直到第一只猴子能够分成3堆还剩一个桃子为止。
第四节 高精度计算
思考与练习P189:
1、用高精度算法求菲波那契数列的前2000项。
算法分析:该题采用递推算法,因为要求到前2000项,具体计算时要用高精度加法实现。
#include <stdio.h>
main()
{
int a1[500],a2[500],a3[500];
int jw,he,i,j,k;
for(i=1;i<=500;i++)
{a1[i]=a2[i]=a3[i]=0;}
a2[1]=1;jw=0;
printf("%d\n",0);
printf("%d\n",1);
for(i=3;i<=2000;i++)
{
for(j=1;j<=500;j++)
{he=a1[j]+a2[j]+jw;
jw=he/10;
a3[j]=he%10;
}
j=500;
while(a3[j]==0) j--;
for (k=j;k>=1;k--)
printf("%d",a3[k]);
printf("\n");
for(j=1;j<=500;j++)
{a1[j]=a2[j];a2[j]=a3[j];}
}
}
2、求1!+2!+3!+……+n!之和。(n≤500)
算法分析:该题可以用高精度乘法来实现,也可以用高精度加法来实现,下面的程序代码采用高精度加法来实现。
#include <stdio.h>
main()
{
int s[1200],a[1200],b[1200];
int jw,he,i,j,k,n;
scanf("%d", &n);
for(i=1;i<=1200;i++)
{s[i]=a[i]=b[i]=0;}
a[1]=1;
for(i=1;i<=n;i++)
{
for(j=1;j<=1200;j++) b[j]=0;
for(j=1;j<=i;j++)
{jw=0;
for(k=1;k<=1200;k++)
{he=a[k]+b[k]+jw;
jw=he/10;
b[k]=he%10;
}
}
for(k=1;k<=1200;k++) a[k]=b[k];
jw=0;
for(k=1;k<=1200;k++)
{he=s[k]+b[k]+jw;
jw=he/10;
s[k]=he%10;
}
}
j=1200;
while(s[j]==0) j--;
for (k=j;k>=1;k--)
printf("%d",s[k]);
printf("\n");
}
3、求数列1+1/2+1/3+…+1/n之和。(n≤500,和的值精确到小数点后500位)
算法分析:本题采用高精度加法和两个单精度数的除法(结果采用高精度存储)来实现。
#include <stdio.h>
main()
{
int a[601],b[601];
int n,i,j,g,s;
scanf("%d", &n);
for(i=0;i<=600;i++)
{a[i]=b[i]=0;}
for(i=1;i<=n;i++)
{
a[0]=1/i;
g=1%i;
for(j=1;j<=600;j++)
{s=g*10;
g=s%i;
a[j]=s/i;
}
s=g=0;
for (j=600;j>=0;j--)
{s=a[j]+b[j]+g;
g=s/10;
b[j]=s%10;
}
}
printf("%d.",b[0]);
for(j=1;j<=500;j++)
{printf("%d",b[j]);
}
printf("\n");
}
4、试根据以下公式来设计程序求圆周率n,结果精确到小数点后指定的m位。
算法分析:这道题目涉及到高精度加法,高精度乘法与两个高精度数的除法运算,大家可以借助前面所列出的知识点来解决,代码较长,略去。
第五节模拟法
思考与练习P190:
1、机场检票进站口进一个人的时间至少为12秒钟,至多为30秒钟,某个航班有n个乘客,试求这n个人进站所需要的进站时间,如要求排队的n个乘客在30分钟内全部进站,则一般需要安排多少个检票口。(要求模拟m次,输出m次各自所需时间和需要安排的检票口数)
算法分析:用计算机可以模拟很多事件处理的过程。该题用随机函数产生n个12-30之间的随机数12+random(19),累加起来即为所需要的进站时间。再根据时间来安排检票口的数量即可。
2、玩扑克牌是人们最为喜爱的文娱活动之一。玩扑克牌有许多种不同的玩法,常见的有桥牌、升级等。请设计程序模拟升级时扑克牌的发牌过程,把含大小王在内的54张牌随机分发给4家,每家12张,底牌留6张。
算法提示:可以简单模拟产生1~54之间的随机数,依次放到集合里面并用一个对应的数组来存储,如遇重复产生的数则跳过直到54个数全部产生为止,然后再根据要求分配这些数即可。
第六节 分治算法
思考与练习P194
1、
算法描述:
…
e0<-1e-38
函数f
{
f<-exp(x*ln(2))+ exp(x*ln(3))- exp(x*ln(4))
}
{
a<-1
b<-2
repeat
x<-(a+b)/2
fa<-f(a)
fx<-f(x)
if fa*fx<0 then b<-x /*在A、X内有解*/
else a<-x
until (abs(b-a)<e0)
}
…
2、
分析:我们可以先把比赛时间表看作一个2m*2m的矩阵,设为A,由于我们很难直接给出结果。如果把它的规模减半,我们会发现所得到的新矩阵A1与原矩阵A存在着一种关系:
A= | A1 | B |
C | D |
当规模减半到只有两个选手时,矩阵为:
1 | 2 |
2 | 1 |
由于各种情况性质一致,只是规模不同,参照这一矩阵,可知A1与A的关系:
A1 | B |
B | A1 |
当有4个选手参加比赛时,由于A、A1的关系可以得出下列矩阵:
|
| ||||||||
|
|
由于有这一规律,于是可以得到一种比赛顺序。也就是将(2k*2k)矩阵分成了四块,每块是(2k-1*2k-1)的矩阵,它是对称的(如A1与A1,B与B)。然后分别把A1、B分成四块,一直划分到(2*2)矩阵为止,这时就可定出每个元素的值,再按对称关系构成比赛表。问题就被简化成了设计一个对称矩阵。
第七节 回溯算法
思考与练习P198:
1、有n个0和n个1排成一个数列,要求从该数列的第一个位置到数列的任意一个位置时数字0的个数总是大于或等于数字1的个数,求所有可能的排列。
算法分析:该题与书中排除买票问题类似,可以用数组a来存储数列的排队情况。先将a数组的各元素值赋为-1。从a[1]的值加1开始试探、搜索。试探时b[0]用于存储0的个数,b[1]用于存储1的个数。某个元素a[i]的值是否符合要求需满足以下条件:
①(a[i]=0)&&(b[0]<n) /*如果第i个元素值为0,而队列中0的个数不到n个时表示可以放入*/
②(a[i]=1)&&(b[1]<b[0]) /*如果第i个元素值为1,而队列中1的个数小于0的个数时表示可以放入*/
如果满足条件,则将条件判断变量pd的值设为真。Pd值为真后将该元素加入,然后判断i的值是否为总的人数,如等于则输出这组排列,如否则将i值加1后继续判断下一个人的情况。
如果不满足条件,则a[i]的值加1后继续试探,如果0,1两个值均不符合要求,则回溯,返回上一个元素a[i-1],将其值加1后继续尝试。
样例程序如下:
#include <stdio.h>
main()
{
int i,j,n,pd,a[100],b[2];
scanf("%d",&n);
n=n/2;
i=1;
for (i=1;i<=2*n;i++) a[i]=-1;
/*先将a数组各元素的值全部置为-1,用-1来表示未放数的初始状态*/
i=1;b[0]=0;b[1]=0;
/*将计零和计1个数的计数器b[0]、b[1]清零,i表示数组a的当前元素位置*/
do
{
do
{
pd=0; /*能否放数的判断变量*/
a[i]++; /*数组a的当前元素值加1,开始试探*/
if (a[i]==0&&b[0]<n) /*元素值为零时能不能放的判定条件*/
{ b[0]++;pd=1; }
if (a[i]==1&&b[1]<b[0]) /*元素值为1时能不能放的判定条件*/
{ b[1]++;pd=1;}
}
while ((pd!=1)&&(a[i]!=2));
if (pd==1) /*pd值为1时表示能放*/
if (i==2*n) /*如果已经放到最后一个数了,则输出这一组排列*/
{ for (j=1;j<=2*n;j++)
printf("%2d",a[j]);
printf("\n");
}
else i++; /*继续放下一个位置*/
else /*a[i]为2时表示已经试探了0,1后均不成立,此时回溯*/
{ b[1]--; /*回溯时假设a[i]的值从1变为2时试探的1已经被计数*/
a[i]=-1; /*将当前元素还原为未放值时的初始状态*/
i--; /*回到上一个位置*/
if (a[i]==0) /*上一个位置值为0时的处理*/
{ b[0]--;
if (b[1]==b[0]) b[1]++;
}
}
}
while (i!=0);
}
注意回溯时上一位会进行加1计算,因此不管上一位是0还是1,对应的计数器都应减少1,即有i--,b[a[i]]--。如果上一位的值为0,在加1时会有这样一种情况,这个1在b[1]=b[0]时不会被计数而直接又加1后向前回溯,回溯时的dec(b[1])语句会导致b[1]被多减一次。为避免这种情况,在此处加一判断,(a[i]=0)&&(b[1]=b[0])时b[1]++。
2、用L型骨牌去覆盖一个m×n个方格所组成的长方形,请问怎样覆盖才能使长方形上留下的方格数最少?
算法分析:采用回溯搜索的方法来处理该问题。总是存在一种最优的布局,使得每一块骨牌至少有两条边与其他骨牌的边或者棋盘的边相邻,处理时可先设初始状态时的棋盘为空;再取棋盘的左上角,放第一张骨牌,使骨牌的一个顶点与棋盘的角的顶点重和。第一张骨牌有横放和竖放两种情况,因此得到两个初始状态的子结点;对于当前的状态,已经放置的骨牌的围成一个多边形(其中可能有空隙),该多边形的边和棋盘的边又将棋盘中的空地围成一个多边形,下面只要在这个多边形内部贴着边放置一个骨牌就可以了。
3、有一个由n×n个方格组成的迷宫,入口和出口分别在迷宫的左上角和右上角。用迷宫格子中的0表示此路可通,1表示此路不通。走迷宫时,从某点出发可沿8个方向前进,请找出给出迷宫中从入口到出口的通路。
算法分析:给出从某点出发沿4个方向前进的提示,8个方向与之类似。在二维迷宫里面,从出发点开始,每个点按四邻域算,按照右,上,左,下的顺序搜索下一落脚点,有路则进,无路即退,前点再从下一个方向搜索,即可构成一有序模型。下表为10×10迷宫
{ 1,1,1,1,1,1,1,1,1,1,
0,0,0,1,0,0,0,1,0,1,
1,1,0,1,0,0,0,1,0,1,
1,0,0,0,0,1,1,0,0,1,
1,0,1,1,1,0,0,0,0,1,
1,0,0,0,1,0,0,0,0,0,
1,0,1,0,0,0,1,0,0,1,
1,0,1,1,1,0,1,1,0,1,
1,1,0,0,0,0,0,0,0,1,
1,1,1,1,1,1,1,1,1,1}
从出发点开始,按序查找下一点所选点列构成有序数列,如果4个方向都搜遍都无路走,就回退,并置前点的方向加1,依此类推.......
1 2 3 4 5 6 7 8 9 10
x 1 1 1 2 3 3 3 2 ...
y 0 1 2 2 2 3 4 4 ...
c 1 1 3 3 1 1 2 1 ...
#include<stdio.h>
#include<stdlib.h>
#define n1 10
#define n2 10
typedef struct node
{
int x; //存x坐标
int y; //存Y坐标
int c; //存该点可能的下点所在的方向,1表示向右,2向上,3向左,4向右
}linkstack;
linkstack top[100];
//迷宫矩阵
int maze[n1][n2]={1,1,1,1,1,1,1,1,1,1,
0,0,0,1,0,0,0,1,0,1,
1,1,0,1,0,0,0,1,0,1,
1,0,0,0,0,1,1,0,0,1,
1,0,1,1,1,0,0,0,0,1,
1,0,0,0,1,0,0,0,0,0,
1,0,1,0,0,0,1,0,0,1,
1,0,1,1,1,0,1,1,0,1,
1,1,0,0,0,0,0,0,0,1,
1,1,1,1,1,1,1,1,1,1,};
int i,j,k,m=0;
main()
{
//初始化top[],置所有方向数为左
for(i=0;i<n1*n2;i++)
{
top[i].c=1;
}
printf("the maze is:\n");
//打印原始迷宫矩阵
for(i=0;i<n1;i++)
{
for(j=0;j<n2;j++)
printf(maze[i][j]?"* ":" ");
printf("\n");
}
i=0;top[i].x=1;top[i].y=0;
maze[1][0]=2;
/*回溯算法*/
do
{
if(top[i].c<5) //还可以向前试探
{
if(top[i].x==5 && top[i].y==9) //已找到一个组合
{
//打印路径
printf("The way %d is:\n",m++);
for(j=0;j<=i;j++)
{
printf("(%d,%d)-->",top[j].x,top[j].y);
}
printf("\n");
//打印选出路径的迷宫
for(j=0;j<n1;j++)
{
for(k=0;k<n2;k++)
{
if(maze[j][k]==0) printf(" ");
else if(maze[j][k]==2) printf("O ");
else printf("* ");
}
printf("\n");
}
maze[top[i].x][top[i].y]=0;
top[i].c = 1;
i--;
top[i].c += 1;
continue;
}
switch (top[i].c) //向前试探
{
case 1:
{
if(maze[top[i].x][top[i].y+1]==0)
{
i++;
top[i].x=top[i-1].x;
top[i].y=top[i-1].y+1;
maze[top[i].x][top[i].y]=2;
}
else
{
top[i].c += 1;
}
break;
}
case 2:
{
if(maze[top[i].x-1][top[i].y]==0)
{
i++;
top[i].x=top[i-1].x-1;
top[i].y=top[i-1].y;
maze[top[i].x][top[i].y]=2;
}
else
{
top[i].c += 1;
}
break;
}
case 3:
{
if(maze[top[i].x][top[i].y-1]==0)
{
i++;
top[i].x=top[i-1].x;
top[i].y=top[i-1].y-1;
maze[top[i].x][top[i].y]=2;
}
else
{
top[i].c += 1;
}
break;
}
case 4:
{
if(maze[top[i].x+1][top[i].y]==0)
{
i++;
top[i].x=top[i-1].x+1;
top[i].y=top[i-1].y;
maze[top[i].x][top[i].y]=2;
}
else
{
top[i].c += 1;
}
break;
}
}
}
else //回溯
{
if(i==0) return; //已找完所有解
maze[top[i].x][top[i].y]=0;
top[i].c = 1;
i--;
top[i].c += 1;
}
}while(1);
}
4、设有m×n个方格组成的一个棋盘,在棋盘的左下角为A点,A点处有一中国象棋的马,在棋盘的右上角为B点,约定马走的规则:
⑴马走日字;⑵马只能向右走。请找出马从A点走到B点的所有路径。
算法分析:
(1)马从A点出发,根据上面两条马走的规则,只能到达A1,A2两点,但究竟哪条路径能到达B点呢?无法预知,只能任意选择其中的一个点(如A1)试试看。
(2)当马到达A1点后,下面又有三种可能的选择:B1,B2,B3。由于无法预确定哪条路径是正确的,故任选一点(B1),马到达B1之后,也有2种选择C1,C2,由于无法预确定哪条路径是正确的,故任选一个(C1)。
从Cl出发,也有3个点可供选择:Dl,D2,D3。下面选择D1,当马到达D1之后,没有到达目标,而且已无路可走,所以选择D1是错误的,因此从C1出发另选一条路D2,结果与D1相同。没有到达目标,同时也无路可走。因此,从c1出发最后可选择D3。D3虽然不是目标:但是还可以继续走到El,同样在马到达E1后,没有到达目标,也无路可走,因此是错误的;同时可知从D2出发,置EI是唯一的一条路径,因此到达D3之后就错误了,再回到cl,即从c1出发所有的踟径都不可能到达目标,因此,到达cl就错了,再回溯到Bl,已确定到Cl是错误的,因此改走C2。
(3)从C2出发,可能到达的下一个点为D4,D5,…,由于无法确定哪条路是正确的,因此选择D4,最后到达E2,再到达B。
此时,我们找到一条路径A->A1->B1->C2->D4->E2->B。
上面过程就是回溯算法的简单过程,核心思想有下面二点
① 在无法知道走哪条正确时,只能任选一条路试试看
② 当从某点出发,所有可能到达的点都不能到达目标,说明到达此点是错误的,必须回到此点的上一个点,然后重新选择一个点来进行。
第八节 贪心算法
思考与练习P203:
1、分析: 此题的思路就是把数字小的尽量往高位上移,这样得出来的整数就会越小。
假设有一个n位数x1 x2…xixjxk …xn;其中x1< x2 < … < xi < xj;如果xk<xj,则删去xj,即得到一个新的数且这个数为 n-1位中为最小的数x1 x2…xi xk …xn 。
若数为x1 < x2 < … < xi < xj < xk < … < xn ,
则删去xn ,即得到一个新的n-1位中最小的数x1 x2…xi xj xk …xn-1。
参考程序实现是:
........
for(i=1;i<iCount;i++)
{
if(a[i]>a[j])
{
j++;
a[j] = a[i];
}
else
{
while((a[i]<a[j])&&(j>=0)&&(iNum > 0))
{
j--;
iNum--;
}
j++;
a[j] = a[i];
}
}
//指针往前移
if(iNum>0)
{
j = j - iNum;
}
//输出数字
for(i=0;i<=j;i++)
{
if((a[i] == '0')&&(bCheck))
{//在第一个数字为0时,不输出。
}
else
{
outfile<<a[i];
bCheck = false;
}
}
2、C++参考程序
#include<iostream>
using namespace std;
const int n=4;
int a[n+1]={0,25,10,5,2};
int c[n+1]={0};
const int sum=63;
int Find(int,int *,int *,int );//贪心算法找最价值零钱;
void Print(int *,int);
c(i)=c(i-a[j])+1;
int s[sum]={0};
void FindCounts(int);
int DFindCounts(int,int);
int main()
{
cout<<"Sum is:"<<sum;
cout<<"\n"<<"Find "<<DFindCounts(sum,1)<<endl;
cout<<"选择对应零钱数量:";Print(c,n);
}
int Find(int n,int *a,int *c,int sum)
{ int k=0,i=1;
while(sum>0)
{ if(i>n) return 0;
if(sum>=a[i])
{
sum-=a[i];
k++;
c[i]++;
}
else
i++;
}
return k;
}
void Print(int *p,int n)
{
for(int i=1;i<=n;++i)
cout<<" "<<p[i];
}
void FindCounts(int m)
{ s[0]=0;
for(int i=1;i<=m;++i)
{
int min=i;
for(int j=1;j<=n;++j)
if(i>=a[j] && s[i-a[j]]<min)
min=s[i-a[j]];
s[i]=min+1;
}
3、分析:本题的要求是旅行家通过在从起点到终点的各个加油站加油到达终点,对于给定的输入数据,我们首先必须保证汽车通过在各个站的加油能够顺利的到达终点,然后再找出一个代价最小的加油策略。所以,在预算最少费用之前,有必要对输入数据进行分析,看是否能使旅行家到达目的地。所以解决本问题分两步来进行。
第一步:分析输入的数据,看汽车是否能够顺利到达终点,如果不能就就直接结束;如果能就转到第二步;
第二步:当已经确认旅行家能够到达目的地之后,再预算最少费用,此部分为本题的核心部分。
为了便于分析,我们把起点命名为s0加油站,终点为s(n+1)加油站,中间n个加油站依次为s1,……,sn。由于汽车油箱的容量有限,所以在加满油后所走的路程要大于等于相邻两个加油站的最大距离,否则就不能顺利到达终点。设加满一箱油能走的最大距离为maxmile,则通过以下函数可以判断是否能够顺利到达终点。
bool judge(int n ,*s)
for(int i=1;i<=n+1;i++)
if(s[i]与s[i-1]之间的距离> maxmile)
return false
return true;
第二步计算最小费用为为本题的核心。为了能够顺利到达终点,那么我们要使汽车在到达目的地之前的每一时刻,都必须保证油箱中的汽油足够行驶到下一油站。而要达到最小的费用,我们则要遵循这样一个原则:“在油价便宜的站加尽可能多的油”,因为起点到终点的距离是固定的,刚开始油箱是空的,那么总共要加的汽油量也是固定的,如果低价的油加的多,那么高价的油加的就会少,所以总的费用也会少,根据这个原则我们可以采用贪心算法来分析,每次选取最便宜的油加最多。这样可以保证最后的总费用最少。设站s[i]的油价为s[i].price,站s[i]与起点s[0]的距离为s[i].distance。终点s[n+1]的油价为0。
每到一个站要加油之前,我们通过检查后续站的油价来决定在当前站加多少油。我们可以分两情况来讨论:
第一种:当前站i的油价高于下一站i+1的油价,在这种情况下,我们在当前站i所加的油只需要刚好能够到达下站i+1就可以了,下步就直接转到i+1站加油。
第二种:如果当前站i的价格小于i+1站的价格,而且在加满油能够到达i+1站,那么就连续与同时满足以上两个条件的后续加油站i+2,i+3,i+4,……进行比较,最后查找在站j处停下,分析查找停下的原因,选择不同的加油策略。
(1)当j=n+1时,即在终点处停下时,则把油直接加到刚好能到达终点,结束程序。
(2)如果是因为从i站加满油都不能到达j站而停止查找时,那么在i站把油箱加满,然后找出从i+1到j-1站中油价最底的那个站d作为下一个要加油的站点(d可以在查找比较的过程中同时完成),下一步就直接跳到第d站进行分析。
(3)如果是因为j站的油价小于i站而停止查找时,则在i站把油加到刚好能到
达j站,把j站作为下一个要加油的站点,下一步就直接跳到第j站进行分析。
在每个要加油的站点都做这样的分析,然后记录下必要的信息(加油量,所用的费用,剩余的油量,下一个加油站点),直到到达终点站。
核心算法:
while(true)
{ if(s[i].price>s[i+1].price)
{把油加到i+1站;记录下必要的信息;i=i+1;}
else
{ j=i+1;
while((s[i].price<=s[j].price)且(能够到达j站)
{j=j+1;记录下油价最小的站d;到达终点退出循环}
if(j为终点)
{加油到终点;记录下必要的信息;退出循环}
if(s[j].distance-s[i].distance>maxmile)
{把油箱加满;记录下必要的信息;i=d;}
if(s[i].price>s[j].price)
{把油箱加到刚能到j站;记录下必要的信息;i=j;}
if(i为终点站) 退出循环;}
第九节简单搜索算法
思考与练习:
1、有一个由数字1,2,3……,9组成的数字串(长度不超过200),问如何将M(M<=20)个加号("+")插入到这个数字串中,使所形成的算术表达式的值最小.请编一程序解决这个问题.
注意:加号不能加在数字串的最前面或最末尾,也不应有两个或两个以上的加号相邻,M保证小于数字串的长度。
例如:数字串79846,若需要加入两个加号,则最佳方案为79+8+46,算术表达式的值为133。
算法分析:用f[i][j],表示的是在前i个字符中插入j个加号能达到的最小值,最后的答案也就是F[length(s)][m]。
于是就有一个动规的方程: F[i][j]:=min(f[i][j],f[k][j-1]+num[k+1][i]),num[k+1][i]表示k+1位到i位所形成的数字,这里显然是把加号插入了第k+1个位置上。
2、在一个瓶子中装有80毫升化学溶剂,实验中需要精确地平分成两份,没有量具,只有两个杯子,其中一个杯子的容量是50毫升,另一个杯子是30毫升,试设计一个程序将化学溶剂对分成两个40毫升,并以最少步骤给出答案。
算法提示:三个杯子水的初始状态为:80、0、0,生成新结点状态的方法为:将一个不空的杯子水倒入一个不满的杯中,且结点状态不重复,直到生成目标结点为止。
⑴数据: 可用一个数组构成存放生成结点(杯子水状态)的队;用另一个数组作为指向父结点的指针;设置好队头与队尾指针。
⑵结点的产生:
①将结点中不空的杯子水倒入一个不满的杯子中,生成新的结点并记下父结点位置;
②判断新结点是否已生成过,以避免死循环;
③生成的结点若为目标结点,输出。
⑶采用广度优先搜索策略。
3、编程计算由“*”号所围成的下列图形的总面积。面积的计算方法是统计*号所围成的闭合曲线中水平线和垂直线交点的数目。比如在下图中“*”号围住了17个点,因此面积为17。
0 0 0 * *
0 0 0 * *
0 0 0 0 0
0 0 0 0 0
0 0 * 0 0
0 * 0 * *
0 * 0 0 *
0 0 * 0 *
0 0 0 * 0
0 0 0 0 0
输入:
输入的第一行为一个整数N(表示N*N 个节点),接下来的每行有N个数,为0或为*。
输出:
只需输出一个表示面积的整数。
算法分析:可采用广度优先,也可采用宽度优先搜索策略。先寻找*号点,然后开始进行搜索,先判断该图形是否是封闭区域,确定后再统计点数。
4、八数码问题。
现有一个数字矩阵:
2 8 3
1 6 4
7 0 5
要求通过移动“0”(与其上下左右的数字交换)得到:
1 2 3
8 0 4
7 6 5
请输出矩阵从起始状态到达目标状态的变化过程。
算法分析:初始状态为搜索的出发点,把移动一步后的布局全部找到,检查是否有达到目标的布局,如果没有,再从这些移动一步的布局出发,找出移动两步后的所有布局,再判断是否有达到目标的。依此类推,一直到某布局为目标状态为止,输出结果。
建立产生式系统:
(1)综合数据库。用3X3的二维数组来表示棋盘的布局比较直观。我们用Ch[i,j]表示第i行第j列格子上放的棋子数字,空格则用0来表示。为了编程方便,还需存储下面3个数据:该布局的空格位置(Si,Sj);初始布局到该布局的步数,即深度dep;以及该布局的上一布局,即父结点的位置(pnt)。这样数据库每一个元素应该是由上述几个数据组成的记录。
因为新产生的结点深度(从初始布局到该结点的步数)一般要比数据库中原有的结点深度大(或相等)。按广度优先搜索的算法,深度大(步数多)的结点后扩展,所以新产生的结点应放在数据库的后面。而当前扩展的结点从数据库前面选取,即处理时是按结点产生的先后次序进行扩展。这样广度优先搜索的数据库结构采用队列的结构形式较合适。我们用记录数组data来表示数据库,并设置两个指针:Head为队列的首指针,tail为队列的尾指针。
(2)产生规则。原规则规定空格周围的棋子可以向空格移动。但如果换一种角度观察,也可看作空格向上、下、左、右4个位置移动,这样处理更便于编程。设空格位置在(Si,sj),则有4条规则:
①空格向上移动: if (si-1>=1 then ch[si][sj]=ch[si-1][sj];ch[si-1][sj]=0
②空格向下移动: if si+1<=3 then ch[si][sj]=ch[si+1][sj];ch[si+1][sj]=0
③空格向左移动: if sj-1<=1 then ch[si][sj]=ch[si][sj-1];ch[si][sj-1]=0
④空格向右移动: if sj+1<=3 then ch[si][sj]=ch[si][sj+1];ch[si][sj+1]=0
我们用数组Di和Dj来表示移动时行列的增量,移动后新空格的位置可表示为:
nx=si+di(r)
ny=sj+dj(r)
其中,r=1,2,3,4为空格移动方向,且
r 1 2 3 4
方向 左 上 右 下
di 0 -1 0 1
dj -1 0 1 0
根据给出的分析可写出对应的程序。
第十节动态规划
思考与练习P214:
1、石子合并问题。
在一个圆形操场的四周摆放着n堆石子(n<=100),现要将石子有次序地合并成一堆.规定每次只能选取相邻的两堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分.编一程序,由文件读入堆栈数n及每堆栈的石子数(<=20).
(1)选择一种合并石子的方案,使用权得做n-1次合并,得分的总和最小;
(2)选择一种合并石子的方案,使用权得做n-1次合并,得分的总和最小;
输入数据:
第一行为石子堆数n;
第二行为每堆的石子数,每两个数之间用一个空格分隔.
输出数据:
从第一至第n行为得分最小的合并方案.第n+1行是空行.从第n+2行到第2n+1行是得分最大合并方案.每种合并方案用n行表示,其中第i行(1<=i<=n)表示第i次合并前各堆的石子数(依顺时针次序输出,哪一堆先输出均可).要求将待合并的两堆石子数以相应的负数表示.
输入输出范例:
输入:
4
4 5 9 4
输出:
-4 5 9 -4
-8 -5 9
-13 -9
22
4 -5 -9 4
4 -14 -4
-4 -18
22
算法分析:采用动态规划求解的关键是确定所有石子堆子序列的最佳合并方案。这些石子堆子序列包括:
{第1堆、第2堆}、{第2堆、第3堆}、……、{第N堆、第1堆};
{第1堆、第2堆、第3堆}、{第2堆、第3堆、第4堆}、……、{第N堆、第1堆、第2堆};……
{第1堆、……、第N堆}{第1堆、……、第N堆、第1堆}……{第N堆、第1堆、……、第N-1堆}
为了便于运算,我们用〔i,j〕表示一个从第i堆数起,顺时针数j堆时的子序列{第i堆、第i+1堆、……、第(i+j-1)mod n堆}
它的最佳合并方案包括两个信息:
①在该子序列的各堆石子合并成一堆的过程中,各次合并得分的总和;
②形成最佳得分和的子序列1和子序列2。由于两个子序列是相邻的, 因此只需记住子序列1的堆数;
设
f〔i,j〕──将子序列〔i,j〕中的j堆石子合并成一堆的最佳得分和;
c〔i,j〕──将〔i,j〕一分为二,其中子序列1的堆数;
(1≤i≤N,1≤j≤N)
显然,对每一堆石子来说,它的
f〔i,1〕=0
c〔i,1〕=0 (1≤i≤N)
对于子序列〔i,j〕来说,若求最小得分总和,f〔i,j〕的初始值为∞;若求最大得分总和,f〔i,j〕的初始值为0。(1≤i≤N,2≤j≤N)。
动态规划的方向是顺推(即从上而下)。先考虑含二堆石子的N个子序列(各子序列分别从第1堆、第2堆、……、第N堆数起,顺时针数2堆)的合并方案
f〔1,2〕,f〔2,2〕,……,f〔N,2〕
c〔1,2〕,c〔2,2〕,……,c〔N,2〕
然后考虑含三堆石子的N个子序列(各子序列分别从第1堆、第2堆、……、第N堆数起,顺时针数3堆)的合并方案
f〔1,3〕,f〔2,3〕,……,f〔N,3〕
c〔1,3〕,c〔2,3〕,……,c〔N,3〕
……
依次类推,直至考虑了含N堆石子的N个子序列(各子序列分别从第1堆、第2堆、 ……、第N堆数起,顺时针数N堆)的合并方案
f〔1,N〕,f〔2,N〕,……,f〔N,N〕
c〔1,N〕,c〔2,N〕,……,c〔N,N〕
最后,在子序列〔1,N〕,〔2,N〕,……,〔N,N〕中,选择得分总和(f值)最小(或最大)的一个子序列〔i,N〕(1≤i≤N),由此出发倒推合并过程。
动态规划方程和倒推合并过程
对子序列〔i,j〕最后一次合并,其得分为第i堆数起,顺时针数j堆的石子总数t。被合并的两堆石子是由子序列〔i,k〕和〔(i+k-1)mod
n+1,j-k〕(1≤k≤j-1)经有限次合并形成的。为了求出最佳合并方案中的k值,我们定义一个动态规划方程:
当求最大得分总和时
f〔i,j〕=max{f〔i,k〕+f〔x,j-k〕+t}
1≤k≤j-1
c〔i,j〕=k│ f〔i,j〕=f〔i,k〕+f〔x,j-k〕+t
(2≤j≤n,1≤i≤n)
当求最小得分总和时
f〔i,j〕=min{f〔i,k〕+f〔x,j-k〕+t}
1≤k≤j-1
c〔i,j〕=k│ f〔i,j〕=f〔i,k〕+f〔x,j-k〕+t
(2≤j≤n,1≤i≤n)
其中x=(i+k-1)mod n+1,即第i堆数起,顺时针数k+1堆的堆序号。
2、背包问题
设有n种物品,每种物品有一个重量及一个价值。但每种物品的数量是无限的,同时有一个背包,最大载重量为xk,今从n种物品中选取若干件(同一种物品可以多次选取),使其重量的和小于等于xk,而价值的和为最大。
输入数据:
第一行两个数:物品总数n,背包载重量xk;两个数用空格分隔;
第二行n个数,为n种物品重量;两个数用空格分隔;
第三行n个数,为n种物品价值; 两个数用空格分隔;
输出数据:
第一行总价值;
以下n行,每行两个数,分别为选取物品的编号及数量;
输入样例:
4 10
2 3 4 7
1 3 5 9
输出样例:
12
2 1
4 1
算法提示:
设
w[i]:第i个背包的重量;
p[i]:第i个背包的价值;
其状态转移方程为f[i][,j] = max { f [i- k*w[j]][ j-1] + k*p[j] } (0<=k<= i div w[j])
其中f[i][j]表示容量为i时取前j种背包所能达到的最大值。
3、抄书问题。
假设有m本书(编号为1,2,…m),想将每本复制一份,m本书的页数可能不同(分别是p[1],p[2],…p[m])。任务是将这m本书分给k个抄写员(k<=m〉,每本书只能分配给一个抄写员进行复制,而每个抄写员所分配到的书必须是连续顺序的。
意思是说,存在一个连续升序数列0=b[0]<b[1]<b[2]<…<b[k-1]<b[k]=m,这样第i号抄写员得到的书稿是从b[i-1+1]第b[i]本书。复制工作是同时开始进行的,并且每个抄写员复制的速度都是一样的。所以,复制完所有书稿所需时间取决于分配得到最多工作的那个抄写员的复制时间。试找一个最优分配方案,使分配给每一个抄写员的页数的最大值尽可能小(如存在多个最优方案,只输出其中一种)。
输入格式:
第一行两个数m,k:表示书本数目与人数;第二行m个由空格分隔的整数,m本书每本的页数.
输出格式:
共k行。每行两个整数:第i行表示第i号抄写员抄的书本编号起止。
算法分析:该题中M本书是顺序排列的,K个抄写员选择数也是顺序且连续的。不管以书的编号,还是以抄写员标号作为参变量划分阶段,都符合策略的最优化原理和无后效性。考虑到K〈=M,以抄写员编号来划分会方便些。
设F(I,J)为前I个抄写员复制前J本书的最小“页数最大数”。于是便有F(I,J)=MIN{ F(I-1,V),T(V+1,J)} (1〈=I〈=K,I〈=J〈=M-K+I,I-1〈=V〈=J-1〉。其中T(V+1,J)表示从第V+1本书到第J本书的页数和。起步时F(1,1)=P1。
观察函数递推式,发现F(I)阶段只依赖于F(I-1)阶段的状态值,编程时可令数组F的范围为(0…1,1…M),便于缩小空间复杂度。