102

本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

  • Upload
    others

  • View
    9

  • Download
    0

Embed Size (px)

Citation preview

Page 1: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732
Page 2: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732
Page 3: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

本书包含全部题目的源代码和测试数据,以及多达 110 多页的扩展资料电子稿

扫描书后的二维码即可下载资源包,或者凭购书单号与我联系 QQ:110289732

全套淘宝链接

https://item.taobao.com/item.htm?spm=a230r.1.14.210.jdwfzr&id=531290748180&ns=1&abb

ucket=4#detail

此外在京东网、淘宝网、当当网、亚马逊搜索关键字“算法竞赛宝典”也可购买。

全国各省市中,较大的新华书店,图书大厦也可能有售。

购买的读者,谢谢你们的支持, 请一定要在购买网站上点赞和留言,因为出版社最后是要

算销量的,谢谢!

试读章节及本书资源包下载网站:www.razxhoi.com

Page 4: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

第二部试读章节

第一章 分治算法

在计算机科学中,分治算法是一种很重要的算法。字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的

相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单地直接求解,原问题的解即子问题的解的合并。

这个技巧是很多高效算法的基础,如排序算法(快速排序,归并排序),傅立叶变换(快速傅立叶变换)……

折半查找法

【题目描述】折半查找法(half.cpp/c/pas)

大魔导师培根曾经说过:“读史使人明智,读诗使人聪慧,演算使人精密,哲理使人深刻,伦理学使人有修养,逻辑修辞使

人善辩。”由此可见书籍的重要性是不言而喻的。而与书籍天天打交道的图书管理员,更是夺天地之造化,吸日月之精华的“神

之职业”。据史料记载,魔法世界从古至今诞生的众多不平凡的人物中,有不少人都曾经做过“图书管理员”,如道家学派创始人

老子,威软公司创始人比耳、少林藏经阁的扫地神僧等等。所以,作为以马虎自负出名的楚继光,在魔法学院的社会实践活动中

又怎么会放过这“天将降大任于斯人也”的必经锻炼呢。但想成为一个合格的图书管理员并不容易,他必须能够在一排(10000

以内)已按编号大小排好序的图书中,快速地按编号查找到某本书所在的位置。

【输入格式】

输入文件第一行是 N,表示有 N个元素,第二行是 N个数,第三行是 M表示要查找的数。

【输出格式】

一个数,即如找到该数,则输出位置,否则输出-1。

【输入样例】

3

2 4 6

4

【输出样例】

Page 5: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

2

递归二分算法★

将已排好序的数列依次存入数组 a[],设查找数值为 X,用指针 bot 指向数列最左端位置(最小值),指针 top 指向数列最右

端位置(最大值),取 bot 和 top 的中间值 mid 指向数列中间,如图 1.1 所示。

图 1.1

当 top>bot 时,比较查找 X 与 a[mid],有 3 种可能:

(1) 若 X=a[mid],则表示找到,退出比较查找;

(2) 若 X<a[mid],则选择前半段继续比较查找,bot 不变,top 变成 mid-1;

(3) 若 X>a[mid],则选择后半段继续查找,bot 变成 mid+1,top 不变。

结束的过程有两种:一种是找到了 X=a[mid];另一种是没找到,即 top<bot。

完整的参考代码如下所示:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

//递归的折半查找法

#include <iostream>

#include <cstdio>

#include <cstdlib>

#define Max 10001

using namespace std;

int a[Max],key;

int search(int bot,int top)

{

int mid;

if(top>=bot)

{

mid=(top+bot)/2;//取中间值 mid

if(key==a[mid]) //如果相等,则打印该数

{

cout<<mid<<endl;

return 0;

}

else if(key<a[mid]) //如 x 小于中间值, 则取前半段

search(bot,mid-1);

else //如 x 大于中间值,则取后半段

search(mid+1,top);

}

else

{

printf("-1\n");

return 0;

}

}

Page 6: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

32

33

34

35

36

37

38

39

40

41

42

43

int main()

{

freopen("half.in","r",stdin);

freopen("half.out","w",stdout);

int n;

cin>>n;

for(int i=1;i<=n;++i)

cin>>a[i];

cin>>key;

search(1,n);

return 0;

}

考虑一个猜数字游戏:即取任何一个大于 0 小于 1024 的自然数,提问方最

多问 10次这个数比某数大吗?被提问方只需回答“是”或者“不是”,提问方就

可以猜出这个数字。呵呵,你能明白其中的奥秘吗?

非递归二分法★

折半查找法除了可以用递归的方法外,还可以用非递归的方法,完整代码如下所示:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

//非递归的折半查找法

#include <iostream>

#include <cstdlib>

#include<cstdio>

#define MAXN 10001

using namespace std;

int key,top,bot,mid,n,a[MAXN];

void half()//二分查找法

{

top=1;

bot=n;

while (top<=bot)

{

mid=(bot+top)/2;

if (key==a[mid])//如果正好找到

{

cout<<mid<<endl;

exit(0);

}

else if (key<a[mid])//选择左半段

bot=mid-1;

else //选择右半段

top=mid+1;

}

cout<<-1<<endl;

}

int main ()

{

freopen("half.in","r",stdin);

freopen("half.out","w",stdout);

cin>>n;

for(int i=1;i<=n;i++)

cin>>a[i];

cin>>key;

if (key<a[1] || key>a[n])

cout<<-1<<endl;

Page 7: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

39

40

41

42

else

half();

return 0;

}

考虑一个放魔法石的游戏:把 1000个魔法石装在 10个袋子中,任取其中的

一袋,或把几个袋中的魔法石数加起来,都能凑成 1~1000中的任何一种魔法石

数。你知道这 10个袋中分别装了多少个魔法石吗?

拓展与练习

【题目描述】神族文字 (dictionary.cpp/c/pas) POJ 2503

楚继光发现图书馆里收藏有许多上古时代的魔法书,这些上古时代的

魔法书使用一种传说中的“神族文字”来书写,幸运的是,楚继光手边恰

巧有一本词典可以帮助他。

【输入格式】

输入的词典内容最多包含有 100000个词条,每一个词条包含一个英文

单词,其次是一个空格和一个对应的“神族文字”。没有一个“神族文字”

在词典中出现一次以上。词典词条全部输入完毕后是一个空行,之后是需

要翻译的“神族文字”,每一个词一行,每个单词是一个最多为 10 个小写

字母的字符串。

【输出格式】

输出翻译好的英文,每行一个字。若词典中查找不到,输出“eh”。

【输入样例】

dog ogday

cat atcay

pig igpay

froot ootfray

loops oopslay

atcay

ittenkay

oopslay

【输出样例】

cat

eh

loops

魔法石的诱惑

【题目描述】魔法石的诱惑(rob.cpp/c/pas)

修罗王远远地看见邪狼狂奔而来,问道:“慌慌张张得跑什么?”

邪狼大口大口地喘气:“我路过一家魔法石店,看到摆着那么多高阶魔法石,我就跑进去抢了一大袋。”

修罗王怒道:“光天化日,众目睽睽之下,你也敢抢?”

邪狼:“我抢魔法石的时候,压根儿就没看见人,眼里只看见魔法石了。”

修罗王:“……”

其实邪狼的贪婪很容易理解,因为高阶魔法石有一个特征,即它的重量进行阶乘运算后末尾有几个0,就拥有同等重量普通

Page 8: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

魔法石几倍的魔法力。例如 5! =5×4×3×2×1=120,而 120 结尾包含 1 个零,这意味着该魔法石拥有同等重量的普通魔

法石1倍的魔法力。你的任务是找到最小自然数 N,使 N!在十进制下包含 Q 个零。

【输入格式】

一个数 Q (0≤Q≤108)。

【输出格式】

如果无解,输出"No solution",否则输出 N 。

【输入样例】

2

【输出样例】

10

分治算法★

这个很简单啊,就好像猜 1~100之间的一个数字,我先猜 50,如果大了,

我就缩小范围到 0~49,如果小了,我就缩小范围到 51~100。

本题中,N!随着N的增加,0的个数也在增加,所以是一个单调递增序列,

显然可以用二分法很快求出答案。

至于判断 N!有多少个零,本套书的第一部里曾经提到过一个公式是 N/5 +

N/(52) + N/(5

3) +…

参考程序如下所示:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

//魔法石的诱惑_分治算法

#include <iostream>

#include <cstdio>

#include <cstdlib>

using namespace std;

int solve(int n)

{

int ans = 0;

while (n > 0)

{

ans = ans + n / 5;

n = n / 5;

}

return ans;

}

void run()

{

int Q, i;

scanf("%d", &Q);

int start = 1;

int end = 500000000;

int ans = 500000001;

int mid;

int t;

while (start <= end)//二分查找

{

int mid = (end - start) / 2 + start;

int t = solve(mid);

if (t == Q && mid < ans)

ans = mid;

if (t > Q)

end = mid - 1;

else if ( t < Q )

Page 9: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

start = mid + 1;

else

end = mid - 1;

}

if (ans != 500000001)

printf("%d\n", ans);

else

printf("No solution\n");

}

int main()

{

run();

return 0;

}

数学方法★

这道题还可以用数学方法解决。

题目给出的是 Q,要求出的是 N,由于是要求出最小的自然数,所以 N 必定

是 5的倍数。又有:

Q= N/5 + N/(52) + N/(5

3) +……

=> Q = N(5k - 1) / [4×(5

k)] (根据等比数列的求和公式)

=> N = 4Q × [(5k)/(5

k-1)]

注意到 1 < (5k)/(5

k-1) ≤ 5/4,且当 k趋于无穷时,(5

k)/(5

k-1)趋于 1,

所以可先算出 N=4Q的末尾零的个数与所给的 Q比较,显然所求的数就在 4Q的附

近。

参考程序如下所示:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

//魔法石的诱惑──数学方法

#include <iostream>

#include <cstdio>

#include <cstdlib>

using namespace std;

int ZeroTrail(int n)//计算 n!的 0 的个数

{

int count = 0;

while(n)

{

count+=n/5;

n/=5;

}

return count;

}

int main()

{

int q;

scanf ("%d", &q);

if (!q)

{

printf ("1\n");

return 0;

}

int i = 4*q/5*5;

while (ZeroTrail(i) < q)

i += 5;

if (q == ZeroTrail(i))

Page 10: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

31

32

33

34

35

printf ("%d\n", i);

else

printf ("No solution\n");

return 0;

}

拓展与练习

【题目描述】近似整数(Approximation.cpp/c/pas) POJ 1650

给定一个浮点数A和一个整数 L,求在范围[1,L]内的两个整数 n 和 d,

使得 n/d 能近似等于A,且使误差|A-n/d|最小。

【输入格式】

第一行为一个浮点数A,第二行为一个整数L。

【输出格式】

两个整数 n 和 d。

【输入样例】

3.14159265358979

10000

【输出样例】

355 113

逃亡

【题目描述】逃亡(escape.cpp/c/pas)

邪狼紧张地说:“老大,警察快追过来了,我们快逃跑吧!”

修罗王傲然道:“在我的字典里没有逃跑……”

邪狼内心崇敬地想:“老大实在是太有领袖范了……”

修罗王接着说:“只有战略转移。”

邪狼:“……”

现在,修罗王和邪狼两人需要从 A 地出发尽快到达 B 地。出发时 A 地有一辆可带一人的自动驾驶悬浮车。又知两人步行速

度相同。问怎样利用小车才能使两人尽快同时到达 B 地。

【输入格式】

输入文件为 escape.in,有三个 int 类型整数,分别表示A、B两地的距离,步行速度和车速。

【输出格式】

输出文件为 escape.out,有一个小数位数为2的浮点数,即最短时间。

【输入样例】

100 5 10

【输出样例】

14.00

分治算法★

如图 1.2 所示,设两人分别为甲、乙,则最优方案应该是甲先乘车到达 C 后下车步行,小车回头接已经走到 E 的乙,假设在

D 相遇,乙乘车到达 B 时正好甲也步行到达,这样花费的时间最短。

Page 11: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

图 1.2

那就是求 C 点的位置了。设 A、B 距离为 S,步行速度为 a,车速为 b,甲

耗时 T1,乙耗时 T2。

再设 C0为起点位置,C1为终点位置,取中点位置 C=(C0+C1)/2作为测试点,

即计算甲耗时 T1与乙耗时 T2,若 T1<T2,则取 C 与 C0的中点,否则取 C 与 C1的

中点,如此反复循环直到 T1=T2即可。

参考程序如下所示:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

//逃亡──分治算法

#include <cstdio>

#include <cstdlib>

#include <math.h>

#include <iostream>

using namespace std;

int main()

{

freopen("escape.in","r",stdin);

freopen("escape.out","w",stdout);

float s,a,b,c,c0,c1,t1,t2,t3,t4 ;

cin>>s>>a>>b;

c0=0;c1=s;

do

{

c=(c0+c1)/2.0;

t3=c/b;//甲乘车到 C 的时间

t1=t3+(s-c)/a;//甲用的总时间

t4=(c-t3*a)/(a+b);//小车从 C 回头与乙相遇的时间

t2=t3+t4+(s-(t3+t4)*a)/b;//乙用的总时间

if(t1<t2)

c1=c;

else

c0=c;

}while(fabs(t1-t2)>1e-4);

printf("%4.2f\n",t1);

return 0;

}

数学方法1★★

分治算法是好,但如果数学功底够好,完全可以用数学推导算出结果。另外

需要注意的是,由于涉及浮点数运算,所以不同的算法或数学运算过程,其结果

可能会略有不同。

如图 1.3 所示:设 S=总路程,V1=步行速度,V2=车速,t1=甲的乘车时间,t2=车返回与乙相遇的时间,t3=甲步行到终点的

时间(即乙乘车到终点的时间),P1 为车返回处,P2 为车与乙相遇入,Q1 为车返回时乙当时所处的位置。

Page 12: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

图 1.3

首先求 Q1 到 P1 的距离,显然有Q1P1 = AP1 − AQ1 = V2t1 − V1t1 = t1(V2 − V1)

则车返回与乙相遇的时间:2V1V

)1V2V(1t2t

2V1V

1P1Q2t

则乙乘车到终点的时间:2V

2t1V1t1VS3t

2t1V

2t2V2t1V1t1VS

(即甲步行到终点的时间)

2V1V

)1V2V(1t

1V

2t2V2t1V1t1VS

推出:2V

2t1V1t1VS

2V1V

)1V2V(1t

1V

2t2V2t1V1t1VS

设2V1V

1V2Vk

,则由①式可得 t2=kt1 ③

将③式代入②式,得2V

)1kt1t(1VS 1kt

1V

1kt2V)1kt1t(1VS

=>V1S-V12(1+k)t1=V2S-V1V2(1+k)t1-V22kt1-V1V2kt1 (等式两边同乘以 V1V2)

=>(V1-V2)S=[V12(1+k)-V1V2(1+k)-V22k-V1V2k]t1

故k2V)k21(2V1V)k1(1V

S)2V1V(1t

22

=>k)2V2V1V21V(2V1V1V

S)2V1V(1t

222

=>

2V1V

1V2V)2V2V1V21V(2V1V1V

S)2V1V(1t

222

=>

2V1V

)21V

2V)(1V2V(

12V

2V1V

1V)1V2V(1V

1V

S

2V

S

1t

(上下同除以 V1V2)

则最终答案为 t1+t2+t3。

Page 13: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

参考程序如下所示:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

//逃亡──数学算法1

#include <iostream>

#include <cstdlib>

#include <cstdio>

using namespace std;

int main()

{

freopen("escape.in","r",stdin);

freopen("escape.out","w",stdout);

double s,v1,v2;

double t1,t2,t3;

cin>>s>>v1>>v2;

//总路程 走速 车速

t1 = (s/v2-s/v1) / //甲先乘车时间

((v1+(v2-v1)*v1/(v1+v2))/v2-1-(v2-v1)*(v2/v1+2)/(v1+v2));

t2 = t1*(v2-v1)/(v1+v2); //车回来与乙相遇的时间

t3 = (s-v1*t1-v1*t2)/v2; //甲步行到终点的时间

//即 乙乘车到终点的时间

printf("%.2f\n",t1+t2+t3);

return 0;

}

数学方法2★★

如图 1.4 所示,设距离为 S,车速为 a,人行走速度为 b,总时间为 T,AC 的距离为 k,则 CB=S-k。

图 1.4

则有b

kS

a

kT

(即甲在 AC 花费的时间+甲在 CB 花费的时间)

=>ab

aSabTk

ab

aS

ab

abT

又因乙到达AC及车到达C点又返回遇到乙的总距离为2k,如图 1.5 所示。

图 1.5

则又有b

a)ba

k2(S

ba

k2T

Page 14: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

=> b

S)ba

k2)(ab(

T

=> b

Sba

)ab

aS

ab

abT(2

)ab(T

(将①式中的 k 代入)

=> (a+b)bT=2abT-2aS+(a+b)S (等式两边同乘以(a+b)b)

=> [(a+b)b-2ab]T=(a+b)S-2aS

故ab2b)ba(

aS2S)ba(T

其实还可以想出其他的数学方法来解决该题。此处不再一一列举,感兴趣的

读者请尝试完成。

拓展与练习

【题目描述】花费(Expense.cpp/c/pas)POJ 3273

邪狼发愁的说:“这么高级的车怎么就断轴了?”

修罗王一脸的郁闷:“是啊,当时厂家还信誓旦旦的拍胸脯说这车经过

魔法加强处理的。这加两块木板也算?没办法,剩下的路程只好走路了。”

邪狼摸摸钱袋,说:“好像钱也没多少了。”

已知修罗王和邪狼的逃亡天数为 N(1 ≤N≤100000),每天需要花的钱

已经分配好,请把这些天分成 M(1≤M≤N)份(每份都是连续的天),则第

i 段的和为 sum[i](i=1,2,…,M),求 max{sum[i]}最小为多少?

【输入格式】

第一行为两个整数即N和M。第二行为N个数。

【输出格式】

输出分成M份后的最小和。

【输入样例】

7 5(表示 N=7,M=5)

200 300 300 200 500 221 420 (表示每天的花费)

【输出样例】

500

【题目描述】预算(budget.cpp/c/pas)NOIP 1999

邪狼:“天哪!这么多魔法石都不够用!”

修罗王:“没办法,通货膨胀,物价太高了。”

修罗王俩人为了节省花费,决定驾驶最老式的燃油汽车以最少的费用

从一个城市到另一个城市(假设出发时油箱是空的)。给定两个城市之间的距

离 D1、汽车油箱的容量 C(以升为单位),每升汽油能行驶的距离 D2,出发

点每升汽油价格 P 和沿途油站数 N(N 可以为零),油站 i 离出发点的距离 Di,

每升汽油价格 Pi(i=1,2,…,N)。计算结果四舍五入至小数点后两位。如

果无法到达目的地,则输出“No Solution”。

【输入格式】

Page 15: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

输入五个数,即 D1,C,D2,P,N。

【输出格式】

输出最小费用,如无法到达目的地,则输出“No Solution”。

【输入样例】

275.6 11.9 27.4 2.8 2 (分别表示 D1,C,D2,P,N)

102.0 2.9 (以下共 N 行,分别表示油站 i 离出发点的距离 Di 和每

升汽油价格 Pi)

220.0 2.2

【输出样例】

26.95(该数据表示最小费用)

快速幂运算

【题目描述】快速幂运算(power.cpp/c/pas)

邪狼:“老大,好像还是不够油钱啊?”

修罗王:“看来只好用我的独门绝技──能量增持术了。”

邪狼:“听说能量增持术很霸道的?”

修罗王:“没错,假设初始燃油能提供的能量为X,当我对它进行能量增持 n 秒后,该燃油的能量将达到 Xn。”

邪狼:“这么强大的技能啊,简直逆天了,不过怎么之前不见老大用过?”

修罗王:“偶尔偷偷用几次没关系,经常用,燃油公司会找我麻烦的。”

现已知X和 n,试计算 Xn的值。

【输入格式】

输入文件为 power.in,有两个正整数,即X和 n,其中 X≥0,n≥0。

【输出格式】

输出文件为 power.out,一个整数即结果,保证结果不超过整型范围。

【输入样例】

3 2

【输出样例】

9

基本快速幂算法★

求幂很简单啊,几行代码就搞定,当然,这速度有点慢:

unsigned power(unsigned x,unsigned n)//计算 x的 n次方

{

for(int i=0;i<n;i++)

x*=x;

return x;

}

传统的求幂算法在计算例如 2 的 13 次方时,程序将计算 12 次乘方,但实际上,可以先计算出 2×2=4 的值,这样 213 可以写

成 4×4×4×4×4×4×2(此处多余一个 2)的形式,再计算 4×4=16 的值,则 213 可以写成 16×16×16×2 的值,这样计算 2×2,4×4,

16×16×16×2 的值,只计算了 5 次即得出结果。

完整的参考程序如下:

1

2

3

4

// 基本快速幂运算

#include <cstdio>

#include <cstdlib>

#include <iostream>

Page 16: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

using namespace std;

int pow(int a,int b)

{

if(b==1)

return a;

else

{

int c=pow(a,b/2);

if((b%2)==0)

return c*c;

else

return c*c*a;

}

}

int main()

{

freopen("power.in","r",stdin);

freopen("power.out","w",stdout);

int X,n;

cin>>X>>n;

cout<<pow(X,n)<<endl;

return 0;

}

这个程序有一个小小的错误,你可以发现吗?

位优化快速幂算法★

其实可以通过位运算对基本快速幂算法进行优化,例如判断 n 是否偶数,可以使用“按位与”运算符“&”,即判断 n & 1 的

值是否为 0 即可。而 n=n/2 可以使用“右移”运算符“>>”,即 n>>=1 即可。

参考程序如下所示:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

//位优化快速幂运算

#include <iostream>

#include <cstdio>

#include <cstdlib>

using namespace std;

long long Pow(long long x, long long n)

{

long long result;

if (n == 0)

return 1;

else

{

while ((n & 1) == 0)//当 n 为偶数时

{

n >>= 1;//即 n=n/2

x *= x;

}

}

result = x;

n >>= 1;

while (n != 0)

{

Page 17: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

x *= x;

if ((n & 1) != 0)

result *= x;

n >>= 1;

}

return result;

}

int main()

{

freopen("power.in","r",stdin);

freopen("power.out","w",stdout);

long long x,n;

cin>>x>>n;

cout<<Pow(x,n)<<endl;

return 0;

}

顺便说一下,如果经过仔细观察,其实可以发现其运算过程与二进制运算是相同的,例如求 a156 的值,其中十进制 156 转换

为二进制为 10011100。

则 a156=(a4)×(a8)×(a16)×(a128) ,如图 1.6 所示。

图 1.6

拓展与练习

【题目描述】快速模幂(Modulo.cpp/c/pas)

试求 ab%n 的值,其中 a、b、n 均为整数范围内的数。

【输入格式】

三个整数即 a、b、n。

【输出格式】

输出结果。

【输入样例】

1 1 1

【输出样例】

0

运动会

循环比赛★

【题目描述】循环比赛(competition.cpp/c/pas)

Page 18: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

在修罗王和邪狼亡命天涯的同时,魔法学院一年一度的运动会正开的如火如荼。赛场上,业余评论员墨老师正在给观众做激

情解说:“鬣狗群……咬住!咬住!鬣狗头子立功了!不要给斑马任何的机会!伟大的鬣狗!伟大的鬣狗头子!它继承了猛兽光

荣的历史和传统!豹子、老虎、雄狮在这一刻灵魂附体!”

呃,这个,我们不要管那些场上选手的绰号了,现在的问题是有某个项目的 n 个选手进行循环比赛,其中 n=2m,要求每名

选手要与其他 n-1 名选手都赛一次。每名选手每天比赛一次,循环赛共进行 n-1 天,要求每天没有选手轮空。比赛时间表格如

图 1.7 所示(假定 m=3)。

图 1.7

【输入格式】

输入文件为 competition.in,为一个整数 m,m≤5。

【输出格式】

输出文件为 competition.out,为 n 行 n 列的整型矩阵,即比赛表格。矩阵中每个元素占用空间为四个字符。

【输入样例】

【输出样例】

1 2 3 4

2 1 4 3

3 4 1 2

4 3 2 1

【测试说明】

比较方式为忽略多余的空格及文件尾的空格。

【算法分析】

通过这个表格很难直接给出结果,因此需要将规模减少一点,比如只有 2 个选手(m=1)时,比赛安排应为图 1.8:

图 1.8

同理可得 4 个选手(m=2)时的比赛安排如图 1.9 所示:

Page 19: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

图 1.9

可以看出,矩阵的左上角与右下角是对称的,右上角与左下角是对称的,由此规律,逐层合并,可得到 8 个选手(m=3)的

比赛顺序如图 1.10 所示。

图 1.10

我明白了,我们可以用数组 a 记录 2m个球队的循环比赛表,整个循环比赛表

从最初的 1×1 方阵按上述规则生成 2×2 的方阵,再生成 4×4 的方阵,……直

到生成出整个循环比赛表为止。变量 h 表示当前方阵的大小,也就是要生成的下

一个方阵的一半。

参考程序如下所示:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

//循环比赛

#include <iostream>

#include <cstdio>

#include <cstdlib>

#include <iomanip>

using namespace std;

int i,j,h,person,n;

int a[32+1][32+1];

int main()

{

freopen("competition.in","r",stdin);

freopen("competition.out","w",stdout);

cin>>n;

person=1;

a[1][1]=1;

h=1;

for(i=1;i<=n;i++)

person=person*2;

do

{

for(i=1;i<=h;i++)

for(j=1;j<=h;j++)

Page 20: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

{

a[i][j+h]=a[i][j]+h;//构造右上角方阵

a[i+h][j]=a[i][j+h];//构造左下角方阵

a[i+h][j+h]=a[i][j];//构造右下角方阵

}

h=h*2;

}while(!(h==person));

for(i=1;i<=person;i++)

{

for(j=1;j<=person;j++)

cout<<setw(4)<<a[i][j];

cout<<"\n";

}

return 0;

}

残缺棋盘★

【题目描述】残缺棋盘(chessboard.cpp/c/pas)

棋手楚继光和张琪曼一动不动地在棋盘前已经沉默地坐了五个小时。

他们全神贯注地盯着每粒棋子。

突然,楚继光说:“原则上我是反对在下棋时说话的,但是我现在不得不开口问:现在究竟该谁走下一步棋了?”

张琪曼说:“谁先走都不重要了,现在的问题是:谁把这个棋盘上的格子损坏了?”

正如图 1.11 所示,有一正方形棋盘,其边长为 2k(1<k<10),其中有一格损坏。现在想用如图中间所示形状的硬纸板将没有

坏的所有格子盖起来。而硬纸板不得放入坏格中和棋盘外面。编程输出一种覆盖方案,将共用一块硬纸板的三个格子用相同的数

字表示。

图 1.11

上图所示是 k=2 的情形,且输出结果不一定和图示方案一致,符合题目要求即可,输出时只需输出数字方阵而不必画出格子

线。

【输入格式】

三个整数,即 k和坏格子的 y坐标和 x坐标(注意坏格子的坐标输入顺序)。

【输出格式】

数字方阵,其中坏坐标以数字7表示。

【输入样例】

2 1 1

【输出样例】

7 4 2 2

4 4 4 2

3 4 4 4

3 3 4 4

Page 21: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

解决这道题,可以从最简单的情况开始分析,即假定 k=1,则棋盘为 2×2的

形式,此时,无论坏格是四个格子中的哪一个,都有唯一的解,即恰好用一块硬

纸片将其覆盖。如果再扩大棋盘呢?

例如 k=2,我们可以将之平分为 2×2 的四个正方形,并将完好的三个正方形共用一个硬纸板,这样,四个正方形就变成了 k=1

的情况。图 1.12 所示两种情况的覆盖方案。

图 1.12

对于更大的棋盘,以此类推即可,其参考程序如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

//残缺棋盘

#include <iostream>

#include <cstdlib>

#include<cstdio>

using namespace std;

int k=1,c[1024][1024];//左上角坐标设为(1,1)

void lt(int x1,int y1,int x2,int y2 )//左上

{

c[x1+(x2-x1)/2+1][y1+(y2-y1)/2]=4;

c[x1+(x2-x1)/2+1][y1+(y2-y1)/2+1]=4;

c[x1+(x2-x1)/2][y1+(y2-y1)/2+1]=4; //填充图形 4

}

void lb(int x1,int y1,int x2,int y2 )// 左下

{

c[x1+(x2-x1)/2][y1+(y2-y1)/2]=2;

c[x1+(x2-x1)/2+1][y1+(y2-y1)/2]=2;

c[x1+(x2-x1)/2+1][y1+(y2-y1)/2+1]=2;//填充图形 2

}

void rt(int x1,int y1,int x2,int y2 )// 右上

{

c[x1+(x2-x1)/2][y1+(y2-y1)/2]=3;

c[x1+(x2-x1)/2][y1+(y2-y1)/2+1]=3;

c[x1+(x2-x1)/2+1][y1+(y2-y1)/2+1]=3;//填充图形 3

}

void rb(int x1,int y1,int x2,int y2 )//右下

{

c[x1+(x2-x1)/2+1][y1+(y2-y1)/2]=1;

c[x1+(x2-x1)/2][y1+(y2-y1)/2]=1;

c[x1+(x2-x1)/2][y1+(y2-y1)/2+1]=1;//填充图形 1

}

void work(int x1,int y1,int x2,int y2)//递归函数

{

int i,j,p,q;

if(x2-x1==1)//当方格为 2*2 时,填充 并结束

{

for(i=x1;i<=x2;i++)//查找坏点或已覆盖点在何处

Page 22: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

for(j=y1;j<=y2;j++)

if(c[i][j]!=0)

p=i,q=j;

if(p==x1 && q==y1)//在左上角

lt(x1,y1,x2,y2);

if(p==x1 && q==y2) //在左下角

lb(x1,y1,x2,y2);

if(p==x2 && q==y1)//在右上角

rt(x1,y1,x2,y2);

if(p==x2 && q==y2) //在右下角

rb(x1,y1,x2,y2);

}

else

{

for(i=x1;i<=x2;i++) //查找该方块的坏点或已覆盖的点在何处

for(j=y1;j<=y2;j++)

if(c[i][j]!=0)

p=i,q=j;

if(p<=(x1+(x2-x1)/2))

if( q<=(y1+(y2-y1)/2))//如该点位于左上角

lt(x1,y1,x2,y2);

else //如该点在左下角

lb(x1,y1,x2,y2);

if(p>(x1+(x2-x1)/2))

if (q<=(y1+(y2-y1)/2)) //如该点位于右上角

rt(x1,y1,x2,y2);

else //如该点在右下角

rb(x1,y1,x2,y2);

work(x1,y1,(x1+(x2-x1)/2),(y1+(y2-y1)/2));//平分为四块后递归

work((x1+(x2-x1)/2+1),y1,x2,(y1+(y2-y1)/2));

work(x1,(y1+(y2-y1)/2+1),(x1+(x2-x1)/2),y2);

work((x1+(x2-x1)/2+1),(y1+(y2-y1)/2+1),x2,y2);

}

}

void out()//输出数组

{

int i,j;

for(i=1;i<=k;i++)

{

for(j=1;j<=k;j++)

cout<<c[j][i]<<' ';

cout<<endl;

}

}

int main()

{

freopen("chessboard.in","r",stdin);

freopen("chessboard.out","w",stdout);

int i,n,x,y;

cin>>n>>x>>y;

for(i=1;i<=n;i++)

k=k*2;

c[x][y]=7;//定义坏的坐标为 7

work(1,1,k,k);//左上顶点坐标为(1,1),右下顶点坐标为(k,k)

out();

return 0;

}

该程序既涉及了递归,又涉及了分治,所以我们称为递归分治。通过分治,

可以将大问题不断细化成几个部分分别处理以减少运行时间。

Page 23: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

解一元三次方程

【问题描述】解一元三次方程(equation. cpp /c/ pas)NOIP 2001

课堂上,墨老师正在对学生上课:“历史上曾经出现过一个出身贫寒但却通过艰苦努力自学成才的数学家尼柯洛•冯塔纳

(Niccolo Fontana),他找到了一元三次方程一般形式的求根方法并以此在几次公开的数学较量中名声大噪。不幸的是,他的研究

成果引来了另一位数学家卡尔丹诺的觊觎,卡尔丹诺通过甜言蜜语和肉麻吹捧获得了冯塔纳的研究成果并据为已有,这就是‘卡

尔丹诺公式’的由来。好了,故事先讲到这儿,楚继光,你来试试解一元三次方程。”

楚继光睡眼惺忪:“老师,一元钱可以解三次方程?”

墨老师怒道:“你有没有认真听我讲课啊!”

现在,不管你有没有一元钱,请你帮助解决形如 ax3+bx2+cx+d=0 这样的一个一元三次方程。给出该方程中各项的系数(a,

b,c,d 均为实数),并约定该方程存在三个不同实根(根的范围在-100 至 100),且根与根之差的绝对值≥1。要求由小到大依

次在同一行输出这三个实根(根与根之间留有空格),并精确到小数点后 2 位。

【输入格式】

输入文件为 equation.in,包含四个实数即 a、b、c、d。

【输出格式】

输出文件为 equation.out,为排好序的三个实根。

【输入样例】

1 -5 -4 20

【输出样例】

-2.00 2.00 5.00

枚举法★

一般地,一元三次方程在坐标上的图像如图 1.13 所示。

图 1.13

因为数据规模不大,所以最简单的方法是从 -100.00 ~ 100.00 枚举 x,步

长 0.01,得到 20000 个 f(x) ,取跟 0 最接近的三个 f(x),对应的 x 即是答

案。

参考程序如下所示:

1

2

3

4

5

6

7

8

9

10

11

//一元三次方程简单枚举法

#include <cstdio>

#include <cstdlib>

#include <iostream>

using namespace std;

int main()

{

double a,b,c,d,x,fx;

int i;

freopen("equation.in","r",stdin);

Page 24: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

12

13

14

15

16

17

18

19

20

21

22

23

24

freopen("equation.out","w",stdout);

cin>>a>>b>>c>>d;

cout.precision(2);//设精度,即小数点位数

cout.setf(ios::fixed);// 以定点形式显示浮点数

for (i=-10000;i<=10000;x=(++i)/100.0)

{

fx=a*x*x*x+b*x*x+c*x+d;

if (fx>=-0.01 && fx<=0.01)

cout<<x<<' ';

}

cout<<'\n';

return 0;

}

二分法★

记方程为 f(x)=0,若存在 2 个不同的数 x1和 x2,且 f(x1)•f(x2)<0,则在(x1,x2)之间一定有一个根。

当已知区间(a,b)内有一个根时,可用二分法求根。若区间(a,b)内有根,则必有 f(a)•f(b)<0。重复执行如下的过程:

令 m=(a+b)÷2,

(1) 若 a+0.0001>b 或 f(m)=0,则可确定根为 m,并退出过程;

(2) 若 f(a)•f(m)<0,则必然有 f(m)•f(a)<0 根在区间(a,m)中,故对区间重复该过程;

(3) 若 f(a)•f(m)>0,则必然有 f(m)•f(b)<0,根在区间(m,b)中,对此区间重复该过程。

执行完毕,就可以得到精确到 0.0001 的根。

所以,根据“根与根之差的绝对值≥1”,先对区间[-100,-99]、[-99,

-98]……[99,100]进行枚举,确定这些区间内是否有解,然后对有解的区间使

用上面的二分法。这样就能快速地求出所有的解了。

参考程序如下所示:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

//一元三次方程──二分法

#include<fstream>

#include<iomanip>

#include<cstdio>

#include<cstdlib>

using namespace std;

ifstream cin("equation.in");

ofstream cout("equation.out");

float a,b,c,d;

int n;

float ans[4];

float Equation(float x)

{

return ((a*x+b)*x+c)*x+d;

}

void solve(float l,float r)

{

if(Equation(l)*Equation(r)>0 && ((r-l)<1 || n>=2))

return ;

float mid=(l+r)/2;

if(Equation(mid)<=1e-4 && Equation(mid)>=-1e-4)

{

ans[++n]=mid;

return ;

}

Page 25: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

29

30

31

32

33

34

35

36

37

38

solve(l,mid),solve(mid,r);

}

int main()

{

cin>>a>>b>>c>>d;

solve(-100,100);

cout<<fixed<<setprecision(2)<<ans[1]<<' '<<ans[2]<<' '<<ans[3]<<'\n';

return 0;

}

拓展与练习

【问题描述】交叉的梯子(ladders.cpp/c/pas) PKU 2507

如图 1.14 所示,魔法学院有一个狭窄的街道上矗立着两栋楼,右楼有

一个长为 x 的梯子搭在左楼,左楼有一个长为 y 的梯子搭在右楼,两梯子

的交叉点的高度在 c 处。问这个街道的宽度。

图 1.14

【输入格式】

每行一组数据,表示 x,y,c。

【输出格式】

输出街道的宽度。

【输入样例】

30 40 10

12.619429 8.163332 3

10 10 3

10 10 1

【输出样例】

26.033

7.000

8.000

9.798

数的查找

第 k 小数1★

【问题描述】第 k 小数1(K1.cpp/c/pas)

Page 26: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

“哇,好多冰淇淋啊!”张琪曼跑到学院的冷饮店,伸出2根手指对冰淇淋老板说:“来3个。”老板蒙了,问:“几个?”张

琪曼又伸出3根手指说:“2个。”

老板满头大汗:“我不管你要几个,只要你能从你口袋里的魔法石里找出第 k 小的魔法石,冰淇淋随便你吃。”

现在你知道该怎么做了吧?那就是:对于给定的 n 个元素的无序数组,要求从中找出第 k 小的元素。

【输入格式】第一行是总数 n(1<n<100000)和 k,第二行是 n 个待比较元素。

【输出格式】

第 k 小的数在数组中的位置。

【输入样例】

5 3

25 9 90 57 3

【输出样例】

1

这个好简单吧,直接排序不就行了吗?不过,貌似可以不用排序就可以得到

答案?

例如一列由10个元素组成的数组:[5 7 1 2 3 9 8 10 4 6],假设找出 k=4 的元素。

将第一个元素5作为参照数,将比5小的数放在5的左边,比5大的数放在5的右边。则数组第一次调整为:[2 1 4 3]

5 [10 9 8 7]。

比5小的数有4个,所以将搜索范围缩小到5的左边数组即[2 1 4 3],舍弃右边的数组。

以2为参照数,将比2小的数放在2的左边,比2大的数放在2的右边,则数组第二次调整为:[1] 2 [4 3]。

可以看出,2为数组中的第2小的数,所以将搜索范围缩小到2的右边数组[4 3],舍弃左边的数组。

最后依此法找出第4小的数为4。

具体操作时定义两个指针 i 和 j,i 指向数组最左端依次向右扫描,j 指向数组最右端依次向左扫描,执行过程如图 1.15 所示。

图 1.15

以此类推,直到 i、j 指针重合,完成数组的第一次调整(代码实现时,i、j 指针是互换的,移动的一直是 j 指针)。再二分缩

小搜索范围,继续以上的操作直到找到第 k 小数。

参考代码如下所示:

1

2

3

4

5

6

7

//第 k 小数

#include <iostream>

#include <string.h>

#include <cstdlib>

#include <cstdio>

#include <algorithm>

using namespace std;

Page 27: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

int a[100001],b[100001];//a 用于交换,b 为原数组,用于比较

int i,j,m,k,l;

void Swap()

{

swap(a[i],a[j]);//两元素互换

swap(i,j);//指针互换

}

void Operation(int START,int END)

{

i=START;

j=END;

while(i!=j)//当指针未重合时

{

if(i<j)//如 i 指针在 j 指针左

{

if(a[i]>a[j])

Swap();

else

j--;//j 指针左移

}

else//如 i 指针在 j 指针右(指针已互换)

{

if(a[i]<a[j])

Swap();

else

j++;//j 指针右移

}

}

if(i<k)

Operation(i+1,END);//取右边数组

else if(i==k)//若已找到第 k 小数

{

for(l=1;l<=m;l++)

if(b[l]==a[i])//找到第 k 小数原来的位置

{

cout<<l<<"\n";//输出答案即原位置

break;

}

}

else

Operation(START,i-1);//取左边数组

}

int main()

{

freopen("k1.in","r",stdin);

freopen("k1.out","w",stdout);

scanf("%d%d",&m,&k);

for(i=1;i<=m;i++)

{

scanf("%d",&a[i]);

b[i]=a[i];

}

Operation(1,m);

return 0;

}

此种方法的平均时间复杂度是O(n),但是在最坏情况下,可能总是按剩下

的元素中最大的进行划分,则复杂度为O(n×n),因此如果能在线性的时间内找

到划分的基准,则可以在最坏情况下复杂度为O(n)时找到第 k小的数。

Page 28: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

如何在 O(n)下找到划分基准呢?方法如下:

1.将数组 a 五个数为一组,分成[n/5]个组。

2.对[n/5]个组的数进行组内排序,采用冒泡排序等任何方法即可。

3.选择每组的中位数,将这些中位数交换到数组的最前面,此时 a[0~(end-start)/5-1]中存的就是这些中位数。

4.对 a[0~(end-start)/5-1]个中位数进行排序,取出排序后这些中位数的中位数 x。则 x 就是需要的划分基准。

5.以 x 为基准再进行二分、比较,递归寻找即可。此种方法的最坏情况下复杂度也是 O(n)。

参考代码如下所示:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

//第 k 小数──方法二

#include <iostream>

#include <string.h>

#include <cstdlib>

#include <cstdio>

#include <algorithm>

using namespace std;

int a[100001],b[100001];

int i,j,m,k,l;

int cmp(int x,int y)

{

return x<y;

}

void Swap()

{

swap(a[i],a[j]);

swap(i,j);

}

void Operation(int START,int END)

{

i=START;

j=END;

while(i!=j)

{

if(i<j)

{

if(a[i]>a[j])

Swap();

else

j--;

}

else

{

if(a[i]<a[j])

Swap();

else

j++;

}

}

if(i<k)

Operation(i+1,END);

else if(i==k)

{

for(l=1;l<=m;l++)

if(b[l]==a[i])

{

cout<<l<<"\n";

break;

}

}

Page 29: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

else

Operation(START,i-1);

}

int main()

{

freopen("k1.in","r",stdin);

freopen("k1.out","w",stdout);

scanf("%d%d",&m,&k);

for(i=1;i<=m;i++)

{

scanf("%d",&a[i]);

b[i]=a[i];

}

for(i=6;i<=m+1;i=i+5)

sort(a+i-5,a+i,cmp);

for(i=1;i<=m/5;i++)

swap(a[i],a[i*5-2]);

sort(a+1,a+i,cmp);

swap(a[1],a[i/2]);

Operation(1,m);

return 0;

}

第 k 小数2★

【问题描述】第 k 小数 2(K2.cpp/c/pas)

“哇,好多冰淇淋啊!”张琪曼拉着李旭琳又跑到学院的冷饮店,问:“老板,这次你要第K小的魔法石?”老板笑着说:“我

要……,咦,你们事先把魔法石都排好了?那可不行,这不赖皮嘛,我要改下规则。”

老板的新规则是:对于两个有序数组 a[n]和 a[m](1<n,m<100000),找出第 k 小的数。

【输入格式】

第一行三个整数 n,m,k。

第二行是第一个有序数组的 n 个元素。

第三行是第二个有序数组的 m 个元素。

【输出格式】

第 k 小的数在数组中的位置。

【输入样例】

6 7 6

786 3891 4258 4694 7130 7899

357 720 1292 2579 7889 9255 9611

【输出样例】

3891

目测还是要用二分法啊,可是这规律应该怎么找呢?你等我拿纸笔模拟算一

下才好。

首先对数组 a[]和数组 b[]取两者的中点位置 idxA = a.lenth / 2, idxB = b.lenth / 2,LenA 为 a 数组前半段的个数,LenB 为 b 数

组前半段的个数。则分三种情况讨论:

(1)若 LenA+LenB>k,操作如图 1.16 所示。

Page 30: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

图 1.16

(2)若 LenA+LenB<k,则操作如图 1.17 所示。

图 1.17

(3)若 LenA+LenB=k,则表示答案已经找到。

参考代码如下所示:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

//第 k 小数2

#include <iostream>

#include <cstdlib>

#include <cstdio>

using namespace std;

const int sup = 100010;

int A[sup], B[sup];

bool myCMP(int a, int b)

{

return a < b;

}

int Bsearch(int s1, int e1, int s2, int e2, int kth)

{

int idxA = (s1 + e1) >> 1;//一分为二

int idxB = (s2 + e2) >> 1;

int lenA = idxA - s1 + 1;

int lenB = idxB - s2 + 1;

if(s1 > e1)

lenA = 0;

if(s2 > e2)

lenB = 0;

int Len = lenA + lenB;//两个数组中当前取出的元素个数和

//超过了要求的 k 个元素,那么根据不同情况,

//将两个数组中的一个截掉后半部分,保留前半部分

if(Len > kth)

{

//数组 A 为空或当前 B 中的中间元素更大,则第 k 大数倾向于存在 B 的前半部分

if(0!=lenB && (0==lenA || A[idxA]<=B[idxB]))//B 中还有元素

return Bsearch(s1, e1, s2, idxB - 1, kth);//截掉 B 数组中一半元素

else

return Bsearch(s1, idxA - 1, s2, e2, kth);//否则只能截取 A 中的元素

Page 31: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

}

else//两个数组中选取的元素个数不够 k 个,那么根据不同情况,

//将两个数组中的一个后移到后半部分

{

if(kth == Len)//正好相等

{

if(0 == lenA)

return B[idxB];

else if(0 == lenB)//并且其中一个是空

return A[idxA];

}

if(0 != lenA && (0 == lenB || A[idxA] <= B[idxB]))//道理同上

return Bsearch(idxA + 1, e1, s2, e2, kth-lenA);

else //已知道前 lenA 个大的,在剩下的元素中找到第 kth-lenA 大的数

return Bsearch(s1, e1, idxB + 1, e2, kth -lenB);

}

}

int main()

{

freopen("k2.in","r",stdin);

freopen("k2.out","w",stdout);

int n, m, k;

scanf("%d %d %d", &n, &m, &k);

for(int i = 0; i < n; ++ i)

cin>>A[i];

for(int i = 0; i < m; ++ i)

cin>>B[i];

sort(A, A + n, myCMP);//若已有序,则无需再排序

sort(B, B + m, myCMP);

printf("%d\n", Bsearch(0, n - 1, 0, m - 1, k));

return 0;

}

其时间复杂度为O(nlogn),同时此方法能推广到多个有序数组求第 k 小数。

第 k 小数3★

【问题描述】第 k 小数3(k3.cpp/c/pas)九度 OJ 1534

张琪曼和李旭琳又跑到冷饮店:“老板,今天又出什么题目啊?”

老板笑着说:“你们听好了,题目是这样的:给定两个升序整型数组 A 和 B。将 A 和 B 中的元素两两相加可以得到数组 C。

譬如,A 为[1,2],B 为[3,4],那么由 A 和 B 中的元素两两相加得到的数组 C 为[4,5,5,6]。现在给你数组 A 和 B,求由 A 和

B 两两相加得到的数组 C 中,第 K 小的数字是多少?”

【输入格式】

输入可能包含多个测试案例。

对于每个测试案例,输入的第一行为三个整数 m,n, k(1≤m,n≤100000, 1≤ k ≤ n ×m):n,m 代表将要输入数组 A

和 B 的长度。

紧接着两行, 分别有 m 和 n 个数, 代表数组 A 和 B 中的元素。数组元素范围为[0,1×109]。

【输出格式】

对应每个测试案例,输出由 A 和 B 中元素两两相加得到的数组 C 中第 k 小的数字。

【输入样例】

2 2 3

1 2

3 4

3 3 4

1 2 7

Page 32: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

3 4 5

【输出样例】

5

6

直接枚举 k 的话时间复杂度为O(n×m),故考虑用二分的方法,显然答案在

[a[1]+b[1],a[n]+b[m]]的区间,即下界为 a[1]+b[1],上界为 a[n]+b[m],反复

枚举上下界的中间值 mid与 k比较以缩小搜索范围即可找到答案。

但问题是怎么知道 mid与 k值比较的大小呢?

假设如图 1.18 所示,有数组 a 和数组 b,并已求出 min、max、mid 的值。

图 1.18

从 a[1]到 a[m],依次逆序与 b 数组的元素相加,若某一轮中 a[i]加到 b[j]时两数和不大于 k,则该轮中比 k 值小的元素个数为

j。下一轮 a[i+1]与 b 数组逆序相加时,直接从 b[j]开始加即可,因为很显然的,a[i+1]>a[i]。累加所有比 k 值小的元素个数即为

mid 在两数组中的排序数,如图 1.19 所示。

图 1.19

参考程序如下所示:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

//第 k 小数 3──方法1

#include <iostream>

#include <cstdlib>

#include<cstdio>

using namespace std;

typedef long long LL;

LL A[100000], B[100000];

int compare(const void * p, const void * q)

{

return *(LL *)p - *(LL *)q;

}

LL cal(LL A[],LL m,LL B[],LL n,LL mid)//计算 mid 值在两数组中的排序数

{

LL i, j;

LL cnt = 0;

j = n - 1;

for (i=0; i<m; ++i)

{

while (j>=0 && A[i]+B[j]>mid)//定位B数组中相加比 mid 小的位置

--j;

cnt += (j+1);//累计

}

return cnt;

Page 33: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

}

LL findKth(LL A[],LL m,LL B[],LL n,LL k)

{

LL min = A[0] + B[0];

LL max = A[m - 1] + B[n - 1];

LL mid;

LL ans;

while (min <= max)//二分

{

mid = ((max - min) >> 1) + min;

if (k <= cal (A, m, B, n, mid))

max = mid - 1;

else

min = mid + 1;

}

return min;

}

int main()

{

freopen("k3.in","r",stdin);

freopen("k3.out","w",stdout);

LL m, n,k,i;

while(scanf ("%lld%lld%lld", &m, &n, &k) != EOF)

{

for (i=0; i<m; ++i)

scanf ("%lld", &A[i]);

for (i=0; i<n; ++i)

scanf ("%lld", &B[i]);

qsort (A, m, sizeof(LL), compare);//如已排好序,可不用

qsort (B, n, sizeof(LL), compare);

printf ("%lld\n", findKth (A, m, B, n, k));

}

return 0;

}

其实在 cal函数里,也可以用二分查找的方式计算小于等于 k的数字个数,

这样时间复杂度可降至 n×log(m)。

参考程序如下所示:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

//第 k 小数 3

#include<cstdio>

#include<iostream>

#include<cstdlib>

using namespace std;

long long a[109999];

long long b[109999];

long long n,m;

long long cmp(long long a,long long b)

{

return a<b;

}

long long cal(long long v)

{

long long ll,rr,mid,i,add=0;

long long min,max;

Page 34: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

for(i=1;i<=n;i++)

{

min=a[i]+b[1];//上界

max=a[i]+b[m];//下界

if(v<min)

break;

if(v>=max)

{

add+=m;continue;

}

ll=1,rr=m;

while(ll<=rr)//二分

{

mid=(ll+rr)/2;

if(v<(a[i]+b[mid]))

rr=mid-1;

else

ll=mid+1;

}

if(v!=(a[i]+b[ll]))

ll--;

add+=ll;

}

return add;

}

long long find(long long ll,long long rr,long long k)

{

long long mid,i;

while(ll<=rr)

{

mid=(ll+rr)/2;

if(k<=cal(mid))

rr=mid-1;

else

ll=mid+1;

}

return ll;

}

int main()

{

freopen("k3.in","r",stdin);

freopen("k3.out","w",stdout);

long long k,ll,rr;

while(scanf("%lld%lld%lld",&n,&m,&k)!=EOF)

{

long long i;

for(i=1;i<=n;i++)

scanf("%lld",&a[i]);

for(i=1;i<=m;i++)

scanf("%lld",&b[i]);

sort(&a[1],&a[n+1],cmp);

sort(&b[1],&b[1+m],cmp);

ll=a[1]+b[1];

rr=a[n]+b[m];

printf("%lld\n",find(ll,rr,k));

}

return 0;

}

Page 35: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

拓展与练习

请完成第 k 小数 1 这道题的优化算法,并进行时间复杂度的验证。

试对多个有序数组求第 k 个小数。

对于一些极端的数据,第 k 小数3的优化代码仍存在缺陷,请尝试对

第 k 小数 3 的程序继续优化。

【问题描述】老板的又一道题(k4.cpp/c/pas)

有两个长度都为 n 的正整数序列 A 和 B,从 A 和 B 中各取其中的一个

数相加一共可以得到 n2 个和。要求输出这 n2 个和中最小的 n 个。

【输入格式】

第一行,一个正整数 n。

第二行,n 个用空格隔开的正整数,代表 A 序列。

第三行,n 个用空格隔开的正整数,代表 B 序列。

【输出格式】

一行,依次是从小到大输出 n 个最小的和,每两个数之间用一个空格

隔开。

【输入样例】

3

2 6 6

1 4 8

【输出样例】

3 6 7

【数据范围】

50%的数据:n≤500,0<Ai,Bi≤1000000000;

100%的数据:n≤100000。

剔除多余括号

【问题描述】剔除多余括号(bracket. cpp/c/pas)

《道德经》中有云:“万物之始,大道至简,衍化至繁”所以当墨老师看到一个表示算式的字符串(含四则运算、乘方、括

号)中包含有很多多余的括号时,受强迫症的影响,他就会要求你去掉多余的括号,并保持原表达式中变量和运算符的相对位置

不变,且与原表达式等价。

注意,只是要求你去括号,并没有要求你化简表达式!此外,“+”和“-”不会用作正负号。例如输入表达式:

a+(b+c)

(a*b)+c/d

a+b/(c-d)

应输出表达式:

a+b+c

a*b+c/d

a+b/(c-d)

注意:表达式以字符串输入,所有字母为小写字母,长度不超过 255。输入不需判错,输入 a+b 时不能输出 b+a,只是要求

去掉多余括号,不要对表达式化简。

Page 36: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

【输入格式】

输入文件为 bracket.in,为一行表达式。

【输出格式】

输出文件为 bracket.out,为一行去括号的表达式。

【输入样例】

a+(b+c)

【输出样例】

a+b+c

二分法★★

这题看起来涉及运算的优先级,似乎有点复杂,但分析符合“多余括号”的

情况只有三种:

(1) 括号内没有运算符;

(2) 括号左侧是加法运算符,右侧是加法或减法运算符;

(3) 括号左侧是乘法运算符,右侧是乘法或除法运算符。

首先定义运算符的优先级,一般地,将运算符“^”的优先级设为3,运算符“*”和“/”的优先级设为 2,运算符“+”和

“-”的优先级设为1。

定义一个字符串数组 s[]保存表达式,与之相对应地定义一个整型数组 a[],标记括号是否多余,如图 1.20 所示。

图 1.20

将相匹配的括号对内的表达式看作是一个整体,与括号外的最低运算符进行比较,若括号内的最低运算符优先级大于或等于

括号外的最低运算符,则此对括号可剔除。如图 1.21 所示。

图 1.21

具体操作时,找出式中最低运算符 A,将式子从A处分为左右两个子式,再对左右两个子式依此递归下去即可,如图 1.22

所示。

Page 37: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

图 1.22

设递归函数为 int cal(int begin,int end,int prev_min),其中 begin 表示表达式的起始处,end 表示表达式末尾,prev_min 表

示递归前式子的最低运算符级别,则对表达式的递归过程如图 1.23 所示。

图 1.23

参考程序如下所示:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

//剔除多余括号

#include <iostream>

#include <cstdio>

#include <cstdlib>

#include <cstring>

using namespace std;

int a[1024];//a,s 两数组元素一一对应,a[i]标记 s[i]是否多余

char s[1024];

int cal(int begin, int end, int prev_min)

{

int t, min=4, min_i;

for (int i=begin; i<=end; i++)//扫描整个字符串,找出最低运算符

{

switch (s[i])

{

case '^':

if (min>3)

min=3, min_i=i;//标记"^"运算符优先级为 3

break;

case '*':

case '/':

if (min>2)

min=2, min_i=i;//标记"*"和"/"运算符优先级为 2

break;

case '+':

Page 38: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

case '-':

if (min>1)

min=1, min_i=i;//标记"+"和"-"运算符优先级为 1

break;

case '('://遇到左括号,则找到与之匹配的右括号,并跳过括号内的字符

i++;

for (t=1;t!=0;i++)

{

if (s[i]=='(') //对括号内多重左括号和右括号的处理

t++;

if (s[i]==')')

t--;

}

i--;

break;

};

}

if (min==4)//去括号操作

{

if (s[begin]=='(' && s[end]==')')//如果首尾即为括号

{

t=cal(begin+1,end-1,0);//求出除去首尾括号继续递归的返回值 min

if (t>=prev_min)

{

a[begin]=a[end]=1;//将首尾的括号标记为多余

return t;

}

}

return 4;

}

cal(begin,min_i-1,min);//递归最低运算符前端的式子

if (s[min_i]=='+' || s[min_i]=='*')

cal(min_i+1,end,min);//递归最低运算符后端的式子

else

cal(min_i+1,end,min+1);//递归最低去处符后端的式子,但运算优先级+1

return min;

}

int main()

{

cin>>s;

int sc=strlen(s);

cal(0,sc-1,0);

for (int i=0;i<sc;i++)

if (!a[i])//当前位标记为 0,则输出

cout<<s[i];

cout<<'\n';

//system("pause");

return 0;

}

非二分法★

其思想类似二分法,即首先从右向左扫描,扫描到的第一个左括号肯定为最内层的左括号,由此找到相匹配的右括号后判断

括号内的最低运算符的优先级。则括号删除规则如下:

(1)如果括号的左边为“/”,则括号不能剔除;

(2)如果括号左边为“*”或“-”且括号内最低运算符为“+”或“-”,则括号不能剔除;

(3)如果右边为“/”或“ *”, 且最低运算级为“+”或“-” ,则括号不能剔除。

(4)跳到外一层括号对(如果有的话),继续上述操作直到结束。

Page 39: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

参考程序如下所示:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

//剔除多余括号──非二分法

#include <iostream>

#include <cstdio>

#include <cstring>

using namespace std;

char v;

const int MAXN=10010;

char s[MAXN];

bool vis[MAXN];

int l,k,x,a,b,z,s1,s2,s3,y;

int Right;

void op(int k)//判断的比配括号中的最低级的运输符

{

for(int i=k;i<=Right;i++)

{

v=s[i];

if(v == '-' || v == '+')

return;

}

}

void FirstLeft()//从右向左查找到第一个左括号

{

for(int j=l-1;j>=0;j--)

if(s[j] == '(')

{

s2=j;

break;

}

}

int FindRight(int Left)//寻找一个括号的匹配括号

{

int i;

int ll;

int count=1;

for(i=Left+1;i<l;++i)

{

if(i==l-1)

ll=i;

if(!count)//找到了匹配的右括号,则退出

{

ll=i-1;

break;

}

else

if(s[i] == '(')

count++;

else if(s[i] == ')')

count--;

}

return ll;

}

void desion()//判断最内层括号是否可删除

{

for(int i=s2;i>=0;i--)//从最中间的括号开始向左边

{

Page 40: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

if(s[i]=='(')

{

Right=FindRight(i);//找到相匹配的右括号

op(i);//找到最低级的运算符

if(i==0)//特判如果第一个字符串就为括号

{

vis[i]=true;//左括号可以删除

vis[Right]=true;//右括号可以删除

}

if(s[i-1]=='/')//若左括号前为/,则不可删除

continue;

else if(s[i-1]=='*' || s[i-1]=='-' && v=='+' || v=='-')

continue;//若左括号前为*,-,且括号内最低运算符为+,-则不可删除

else if(s[Right+1]=='/' || s[Right+1]=='*' && v=='+' || v=='-')

continue;//若右括号为/或*,且括号内最低运算符为+,- 则不可删除

else//将匹配的括号 bool 改为真

{

vis[i]=true;

vis[Right]=true;

}

}

}

}

int main()

{

freopen("bracket.in","r",stdin);

freopen("bracket.out","w",stdout);

scanf("%s",s);

l=strlen(s);

FirstLeft();

desion();

for(int i=0;i<l;i++)

if(!vis[i])//如果不为真输出

printf("%c",s[i]);

cout<<endl;

return 0;

}

有没有发现该程序有一点小错误,从而导致有些测试点无法通过?请尝试修

改程序错误并通过所有测试点。

聪明的质检员

【问题描述】聪明的质检员(qc.cpp/c/pas)NOIP 2011 提高组 day2

“真正愚蠢的人总是以为自己很聪明。”这句古谚语其实讲述的是魔法世界历史上曾经存在的一个古老而强大的帝国最终覆

灭的历史故事。这个帝国的每个人都很“聪明”,比如说质检员检查一批魔法石的质量,这批魔法石共有 n 块矿石,从 1 到 n 逐

一编号,每个矿石都有自己的重量 wi 以及价值 vi 。检验矿石的流程是:

(1)给定 m 个区间[Li ,Ri];

(2)选出一个参数 W;

(3)对于一个区间[Li ,Ri],计算矿石在这个区间上的检验值 Yi:

Yi= j

jj

v*1 ,j∈[Li,Ri]且 wj≥W, j 是矿石编号。

这批矿石的检验结果 Y 为各个区间的检验值之和,即

m

1iiyY

Page 41: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

若这批矿石的检验结果与所给标准值 S 相差太多,就需要再去检验另一批矿石。你知道的,质检员不想浪费时间去检验另

一批矿石,所以他想通过调整参数 W 的值,让检验结果尽可能地靠近标准值 S,即使得 S-Y 的绝对值最小。请你帮忙求出这个

最小值。

【输入格式】

第一行包含三个整数 n ,m,S,分别表示矿石的个数、区间的个数和标准值。 接下来的 n 行,每行 2 个整数,中间用空

格隔开,第 i+1 行表示 i 号矿石的重量 wi 和价值 vi 。

接下来的 m 行,表示区间,每行 2 个整数,中间用空格隔开,第 i+n+1 行表示区间[Li, Ri]的两个端点 Li 和 Ri 。注意:不同

区间可能重合或相互重叠。

【输出格式】

输出只有一行,包含一个整数,表示所求的最小值。

【输入样例】

5 3 15

1 5

2 5

3 5

4 5

5 5

1 5

2 4

3 3

【输出样例】

10

【样例说明】

当 W 选 4 的时候,三个区间上检验值分别为 20、5 、0 ,这批矿的检验结果为 25,此时与标准值 S 相差最小为 10。

【数据规模】

对于 10%的数据,有 1≤n,m≤10;

对于 30%的数据,有 1≤n,m≤500;

对于 50%的数据,有 1≤n,m≤5000;

对于 70%的数据,有 1≤n,m≤10000;

对于 100%的数据,有 1≤n,m≤200000,0 < wi, vi≤106,0 < S≤1012,1≤Li≤Ri≤n。

二分法+前序和★★

我先解释一下:∑的英语名称为 Sigma,是求和符号,例如

100

1i

i=1+

2+3+…+98+99+100。根据样例,由于W=4,故区间(1,5)≥4的矿石只有

4,5,故Y1=(1+1)×(5+5)=20。区间(2,4)≥4 的矿石只有 4,故 Y2

=1×5=5,区间(3,3)≥4的矿石一个没有,故 Y3=0。

观察Yi 的式子,显然,随着 W 的增大,符合条件的矿石减少,Yi和 Y 都会减小。所以 Y 是一个随 W 变化而变化的单调函数,

也就是说 Y 具有二分性质。利用二分查找,上界是 0,下界是 max{w}(重量数组中的最大值)就可以找到最接近 S 的 Y。

此外,由于区间很多,计算时需要用前序和算法求。即定义 sum 数组,sum[i]表示从 1,2,…,i 有几个重量大于 s 的个数;

定义 sumv 数组,sumv[i]表示从 1,2,…,i 重量大于 s 价值的价值总和。

那么从 y 矿石到 x 矿石的检验值就为:(sum[y]-sum[x-1]) ×(sumv[y]-sumv[x-1])。

参考代码如下所示:

1 //聪明的质检员

Page 42: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

#include <iostream>

#include <cstdio>

#include <cstdlib>

using namespace std;

typedef long long LL;

LL n,m,s;

int value[200005],weight[200005];

int ST[200005],END[200005];//保存区间

LL sum[200005],sumv[200005];

void work(LL S)

{

sum[0]=sumv[0]=0ll;//long long 型后面必须要加 ll

for(int i=1;i<=n;i++)

if(weight[i]>=S)

{

sum[i]=sum[i-1]+1;//前序和计算

sumv[i]=sumv[i-1]+value[i];

}

else

sumv[i]=sumv[i-1],sum[i]=sum[i-1];

}

LL calc(LL MID)//计算Y值

{

LL checkans=0ll;

for(int i=1;i<=m;i++)

checkans+=(sum[END[i]]-sum[ST[i]-1])*(sumv[END[i]]-sumv[ST[i]-1]);

return checkans;

}

LL myabs(LL num)//取 num 的绝对值

{

if(num>0)

return num;

else

return -num;

}

LL max(LL a,LL b)

{

if(a>b)

return a;

else

return b;

}

int main()

{

freopen("qc.in","r",stdin);

freopen("qc.out","w",stdout);

LL start=0ll,end=0ll;//上界和下界

cin>>n>>m>>s;

for(int i=1;i<=n;i++)

{

scanf("%d %d",&weight[i],&value[i]);

end=max(weight[i],end);//找出最重的

}

for(int i=1;i<=m;i++)

scanf("%d %d",&ST[i],&END[i]);

LL ans=9223372036854775807ll;

while(start<=end)//二分查找

{

LL mid=(start+end)/2;

Page 43: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

67

68

69

70

71

72

73

74

75

76

77

78

79

80

work(mid);

LL t=calc(mid);

if(myabs(t-s)<ans)

ans=myabs(t-s);

if(ans==0)

break;

if(s<t)

start=mid+1;

else

end=mid-1;

}

cout<<ans<<endl;

return 0;

}

拓展与练习

【问题描述】电脑组装(Assemble.cpp/c/pas) POJ 3497

魔法世界的魔法师们近来讨论话题最多的无疑是最新型量子计算机的

上市,量子计算机运算速度惊人,例如求解一个亿亿亿级变量的方程组,

即便是用世界上最快的超级计算机也至少需要几百年。而量子计算机十秒

钟就可解决。所以对于一直信奉“工欲善其事,必先利其器。”的张琪曼来

说,购买一台最新型量子计算机是她的近期目标。但是由于量子计算机价

格昂贵,她只能用一定的预算去买各种量子计算机的组件,组件每种买一

个,其中电脑组件都有品质和价格两个参数。

求在不超过预算的情况下,能买到的所有组件的最差品质的最大值是

多少。

【输入格式】

第一行为一个整数N,表示测试组数,N不超过 100。

每组数据第一行有两个数,即组件数和预算,其中 1≤组件数≤1 000,

1≤预算≤1 000 000 000。

以下各行为组件的类型、名称、价格、质量。

【输出格式】

能买到的所有组件的最差品质的最大值。每组测试数据为一行。

【输入样例】

1 (表示测试组数,不超过 100 组)

18 800 (表示 1≤组件数≤1 000, 1≤预算≤1 000 000 000)

processor 3500_MHz 66 5 (类型,名称,价格,质量)

processor 4200_MHz 103 7

processor 5000_MHz 156 9

processor 6000_MHz 219 12

memory 1_GB 35 3

memory 2_GB 88 6

memory 4_GB 170 12

mainbord all_onboard 52 10

harddisk 250_GB 54 10

harddisk 500_FB 99 12

casing midi 36 10

monitor 17_inch 157 5

monitor 19_inch 175 7

monitor 20_inch 210 9

Page 44: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

monitor 22_inch 293 12

mouse cordless_optical 18 12

mouse microsoft 30 9

keyboard office 4 10

【输出样例】

9

最接近点对问题

【问题描述】最接近点对问题(nearest.cpp/c/pas)HDU 1007

楚继光和李旭琳一人骑着一辆自行车迎面而来,楚继光大叫:“小心,小心,你往左拐,我往右拐!”

呵呵,幸好他们骑的是自行车。更进一步,考虑在空中交通控制中,若将飞机作为空间移动的一个点来看待,则具有最大碰

撞危险的 2 架飞机,就是这个空间中最接近的一对点。这类问题是计算几何学中研究的基本问题之一。下面我们着重考虑平面上

的最接近点对问题。最接近点对问题的提法是:给定平面上 n 个点,找其中的一对点,使得在 n 个点的所有点对中,该点对的距

离最小。严格地说,最接近点对可能多于 1 对。为了简单起见,这里只限于找其中的一对。

【输入格式】

输入文件为 nearest.in,第一行为点的个数 n,且 2≤n≤60000;接下来 n 行:每行两个实数:x y,表示一个点的行坐标和列

坐标,中间用一个空格隔开。

【输出格式】

输出文件为 nearest.out,仅一行,为一个实数,表示最短距离的一半,精确到小数点后面2位。

【输入样例】

3

1 1

1 2

2 2

【输出样例】

0.50

一维算法★★

显然,n个点的集合 Q中共有2nc 个点对。最原始的算法是穷举所有点对,这

种算法时间复杂度太高。为了提高时效,我们使用分治算法。

先来考虑一维的情形,将一维的 n 个点的排序映射到X轴上设为集合 S,用各点坐标的中位数 m 将 S 划分为 2 个子集 S1 和

S2 ,每个子集中约有 n/2 个点,对于所有 p∈S1 和 q∈S2 有 p<q。如图 1.24 所示。

图 1.24

通过递归算法可求出 S1 的最接近点对是{p1,p2},S2 的最接近点对是{q1,q2},那么 S 的最接近点对或者是{p1,p2},或者

Page 45: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

是{q1,q2},或者是{p3,q3},其中 p3∈S1 且 q3∈S2。

设 d=min{|p1-p2|,|q1-q2|},如果 S 的最接近点对是{p3,q3},即|p3-q3|小于 d,则 p3 和 q3 两者与 m 的距离不超过 d,

即 p3∈(m-d,m],q3∈(m,m+d]。由于在 S1 中,每个长度为 d 的半闭区间至多包含一个点(否则必有两点距离小于 d),并且

m 是 S1 和 S2 的分割点,因此(m-d,m]中至多包含 S 中的一个点。由图可以看出,如果(m-d,m]中有 S 中的点,则此点就是

S1 中最大点。因此,我们用线性时间就能找到区间(m-d,m]和(m,m+d]中所有点,即 p3 和 q3。从而我们用线性时间就可以将

S1 的解和 S2 的解合并成为 S 的解。

参考程序如下所示:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

//一维最接近点对

#include <iostream>

#include <cstdio>

#include <cstdlib>

#define M 20000

using namespace std;

struct cpair//表示具有最近距离的点对(d1,d2)的距离 dist

{

float dist;

float d1,d2;

};

int input(float s[],int n)//s[]为一维点集,n 为 s[]中的元素个数

{

cout<<"输入一维点集的各元素(以-1 结束):\n";

n=0;

cin>>s[n];

while(s[n]!=-1)

{

n++;

cin>>s[n];

}

return n;

}

float max(float s[],int b,int e)//返回 s[b]到 s[e]中的最大值

{

float m1=s[b];

for(int i=b+1;i<=e;i++)

if(m1<s[i])

m1=s[i];

return m1;

}

float min(float s[],int b,int e)//返回 s[b]到 s[e]中的最小值

{

float m1=s[b];

for(int i=b+1;i<=e;i++)

if(m1>s[i]) m1=s[i];

return m1;

}

//返回 s[]中的具有最近距离的点对及其距离

cpair cpair1(float s[],int n)

{

cpair temp={1000,0,0};

if(n<2) //当点集中元素的个数不足 2 时,返回具有无穷大的 dist 值 1000

return temp;

float m1=max(s,0,n-1),m2=min(s,0,n-1);//获取最大值,最小值

float m=(m1+m2)/2;//找出点集中的中位数

int j=0,k=0;

Page 46: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

float s1[M],s2[M];

for(int i=0;i<n;i++)//将点集中的各元素按与 m 的大小关系分为 s1 和 s2 组

{

if(s[i]<=m)

{

s1[j]=s[i];

j++;

}

else

{

s2[k]=s[i];

k++;

}

}

cpair d1=cpair1(s1,j),d2=cpair1(s2,k);//递归

float p=max(s1,0,j-1),q=min(s2,0,k-1);

//返回 s[]中的具有最近距离的点对及其距离

if(d1.dist<d2.dist)

{

if((q-p)<d1.dist)

{

temp.dist=(q-p);

temp.d1=q;

temp.d2=p;

return temp;

}

else

return d1;

}

else

{

if((q-p)<d2.dist)

{

temp.dist=(q-p);

temp.d1=q;

temp.d2=p;

return temp;

}

else

return d2;

}

}

int main()

{

int n,m;

float s[M];

cpair dist;

m=input(s,n);//获得点的个数

dist=cpair1(s,m);

cout<<"\n 该一维点集中最近点对为("<<dist.d1<<","<<dist.d2<<")";

cout<<"其距离为"<<dist.dist<<endl;

system("pause");

}

请自己制作一些测试数据检验程序的正确性。

Page 47: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

二维算法★★

下面来考虑二维的情形,如图 1.25 所示。

图 1.25

我明白了,这和一维算法相类似,即选取一垂直线 l来作为分割直线将 S平

均分割为 S1和 S2。递归地在 S1和 S2上找出其最小距离 d1和 d2,并设 d=min{d1,

d2},S中的最接近点对或者是 d,或者是某个{p,q},其中 p∈P1且 q∈P2。那么,

怎么计算{p,q}的最接近点对呢?

计算{p,q}的最接近点对如图 1.26 所示。

图 1.26

P1 中任意一点 p,它若与 P2 中的点 q 构成最接近点对的候选者,则必有 (p,q)的距离小于 d。满足这个条件的 P2中的点一

定落在一个 d×2d 的灰色矩形 R 中。由 d 的意义和抽屉原理可知,P2 中任何 2 个 S 中的点的距离都不小于 d。由此可以推出矩形

R 中最多只有 6 个 S 中的点(并且6个点必在图中*号位置)。因此,在分治法的合并步骤中最多只需要检查 6×n/2=3n 个候选者。

为了确切地知道要检查哪 6 个点,可以将 p 和 P2 中所有 S2 的点投影到垂直线 l 上,由于能与 p 点一起构成最接近点对候选

者的 S2 中的点一定在矩形 R 中,所以它们在直线 l 上的投影点距 p 在 l 上投影点的距离小于 d。由上面的分析可知,这种投影点

最多只有 6 个。因此,若将 P1 和 P2 中所有 S 中的点按其 y 坐标排好序,则对 P1 中所有点,对排好序的点列作一次扫描,就可以

找出所有最接近点对的候选者。对 P1 中每一点最多只要检查 P2 中排好序的相继 6 个点。

为提高算法效率,可在算法中采用预排序技术,即在使用分治法之前,预先

将 S中的 n个点依其 x坐标排序好。程序的时间复杂度为 O(nlogn)。

参考代码如下所示:

1 //最接近点对

Page 48: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

#include <iostream>

#include <cstdlib>

#include<cstdio>

#include <cmath>

using namespace std ;

const int maxn = 1000001 ;

const int INF = 1000000001 ;

int n,temp[maxn];

struct Point

{

double x , y ;

}S[ maxn ] ;

bool cmp(const Point &a , const Point &b )

{

if( a.x == b.x )

return a.y < b.y ;

else

return a.x < b.x ;

}

bool cmpy( const int& a , const int& b )

{

return S[ a ].y < S[ b ].y ;

}

double min( double a , double b )

{

return a < b ? a : b ;

}

double dist( int i , int j )

{

double x=(S[i].x-S[j].x)*(S[i].x-S[j].x);

double y=(S[i].y-S[j].y)*(S[i].y-S[j].y);

return sqrt(x+y);

}

double merge( int left , int right )

{

double d = INF ;

if( left == right )//若左右边界重合

return d ;

if( left + 1 == right )//若左右边界差值为1即只有两个点

return dist( left , right ) ;

int mid = ( left + right ) >> 1 ;//从中间划分

double d1 = merge( left , mid ) ;

double d2 = merge( mid + 1 , right ) ;

d = min( d1 , d2 ) ;//递归求出最小值 d

int i , j , k = 0 ;

for( i = left ; i <= right ; ++i )//找出 d 范围内的点存入 temp[]

if( fabs( S[ mid ].x - S[ i ].x ) <= d )

temp[ k++ ] = i ;

sort( temp , temp + k , cmpy );//按 y 坐标排序

for(i=0;i<k;++i)//穷举存入 temp[]内的这些点

for(j=i+1;j<k && S[temp[j]].y-S[temp[i]].y<d;++j)//找出最近(p,q)

{

double d3 = dist( temp[ i ] , temp[ j ] ) ;

if( d > d3 )

d = d3 ;

}

return d ;

Page 49: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

67

68

69

70

71

72

73

74

75

76

77

78

79

}

int main()

{

freopen("nearest.in","r",stdin);

freopen("nearest.out","w",stdout);

scanf( "%d" , &n);

for(int i = 0 ; i < n ; ++i )

scanf( "%lf%lf" , &S[ i ].x , &S[ i ].y ) ;

sort( S , S + n , cmp );//点按 x 坐标由小到大排序

printf( "%.2lf\n" , merge( 0 , n - 1 )/2);

return 0 ;

}

拓展与练习

最接近点对问题可以扩展到三维,感兴趣的读者可以查询相关的资料。

第一部试读章节

第二章 基本结构

这个世界以及万物的本源是什么,是一代代魔法师一生苦苦思索的事情,但对于程序来说就简单得多了,所谓“万变不离其

宗”,C++的任何一个程序其实都可以分解为以下三种基本结构:顺序结构、选择结构和循环结构。

顺序结构

顺序结构如同自然语言中的文章一样,按照事件的发展顺序,依次自上而下书写需要的程序语句,并以语句出现的顺序来执

行。

【例题描述】魔法石检验

魔法的能量来源是从魔法石中提取的,如图 2.1所示。每一块魔法石的价值由一个三位整数表示,其中百位数表示等级,十

位数表示类别,个位数表示纯净度。现已知一个魔法石的三位数表示,试分别输出该魔法石的等级、类别、纯净度。

图 2.1

题目本质是分解一个三位整数,而取余运算可以得到数据的各个位数。因此

对实际分解过程进行模拟,即不断取余,依次得到数据的个位数。例如分解数据

398,采用的方案为:

(1)取其个位:398%10=8

Page 50: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

(2)取其十位:398/10%10=9

(3)取其百位:398/100=3

参考程序如下所示: 1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

//魔法石检验

#include <iostream>

using namespace std;

int main()

{

int num;//先定义变量

int hundred,ten,indiv;

cout<<"请输入一个三位数";

cin>>num;//输入一个三位数

hundred=num/100;//分解百位数

ten=num/10%10;//分解十位数

indiv=num%10;//分解个位数

cout<<"百位数为"<<hundred<<endl;//输出

cout<<"十位数为"<<ten<<endl;

cout<<"个位数为"<<indiv<<endl;

system("pause");

return 0;

}

【例题描述】小数的四舍五入

魔法世界用于魔法石能量检验的仪器可以精确到小数点后 7位,但一般使用时只需对小数点后第三位四舍五入就可以了,现

输入一个实数,实现小数点后第三位的四舍五入,例如输入 1.235,输出 1.24。

设输入的实数为 x,假设要操作的数据 x=2.3567,如果希望保留两位小数从

第三位实现四舍五入,那么执行 x=(int)(x*100+0.5),即 2.3567×100+0.5 来

实现第三位的进位。

要想保留两位小数,则取整以后进行整除即 x/=100即可。

参考代码如下所示: 1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

//小数的四舍五入

#include <iostream>

#include <iomanip>

using namespace std;

int main()

{

double x;

cout<<"请输入一个双精度数";

cin>>x;

x=(int)(x*100+0.5);

x/=100;

cout<<setprecision(2)<<fixed<<x<<endl;

system("pause");

return 0;

}

【上机实践】求三角形面积

如图 2.2 所示,三角形魔法阵是魔法师公认的最强大的阵形之一,因为三角形有着稳固、坚定、耐压的特点。现输入三角形

的三边长,求三角形面积(假设输入的 a,b,c能构成三角形)。

Page 51: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

图 2.2

已知三角形三条边的长度 a,b,c,求三角形面积 S,可以使用海伦公式,

海伦公式是这样的:

设 p=2

cba ,则 S c)b)(pa)(pp(pABC△ 。

求一个数的平方根可以使用 sqrt函数,例如 sqrt(16)的结果是 4。使用 sqrt

函数需在程序开头写#include <math.h>,即包含数学头文件。

核心伪代码为: 1

2

3

4

5

注意要包含数学头文件

定义浮点数 a,b,c,p,area

输入 a、b、c 的值

计算 p 的值

计算面积值 area

输出面积值 area

注 意 , 计 算 p 的 值 不 能 这 样 写 : p=1/2*(a+b+c), 而 应 该 写 成

p=1.0/2*(a+b+c),或者 p=(a+b+c)/2。为什么要用 1.0而不是 1呢,这是因为如

果是 1/2,系统将会视之为两个整型数据相除,得到的值也是一个整型数,即 1/2

的结果舍弃小数部分后结果为 0,而 0乘以任何数也为 0。

这种错误一般都非常隐蔽,在编程中要特别小心。

【上机实践】求一元二次方程的根

含有一个未知数,且未知数的最高次数是 2 的整式方程叫做一元二次方程,其一般形式为 ax2+bx+c=0(a≠0)。如图 2.3 所

示,魔法学院图书馆珍藏着据说是上古时代古巴比伦人的《泥板文书》的魔法古籍中,就记载有求解一元二次方程根的解法。现

已知一元二次方程 ax2+bx+c=0,其中 a,b,c 由键盘输入,试求出方程的根。为了简便起见,假设输入的 a,b,c 三个值后 b

2

-4ac≥0。例如当程序运行时输入:1 6 5,输出结果为:x1=-1 x2=-5。

图 2.3

此题可以使用求一元二次方程式的求根公式a2

ac4bbx

2 ,

即a2

ac4b

a2

bx

2

设 p a2

b ,q

a2

ac4b2

Page 52: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

则 x1=p+q,x2=p-q

核心伪代码为: 1

2

3

4

5

6

定义 a,b,c,disc,p,q

输入 a,b,c 的值

计算 b*b-4*a*c 值给 disc

计算 p 的值

计算 q 的值

输出 x1、x2 的值

选择结构

选择结构用于判断给定的条件,根据判断的结果来控制程序的流程。

要使用选择结构,我们先来学习关系运算。所谓关系运算,实际上就

是“比较运算”。将两个值进行比较,判断其比较的结果是否符合给定的条

件。例如 a>4是一个关系表达式,大于号是一个关系运算符,如果满足 a>4

的条件下,则该关系表达式的值为“真”,否则该关系表达式的值为“假”。

C++语言提供 6种关系运算符,如表 2.1所示。

表 2.1

符号 含义 优先级

< 小于 6

<= 小于或等于 6

> 大于 6

>= 大于或等于 6

== 等于 7

!= 不等于 7

if语句是用来判定所给定的条件是否满足,根据结果的真或假决定执行给出的两种操作之一。

C++语言提供了三种形式的 if语句:

1.if(表达式成立)

执行语句

2.if(表达式成立)

执行语句 1

else

执行语句 2

3.if(表达式 1成立)

执行语句 1

else if(表达式 2成立)

执行语句 2

else if(表达式 3成立)

执行语句 3

……

else if(表达式 m成立)

执行语句 m

else

执行语句 n

Page 53: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

这个很容易理解,我可以举生活中的例子如下:

1. if(天气好)

我就上街逛逛;

2. if(天气好)

我就上街逛逛;

else

我就待在家;

3. if(我至少有五百块钱)

我可以买五件货;

else if(我至少有四百块钱)

我可以买四件货;

else if(我至少有三百块钱)

我可以买三件货;

else if(我至少有两百块钱)

我可以买两件货;

else if(我至少有一百块钱)

我可以买一件货;

else

回家吧,什么都不说了,一说都是泪啊;

【例题描述】求绝对值

魔法师使用的能量除了正能量以外,还有一种神秘的暗能量,暗能量是一种不可见的、能推动宇宙运动的能量。如图 2.4

所示,与正能量级别相对应的,魔法师将暗能量的级别以负数表示,现已知某种能量的级别,试用 if 语句显示该能量级别的绝

对值。

图 2.4

我是这样写的:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

//求绝对值 1

# include <iostream>

using namespace std;

int main()

{

int x;

cin>>x;

if (x<0)

x=-x;

cout<<x<<endl;

system("pause");

return 0;

}

我是这样写的:

1

2

3

//求绝对值 2

# include <iostream>

using namespace std;

Page 54: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

4

5

6

7

8

9

10

11

12

13

14

15

int main()

{

int x;

cin>>x;

if (x<0)

cout<<-x<<endl;

else

cout<<x<<endl;

system("pause");

return 0;

}

在 if 和 else后面只含一个内嵌的操作语句(如上例),如果有多个操作语句,就必须要用花括号“{ }”将几个语句括起

来成为一个复合语句。如:

if(x>y)

{

number=0; //语句 1

cout<<x<<endl; //语句 2

}

else

{

number=1; //语句 1

cout<<y<<endl; //语句 2

}

我明白了,我举一个生活中的例子:

if(天气好)

{

我就购物;

我就看电影;

我就吃冰淇淋;

} else {

我就在家看书;

我就在家看动画片; }

【例题描述】两实数排序

输入两个实数 a和 b,按代数值由小到大的次序输出这两个数,例如输入 3 2,输出 2 3,输入 2 3,输出仍是 2 3。注意只

允许使用 cout<<a<<' '<<b这样的语句,而不允许使用诸如 cout<<b<<' '<<a这样取巧的语句。

根据题目要求,显然当 a>b 的时候,a和 b的值要互换,但 a和 b的值不能

直接互换,而要通过一个中间变量,例如 t 互换 a,b 的值。可以这样想象:假

设有一瓶酱油和一瓶醋,如果要把酱油装入醋瓶,醋装入酱油瓶,就必须要有一

个空瓶子作为临时盛放的容器。中间变量 t就相当于这个空瓶子。

参考程序如下所示: 1

2

3

4

5

6

7

8

9

10

11

//两实数排序

# include <iostream>

using namespace std;

int main()

{

float a,b,t;

cin>>a>>b;;

if(a>b)

{t=a;a=b;b=t;}

cout<<a<<" "<<b<<endl;

Page 55: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

12

13

14

system("pause");

return 0;

}

【上机实践】三实数排序

输入 3个数 a、b、c,要求模拟两实数排序的程序按由小到大的顺序输出。

伪代码如下所示:

1

2

3

4

5

6

7

8

9

定义浮点数 a,b,c,t

输入 a,b,c

如果(a>b)

a,b 两值交换

如果(a>c)

a,c 两值交换

如果(b>c)

b,c 两值交换

输出 a,b,c 的值

注意该程序中三个变量的比较过程,即先比较 a是否大于 b,再比较 a是否

大于 c,最后比较 b 是否大于 c。想想看,如果改变比较顺序,结果是否仍然正

确?

【上机实践】等级制

魔法学院的考试采用等级制,即将百分制转化为 A、B、C、D、E 五个等级,设成绩为 X,则 X≥90 为 A 等,X≥80为B等,

X≥70为 C 等,X≥60为 D等,否则为 E等。试编写一个程序,将输入的分数转换成 A、B、C、D和 E五个等级。

伪代码如下所示:

1

2

3

4

5

6

7

8

9

10

11

12

定义分数为浮点数 x

输入 x 的值

如果(x>=90)

输出"A"

否则如果(x>=80)

输出"B"

否则如果(x>=70)

输出"C"

否则如果(x>=60)

输出"D"

否则

输出"E"

C++有一个可替代 if~else 语句的操作符,这个操作符被称为三目运算符(? :),它是 C++中唯一一个需要 3 个操作数的操作符。

如 a>b?c:d;此句的含义是:a 大于 b 吗?如果是,则取 c 的值,否则取 d 的值。

例如下面的语句:

if(a>b)

Max=a;

else

Max=b;

可以写成 Max=(a>b)?a:b;

【例题描述】找最大值

编程输出两个整数的最大值。

1

2

3

4

5

//求最大值

# include <iostream>

using namespace std;

int main()

Page 56: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

6

7

8

9

10

11

12

13

{

int x,y;

cin>>x>>y;

int c=x>y?x:y;

cout<<c<<endl;

system("pause");

return 0;

}

分析以下程序的执行错误:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

//输出星期几

#include <iostream>

using namespace std;

int main()

{

int n;

cin>>n;

if(n=1)

cout<<"星期一"<<endl;

if(n=2)

cout<<"星期二"<<endl;

if(n=3)

cout<<"星期三"<<endl;

if(n=4)

cout<<"星期四"<<endl;

if(n=5)

cout<<"星期五"<<endl;

if(n=6)

cout<<"星期六"<<endl;

if(n=7)

cout<<"星期日"<<endl;

system("pause");

return 0;

}

我找到了,对于 n=1来说,它的意思是将 1赋给 n,所以,这是一条赋值语

句,整个表达式返回值为 1,表示 true,所以输出星期一,而对于其他的 if 语

句也同样执行。改正方法是将所有 if条件表达式中的“=”改为“==”。

在 if 语句中又包含一个或多个 if语句称为 if语句的嵌套,一般形式如下:

if( )

if( ) 语句 1

else 语句 2

else

if( ) 语句 3

else 语句 4

应当注意 if与 else的配对关系。else总是与它上面的最近的未配对的 if配对。就好像穿衣服一样,总是一层套一层的。

类似的生活中的例子有:

if(天气好)

if(是周末)

我就逛街;

else

我就看电影;

else

//内嵌

//内嵌

Page 57: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

if(是周末)

我就看电视;

else

我就学习

【上机实践】完整的解一元二次方程

完整解方程 ax2+bx+c=0的解实际上应该有以下几种可能:

(1)a=0,不是二次方程。

(2)b2-4ac=0,有两个相等实根。

(3)b2-4ac >0,有两个不等实根。

(4)b2-4ac <0,没有实根(注意:引入虚数概念后,应该有两个共轭复根)。

这题要注意的是:因为 a是实数,而实数在计算和存储时会有一些微小的误

差,因此本来是零的值,由于误差原因而判别为非零的值。所以不能通过 (a==0)

的方式判断 a是否等于 0。我们采取的办法是判断 a的绝对值是否小于一个很小

的数(例如 0.000001)。

fabs函数为求一个 double 类型的绝对值,例如 fabs(-3)=3。使用 fabs函

数,程序头需引用 math.h 头文件。顺便提一下,abs 函数为求一个整数变量的

绝对值。

伪代码如下所示:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

定义浮点数 a,b,c,disc,x1,x2

输入 a,b,c 的值

如果(fabs(a)<=1e-6)//1e-6 为科学计数法,即1×10-6

输出“不是一元二次方程”

否则

{

设 disc=b*b-4ac

如果(disc==0)//注意实际代码的括号内应写成(fabs(disc)<=0.000001)

输出两个相同的实根

否则如果(disc>0)//此处的代码应该怎么写?

输出两不同的实根

否则

输出“无实根”

}

【例题描述】判断闰年

闰年(Leap Year)是为了弥补因人为历法规定造成的年度天数与地球实际公转周期的时间差而设立的。因为地球绕太阳运行

周期为 365天 5小时 48分 46秒(合 365.24219天)即一回归年(tropical year)。公历的平年只有 365 日,比回归年短约 0.2422

日,所余下的时间约为四年累计一天,故四年于 2月加 1天,使当年的历年长度为 366日,这一年就为闰年。现行公历中每 400

年有 97个闰年。

闰年的条件是符合下面二者之一:(1)能被 4 整除,但不能被 100 整除。(2)能被 4整除,又能被 400 整除。请编程判断

某一年是否是闰年。

这个程序看上去很简单么,只要进行几次选择判断就可以了,这是我写的程

序,你能写一个和我这个程序不一样的代码吗?

1

2

3

4

5

6

7

8

//判断闰年 1

#include <iostream>

using namespace std;

int main()

{

int year,leap;

cin>>year;

Page 58: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

9

10

11

12

13

14

15

16

17

18

19

20

21

22

if(year%400==0)

cout<<"是闰年";

else if(year%100!=0)

{

if (year%4==0)

cout<<"是闰年";

else

cout<<"不是闰年";

}

else

cout<<"不是闰年";

system("pause");

return 0;

}

学过下面要讲的逻辑运算符后,这道题也可以只用一个逻辑表达式来表示:

(year%4==0 && year%100!=0) || year%400==0

或者加一个“!”用来判别非闰年:

!((year%4==0 && year%100!=0) || year%400==0)

关系表达式的值是一个逻辑值,即“真”或“假”。例如,关系表达式“3==4”的值为假,“4>=0”的值为真。C++语言以“1”

代表真,以“0”代表假。

C++语言里只要系统给出的逻辑运算结果非零,即为真。

C++语言提供三种逻辑运算符,如表 2.2 所示。

表 2.2

符号 含义

&& 逻辑与(相当于其他语言中的 and,并且的意思)

|| 逻辑或(相当于其他语言中的 or,或者的意思)

! 逻辑非(相当于其他语言中的 not,非的意思)

逻辑运算举例如下:

a && b: 若 a、b为真,则 a && b为真。

a || b :若 a、b之一为真,则 a || b为真。

!a : 若 a为真,则!a为假。

表 2.3为逻辑运算的“真值表”。

表 2.3

a b !a !b a&&b a||b

真 真 假 假 真 真

真 假 假 真 假 真

假 真 真 假 假 真

假 假 真 真 假 假

在一个逻辑表达式中如果包含多个逻辑运算符,如

!a&&b||x>y&&c

按以下优先次序:

(1)!> && > ||

(2)&&和||低于关系运算符,!高于算术运算符。

例如:

(a>b)&&(x>y) 可写成 a>b && x>y

(a==b)||(x==y) 可写成 a==b||x==y

(!a)||(a>b) 可写成!a||a>b

为了提高 C++程序的运行效率,在逻辑表达式的求解中,并不是所有的逻辑运算符都被执行,只是在必须执行下一个逻辑运

算符才能求出表达式的解时,才执行该运算符。例如:

(1)a && b && c 只有在 a为真时,才需要判别 b 的值。只有 a 和 b 都为真的情况下才需要判别 c的值。只要 a为假,就

Page 59: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

不必判别 b和 c的值。如果 a和 b为假,则不判别 c。

(2)a || b || c 只要 a为真,就不必判断 b和 c,只有 a为假,才判别 b。a和 b都为假才判别 c。

例如下例中输出的 a,b,c的值分别为 0,1,1。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

//逻辑表达式示例

#include <iostream>

using namespace std;

int main()

{

int a=0;

int b=1;

int c=1;

if(a && b++ && c++)

b++;

cout<<a<<b<<c<<endl;

system("pause");

return 0;

}

那这样就很容易写了,看看我的代码对不对?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

//判断闰年 2

#include <iostream>

using namespace std;

int main()

{

int year;

cin>>year;

if((year%400==0) || ((year%4==0) && (year%100!=0)))

cout<<year <<"是闰年"<<endl;

else

cout<<year<<"不是闰年"<<endl;

system("pause");

return 0;

}

switch 语句是多分支选择语句。用来实现多分支选择结构。如学生的成绩‘A’等为 85 分以上,‘B’等为 70~84 分,‘C’

等为 60~69 分,‘D’等为 50~59,其余的为‘E’等,我们可以实现如下:

1

2

3

4

5

6

7

8

switch(grade) //比较 grade 值,grade 值为一个字符

{

case ‘A’:cout<<“85~100 分\n”;//若 grade 值为‘A’

case ’B’:cout<<“70~84 分\n”; //若 grade 值为 ‘B’

case ‘C’:cout<<“60~69 分\n”; //若 grade 值为 ‘C’

case ‘D’:cout<<“50~59 分\n”; //若 grade 值为 ‘D’

default:cout<<“低于 50 分\n”; // 否则为 E

}

switch后面括弧内的表达式,可以为任何类型。如上例中 grade为字符型。

表达式的值必须与某一 case 后面的常量表达式的值完全匹配,才执行此 case后面的语句,若所有的 case 中的常量表达式

的值都没有与表达式的值相匹配,就执行 default后面的语句。

每一个 case 的常量表达式的值必须互不相同,否则会出现互相矛盾的现象。

每个 case和 default的出现次序不影响执行结果。

Page 60: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

要注意的是:执行完一个 case后面的语句后,流程控制转移到下一个 case

继续执行。“case常量表达式”只是起语句标号作用,并不是在该处进行条件判

断。在执行 switch语句时,根据 switch后面表达式的值找到匹配的入口标号后,

就从此标号开始执行下去,不再进行判断。例如上面的例子中,若 grade 的值

等于‘A’,则将连续输出。输出结果如下:

85~100分

70~84分

60~69分

50~59分

低于 50分

应该在执行一个 case分支后,使流程跳出 switch结构,即终止 switch语句的执行。我们可以用一个 break语句来跳出结

构。程序如下:

switch(grade) //比较 grade 值,grade 值为一个字符

{

case 'A':cout<<"85~100 分\n"; break;

case 'B':cout<<"70~84 分\n"; break;

case 'C':cout<<"60~69 分\n"; break;

case 'D':cout<<"50~59 分\n"; break;

default:cout<<"低于 50 分\n"; // 此处可以不加 break 语句。

}

那我可以少写一些语句了,因为多个 case可以共用一组执行语句,如:

switch(grade)

{

case ‘A’:

case ’B’:

case ’C’: cout<<"高于 60 分\n";break; //语句 1

case ‘D’:

default:cout<<"低于 60 分\n"; //语句 2

}

则当 grade 的值为 A,B,C 时执行语句 1,当 grade 的值为 C,D 时执行语

句 2。

【例题描述】五分制

魔法学校实行五分制,即 5 分为最高分,现在输入一个学生的分数,如果是 5 分,则输出“优秀”,如果是 4 分,则输出“良”,

如果是 3 分,则输出“及格”,如果是 2 分,则输出“不及格”,如果是 1 分,则输出“太糟糕了”。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

//五分制

#include <iostream>

using namespace std;

int main()

{

int grade;//注意此处是整数

cin>>grade;

switch(grade) //比较 grade 值,grade 值为一个整数

{

case 5:

cout<<"优秀\n"; break;

case 4:

cout<<"良\n"; break;

case 3:

cout<<"及格\n"; break;

case 2:

cout<<"不及格\n"; break;

default:cout<<"太糟糕了\n"; // 此处可以不加 break 语句

Page 61: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

20

21

22

23

}

system("pause");

return 0;

}

【例题描述】等级划分

魔法学校实行等级制,即 A、B、C、D、E,现在输入一个学生的等级,如果是 A,则输出“优秀”,如果是 B,则输出“良”,

如果是 C,则输出“及格”,如果是 D,则输出“不及格”,如果是 E,则输出“太糟糕了”。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

//等级划分

#include <iostream>

using namespace std;

int main()

{

char grade;//注意此处是字符型

cin>>grade;

switch(grade) //比较 grade 值,grade 值为一个字符

{

case 'A':

cout<<"优秀\n"; break;

case 'B':

cout<<"良\n"; break;

case 'C':

cout<<"及格\n"; break;

case 'D':

cout<<"不及格\n"; break;

default:cout<<"太糟糕了\n"; // 此处可以不加 break 语句

}

system("pause");

return 0;

}

【例题描述】运费计算

魔法学院需要运输一批魔法材料,已知运费计算公式为 f=p×w×s×(1-d),其中 p为运输物品价值,w为运送物品重量,s

为运送物品距离,d为折扣。现已知 p,w,s的值,而折扣 d的计算遵守以下规则:

s<250 没有折扣

250≤s<500 2%折扣

500≤s<1000 5%折扣

1000≤s<2000 8%折扣

2000≤s<3000 10%折扣

3000≤s 15%折扣

试计算运费 f的值。注意,只能用 switch 语句判断和计算折扣值。

这个程序如果用判断语句解决并不是特别困难,可是如果用 switch 语句来解

决,好像有点困难。因为表达式的值必须与某一 case 后面的常量表达式的值完

全匹配,所以显然不能写成 case s<250 或者 case (2000<=s) && (s<3000)这样的错

误形式。

可也总不能写几千个 case 吧?因此我们需要想办法将无尽的数集映射为有

限的数集,你想到怎么做了吗?

参考程序如下所示:

1

2

3

4

5

//计算运费

#include <iostream>

using namespace std;

int main()

Page 62: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

{

int c,s;

float p,w,d,f;

cin>>p>>w>>s;

c=s/250; //c 是 s 的映射

switch(c)

{

case 0:d=0;break;

case 1:d=2;break;

case 2:

case 3:d=5;break;

case 4:

case 5:

case 6:

case 7:d=8;break;

case 8:

case 9:

case 10:

case 11:d=10;break;

default:d=15;break;

}

f=p*w*s*(1-d/100.0);

cout<<"总运费="<<f<<endl;

system("pause");

return 0;

}

由于折扣的变化是有规律的,折扣的变化点都是 250 的倍数,(250,500,

1000,2000,3000)。所以利用这一特点,加一个变量 c=s/250,c代表 250的倍

数。

【上机实践】判断某日是一年的第几天

键盘上按照年月日的格式输入年份、月和日期,运行程序以后,判断这一天是这一年的第几天。

一年有 12 个月份,每个月份的天数是一定的,因此在解决问题时,可以用

switch 语句对应每个月,然后将天数进行累加。需要考虑的是 2 月份的天数计

算,2 月份天数可以先看成 28,计算完毕后再根据年份的闰/平年的不同考虑是

否加 1。

循环结构

使用循环结构的程序可以解决一些按一定规则重复执行的问题而无须重复书写源代码,这是程序设计中最能发挥计算机特长

的程序结构。首先介绍 while语句。

while语句是用来实现“当型”循环结构,其一般形式如下:

while(表达式成立)

执行一条语句;

或者是:

while(表达式成立)

{

语句 1;

语句 2;

...

}

Page 63: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

即当表达式的值为真时,执行 while语句中的内嵌语句。

例如求 1+2+3…+100的值的程序可以这样写:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

//计算 1+2+3…+100 的值

#include <iostream>

using namespace std;

int main()

{

int i=1,sum=0;

while(i<=100) //当 i<=100 时,则执行循环体内的语句

{

sum=sum+i;

i++;

}

cout<<sum<<endl;

system("pause");

return 0;

}

【上机实践】计算 15 的阶乘

试计算 1×2×3×4×…×15 的值。

【例题描述】电文保密

魔法世界电报局的电文保密的规律是将每个英文字母变成其后的第 4个字母,如 A变成 E,a变成 e。最后的 4个大写字母 W、

X、Y、Z分别变为 A、B、C、D。非字母字符不变。输入一行字符,要求输出相应的密码。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

//电文保密

#include <iostream>

#include <math.h>

using namespace std;

int main()

{

char c;

while((c=getchar())!='\n')

{

if((c>='a' && c<='z') || (c>='A' && c<='Z'))//当字符为英文字母时

{

c=c+4;

if(c>'Z' && c<='Z'+4 || c>'z')

c=c-26;

}

cout<<c;

}

system("pause");

return 0;

}

第 9 行的语句并不是从终端敲入一个字符马上输出一个字符。而是直到按

Enter键后将所有字符送入内存缓冲区,然后每次从缓冲区读一个字符,再输出

该字符。

【例题描述】求魔法力

每个魔法学徒的魔法力是不同的,试编写一个程序,从键盘读入每个学徒的魔法力,求全部魔法学徒的魔法力总和。当用户

输入 0时,程序结束。

Page 64: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

//求魔法力

#include <iostream>

using namespace std;

int main()

{

int sum=0,n;

while(true)//表示永远为真,即永远循环,也可以用 while(1)

{

cout<<"输入一个整数(0 表示结束)";

cin>>n;

if(n==0)

break;//跳出该层循环

sum+=n;

}

cout<<"总和为:"<<sum<<endl;

system("pause");

return 0;

}

【上机实践】火柴游戏

魔法学院流行一种火柴游戏,即 21 根火柴,两人轮流取,每人可取 1~4 根,不可多取,也不可不取,谁取最后一根谁输,

假设楚继光先取,张琪曼后取,如何编程让张琪曼赢。

这是一道简单的博弈题,取胜策略:只需让后方取火柴的数量与前方取火柴

数量之和等于 5即可。

1

2

3

4

5

6

7

8

9

10

11

12

设变量 num=21,即火柴数

设变量 man 表示楚继光,可设另一个变量表示张琪曼,也可以不设

当火柴未取完时

{

屏幕输出现在的火柴数

输入这一次楚继光取的火柴数即变量 man

如果楚继光取的火柴数违反规定

continue; //无效重取,即本次循环无效,继续下轮循环

否则输出张琪曼取的火柴数(即 5-man)和剩余的火柴数(即 num-5)

更新现在的火柴数即 num=num-5

}

屏幕输出"你输了! "

【上机实践】整数猜想

魔法世界的魔法师们相信“万法归一”的说法,即天下所有的学问,到了最后都是相通的。令人惊讶的是,居然有魔法师宣

称从数学角度证明了该说法的正确性,这就是所谓的整数猜想:即对于任意给定的大于 1的一个正整数,如果它是一个偶数请将

其除以 2,若是奇数就将其乘以 3加 1,对其运算结果,如果它不是 1,则重复上述操作经过 。经过若干步操作后,总会得到结

果 1。

对于这道题,由于事先无法知道要经过多少步才能得到结果 1。所以用 while

是比较合适的。剩下的就简单了,模拟运算即可。

伪代码如下所示:

1

2

3

4

5

6

输入任一大于 1 的正整数 i

当(i>1)时

{

如果 i 为偶数时

i=i/2

否则

Page 65: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

7

8

9

i=i*3+1

输出更新后的 i 值

}

【上机实践】求圆周率

圆周率,一般以π来表示,是一个在数学及物理学普遍存在的数学常数。它定义为圆形之周长与直径之比。如图 2.5 所示,

早在远古时代的中国,有一位叫祖冲之的数学家第一次将圆周率(π)值计算到小数点后七位,即 3.1415926 到 3.1415927之间。

而魔法世界的魔法师对于圆周率的精确度相求是相当高的,例如用 35 位精度的圆周率值来计算一个能把太阳系包起来的一个圆

的周长,误差还不到质子直径的百万分之一。

现用π/4=1-1/3+1/5-1/7+…公式求π的近似值,直到某一项的绝对值小于 10-6为止。

图 2.5

由公式π/4=1-1/3+1/5-1/7+…可推出π=(1-1/3+1/5-1/7+…)×4,问

题是(1-1/3+1/5-1/7+…)的值怎么算呢?

设变量 ans 为最终结果,初始值 0,使用 while()循环进行逐项累加直到某

一项的绝对值小于 10-6为止。

但是累加每一项的时候,分母在不断地递增(每次多 2),所以需要定义一

个变量来保存分母的值,此外每一项的正负号也在不断变换,这可能也需要一个

变量来表示,例如 x=-x即可完成正负号的转变。

伪代码如下所示:

1

2

3

4

5

6

7

8

9

10

11

float n=1.0 //n 表示分母,初始为 1

float t=1,ans=0//t 为循环中要加的每一项

则当(t 的绝对值>1e-6)时 //1e-6 即 1 乘以 10 的-6 次方

{

ans=ans+t //累加

更新分母的值

改变正负号

更新 t 的值

}

输出 ans*4 的值

【上机实践】求最大公约数和最小公倍数

输入两个正整数 a和 b,求其最大公约数和最小公倍数。

我们使用辗转相除法(欧几里得算法)来求最大公约数,即将大的数 a除以

小的数 b,得余数 c,a=b,b=c,如此反复直到余数为 0,此时的 b 为最大公约

数。

例如 a=24,b=10,则 c=24%10=4,

由 a=b,b=c得 a=10,b=4,则 c=10%4=2

由 a=b,b=c得 a=4,b=2,则 c=4%2=0,故最大公约数为 b=2。

最小公倍数公式为 初始数 a×初始数 b/最大公约数。

伪代码如下所示:

1 输入 a,b 的值

Page 66: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

2

3

4

5

6

7

8

9

10

11

如果 a<b

交换 a 和 b 的值

当(b!=0)

{

c=a%b

a=b

b=c

}

输出最大公约数 b

输出最小公倍数

【上机实践】求 Sn 的值

光明系魔法的魔法力增长值遵循 Sn=a+aa+aaa+aaaa+…的公式,其中 a 是一个数字。例如某学徒初始魔法力为 3,则 4 年后

的魔法力为 3+33+333+3333,试求 n年后该学徒的魔法力 Sn,其中 a,n由键盘输入。

因为年数 n是键盘输入的,所以我们必须控制 while结构循环 n次后就结束,

这可以用一个变量充当计数器,每循环一次,计数器+1,直到计数器>n 退出循

环即可。

难点是循环中如果更改需累加的项数,即项数由 a变为 aa,由 aa变为 aaa…,

一个想法是使用 a=a×10+a,将 a的值扩大 10倍后再加上 a,当然这可能需要其

他变量的辅助完成才行。

【例题描述】埃及分数

魔法学院院长将家中 11 匹马分给 3 个儿子,老大 1/2,老二 1/4,老三 1/6。3 个儿子正在无奈之际,邻居把自己家的马牵

来,老大二分之一,牵走了 6 匹;老二四分之一,牵走了 3匹;老三六分之一,牵走了 2匹。一共 11匹,分完后,邻居把自己

的马牵了回去。即 11/12=1/2+1/4+1/6。这种分子是 1的分数,叫做埃及分数,因为古代埃及人在进行分数运算时,只使用分子

是 1的分数。

现输入一个真分数,请将该分数分解为埃及分数。如:8/11=1/2+1/5+1/55+1/110。

若真分数 a/b中的分子 a能整除分母 b,则真分数经过化简直接就可以得到

埃及分数,否则若真分数的分子不能整除分母,则可以从原来的分数中分解出一

个分母 c=b/a+1的埃及分数。

分解后剩下的部分形成一个新的 a/b,即 a=a×c-b,b=b×c。继续用这种

方法将剩余部分反复分解,最后可得到结果。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

//埃及分数

#include<iostream>

using namespace std;

int main()

{

long int a,b,c;

cin>>a>>b; //输入分子 a 和分母 b

while(1)

{

if(b%a!=0)//若分子不能整除分母

c=b/a+1;//则分解出一个分母为 b/a+1 的埃及分数

else//否则,输出化简后的真分数(埃及分数)

{

c=b/a;

a=1;

}

if(a==1)//若分子已经为 1 了

{

cout<<"1/"<<c;

break; //则结束

Page 67: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

}

else

cout<<"1/"<<c<<"+";

a=a*c-b; //求出余数的分子

b=b*c; //求出余数的分母

if(a==3) //若余数为 3,输出最后两个埃及分数如 3/14=1/7+1/14

{

cout<<"1/"<<b/2<<"+"<<"1/"<<b;

break;

}

}

system("pause");

return 0;

}

do~while语句的特点是先执行循环体,然后判断循环条件是否成立。其一般形式为

do

循环体语句

while(表达式);

例如求 1+2+3…+100的值的程序我们可以这样写:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

//计算 1+2+3…+100 的值

#include <iostream>

using namespace std;

int main()

{

int i=1,sum=0;

do

{

sum=sum+i;

i++;

} while(i<=100);

cout<<sum<<endl;

system("pause");

return 0;

}

while 与 do~while 循环的比较是 while 先判断表达式是否为真,如果为真

才执行循环,如果为假则不执行循环。而 do~while是先不管表达式的值为何值,

先执行循环体内的语句一遍,然后再进行表达式的判断以决定是否继续执行该循

环。

注意不要忘了 do~while语句结束后的分号。

【例题描述】菜单程序的实现

如图 2.6 所示,张琪曼要编写一个小学生算术练习系统,该系统的菜单项包括 1.加法;2.减法;3.乘法;4.除法;5.退出。编

程实现进入系统后显示菜单,等待用户选择,然后执行相应的功能(设每个子功能暂设置为一个输出结果的功能),执行完毕后

返回主菜单,直至选择退出。

图 2.6

Page 68: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

//菜单程序的实现

#include <iostream>

using namespace std;

int main()

{

int c;

int a,b;

do

{

system("cls");//清屏

cout<<"小学生算术练习系统\n";

cout<<" 1.加法\n";

cout<<" 2.减法\n";

cout<<" 3.乘法\n";

cout<<" 4.除法\n";

cout<<" 5.退出\n";

cout<<"请选择 (1~5): ";

cin>>c;

if(c!=5)

{

cout<<"请输入两个数,以空格间隔";

cin>>a>>b;

switch(c)

{

case 1:cout<<a<<"+"<<b<<"="<<a+b<<endl;break;

case 2:cout<<a<<"-"<<b<<"="<<a-b<<endl;break;

case 3:cout<<a<<"*"<<b<<"="<<a*b<<endl;break;

case 4:if(b!=0)

cout<<a<<"/"<<b<<"="<<a/b<<endl;

else

cout<<"除数不能为 0!";

break;

default:cout<<"错误的输入,请重新选择\n"<<endl;

}

system("pause");

}

else

{

cout<<"练习结束!\n";

break;

}

}while(1);

system("pause");

return 0;

}

for语句是 C++语言里使用最为灵活的语句,它完全可以代替 while语句。一般形式如下:

for(循环变量赋初值;循环条件;循环变量增值)

表达式 1 表达式 2 表达式 3

它的执行过程如下:

(1) 先求解表达式 1。

(2) 求解表达式 2,若其值为真,则执行 for语句的内嵌语句,然后执行下面第(3)步。若为假,则结束循环,转到第

(5)步。

(3) 求解表达式 3。

(4) 转回上面第(2)步骤继续执行。

(5) 循环结束,执行 for语句下面的一个语句。

例如求 1+2+3+…+100的值的程序我们可以这样写:

Page 69: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

//计算 1+2+3+…+100 的值

#include <iostream>

using namespace std;

int main()

{

int sum=0,i;//定义循环变量 i

for(i=1;i<=100;i++)//i 赋初值;循环条件;i 每次自增 1

{

sum+=i;//即 sum=sum+i

}

cout<<sum<<endl;

system("pause");

return 0;

}

for语句是很简单和方便的,使用该语句完全可以替代其他的循环语句。

表达式 1、2、3可以根据实际情况省略(注意不可省略分号)。但程序设计者

应另外设法保证循环能正常结束。以下几个程序和上面的程序结果是一样的。

程序一:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

//计算 1+2+3+…+100 的值

#include <iostream>

using namespace std;

int main()

{

int sum=0,i=1;//此处赋初值

for(;i<=100;i++)

{

sum+=i;//即 sum=sum+i

}

cout<<sum<<endl;

system("pause");

return 0;

}

程序二:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

//计算 1+2+3+…+100 的值

#include <iostream>

using namespace std;

int main()

{

int sum=0,i=1;//此处赋初值

for(;i<=100;)

{

sum+=i;//即 sum=sum+i

i++;//循环变量递增写在此处

}

cout<<sum<<endl;

system("pause");

return 0;

}

程序三:

1

2

3

4

5

//计算 1+2+3+…+100 的值

#include <iostream>

using namespace std;

int main()

Page 70: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

6

7

8

9

10

11

12

13

14

15

16

17

18

{

int sum=0,i=1;//此处赋初值

for(;;)

{

if(i>100)//此处进行判断

break;

sum+=i;//即 sum=sum+i

i++;//循环变量递增写在此处

}

cout<<sum<<endl;

system("pause");

return 0;

}

【上机实践】计算数列和

墨老师在黑板上写了一行字:“试编程计算 12+22+32+…+502 的和。”然后说:“同学们是不是看到这种题目就很头痛啊?我告

诉大家,其实解决这种问题很简单的,吃一片止痛药就好啦。”

可以用 for 循环语句循环 50 次累加每一项,用循环变量表示当前计算的项

数,求和的每一项为项数的平方。

【例题描述】显示 ASCII码

目前计算机中用得最广泛的字符集及其编码,是由美国国家标准局(ANSI)制定的 ASCII 码(American Standard Code for

Information Interchange,美国标准信息交换码),它已被国际标准化组织(ISO)定为国际标准,称为 ISO 646 标准。请编写

一个程序,显示 ASCII码为 15~127的字符。

参考程序如下所示:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

//显示 ASCII 码

#include <iostream>

#include <iomanip>

using namespace std;

int main()

{

int i,n=0;

for(i=15;i<128;i++)

{

if (n%10==0 && n>0)

cout<<endl;//每显示十个换一行

cout <<setw(4)<<i<<":"<<(char)i<<" ";

n++;

}

cout<<endl;

system("pause");

return 0;

}

【上机实践】同构数

张琪曼很喜欢研究数字规律。她发现有一些数值平方后会有一种奇怪的现象,她把会出现这种现象的数叫“同构数”。“同构

数”是指这样的一种数,会出现在它的平方数的右端的数。例如 5是 25右边的数,25是 625右边的数,5和 25都是同构数。张

琪曼觉得这些数并不止一两个,请帮她找出 2到 10000之间全部同构数。

伪代码为:

1

2

3

4

for 循环穷举从 2 到 10000 的每一个数 i

{

计算该数的平方数即 j

依次从后向前比较 i 和 j 的位数是否相等

Page 71: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

5

6

7

8

9

10

11

{

如果 i 的所有位数比较完毕仍然相等

则输出该数 i

否则

跳出进行下一个数的穷举

}

}

【上机实践】求 e的近似值

e(指自然底数 e)与圆周率π被认为是数学中最重要的两个超越数(不满足任何整系数代数方程的数,称超越数)。而且 e、

π与虚数 i三者之间有一个相当有名的关系式:e (iπ)

=-1。通常我们可以通过公式 e=1+1/1!+1/2!+1/3!+…计算到它前 n项或到

某一项小于规定值作为近似值。试计算 e的值计算到前一百项。

计算阶乘时无须一遍遍地反复算,完全可以利用前一项已算好的值递推出下

一项的值,例如当计算出 5!后,计算 6!即为 6×5!。

例如某项值为 t=1/n!,则下一项即为 t/(n+1)。

伪代码如下所示:

1

2

3

4

5

6

7

定义浮点数 ans=1,t=1

for(循环一百次)

{

递推出下一项值 t

ans+=t//累加

}

输出 ans 的值

【例题描述】兔子永生

修罗王在恐怖的冥域中找到了传说中的不死秘术,为了安全起见,他对一对刚出生的小兔子施展了此秘术,现在假

设秘术使得所有的兔子都不死,而兔子在出生两个月后,就有繁殖能力,那么 40 个月后可以繁殖多少对兔子?

如图 2.7 所示:

第一个月小兔子没有繁殖能力,所以还是一对;

两个月后,生下一对小兔后总数共有两对;

三个月以后,老兔子又生下一对,因为小兔子还没有繁殖能力,所以一共是三对;

……

图 2.7

Page 72: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

这就是有名的斐波那契数列(Fibonacci),这个数列有如下特点:第 1,2

两个数为 1,1。从第 3个数开始,该数是其前面两个数之和。即:1,1,2,3,

5,8,13…

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

//兔子永生

#include <iostream>

using namespace std;

int main()

{

int f1=1,f2=1,i;

for(i=1;i<=20;i++)

{

cout<<f1<<' '<<f2;

if(i%2==0) //当一行输出两次 f1 和 f2(即 4 个数字)的值后另起一行

cout<<'\n';

f1=f1+f2;

f2=f2+f1;

}

system("pause");

return 0;

}

【上机实践】求分子序列和

有一分子序列:2/1,3/2,5/3,8/5,13/8,21/13…,求出这个数列的前20项之和。

伪代码为:

1

2

3

4

5

6

7

定义浮点数 a=2,b=1,ans=0;//其中 a 为分子,b 为分母

for(循环 20 次)

{

ans=ans+a/b;

更新分子 a 和分母 b 的值;

}

输出 ans 的值;

【例题描述】计算圆面积

如图 2.8 所示,魔法学院计划建设一个圆形广场,试依次计算半径 r=1到 r=10时的圆面积,直到面积 area 大于 100 为止。

图 2.8

1

2

3

4

5

6

7

8

9

10

11

12

//计算 r=1 到 r=10 时的圆面积,直到面积 area 大于 100 为止

# include <iostream>

using namespace std;

int main()

{

float area;int r;

for(r=1;r<=10;r++)

{

area=3.14*r*r;

if(area>100)

break; //跳出当前循环体,执行循环体下面的语句

cout<<r<<":area="<<area<<endl;

Page 73: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

13

14

15

16

}

system("pause");

return 0;

}

【例题描述】不能被 3 整除的数

与世界上的多数古代文明相同,魔法世界也认为 3是一个神圣的数字,所有能被 3整除的数字也是神圣的数字,试将 100~200

之间不能被 3 整除的数输出。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

//把 100~200 之间不能被 3 整除的数输出。

#include <iostream>

using namespace std;

int main()

{

int n;

for(n=100;n<=200;n++)

{

if(n%3==0)

continue; //跳出当前循环体,执行循环体下面的语句

cout<<n<<' ';

}

system("pause");

return 0;

}

break可以用来从循环体内跳出循环体,即提前结束循环,接着执行循环下

面的语句。break语句不能用于循环语句和 switch语句之外的任何其他语句中。

continue语句与 break语句的区别是:continue语句只结束本次循环,而

不是终止整个循环的执行。而 break语句则是结束整个循环过程,不再判断执行

循环的条件是否成立。

【例题描述】九九乘法表

九九乘法表又称九九乘法歌诀,在《荀子》、《管子》、《淮南子》、《战国策》等中国古书中就能找到“三九二十七”、“六八四

十八”、“四八三十二”、“六六三十六”等句子。试编程输出九九乘法表。

这道题需要两层 for循环语句嵌套完成,即:

for(i=1;i<=9;i++)

for(j=1;j<=9;j++)

cout<<i<<"*"<<j<<"="<<i*j<<' ';

注意两层 for循环的循环变量应分别定义,即定义 i和 j。其运行顺序是执

行外循环到内循环时,要先把内循环的所有循环执行完后再跳到外循环。

1

2

3

4

5

6

7

8

9

10

11

12

13

//九九乘法表

#include <iostream>

using namespace std;

int main()

{

int i,j;

for(i=1;i<=9;i++)

for(j=1;j<=9;j++)

cout<<i<<'*'<<j<<"="<<i*j<<endl;

system("pause");

return 0;

}

【例题描述】执行任务

魔法学院要在 A,B,C,D,E,F六个学员中尽可能多地挑若干人去执行一项任务,但有以下限制条件:

Page 74: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

(1)A和 B两人中至少去一人;

(2)A和 D不能一起去;

(3)A、E 和 F三人中要派两人去;

(4)B和 C都去或都不去;

(5)C和 D两人中去一个;

(6)若 D不去,则 E也不去。

问应当让哪几个人去?

用 a,b,c,d,e,f 六个变量表示六个人是否去执行任务的状态,变量的

值为 1,则表示该人去;变量的值为 0,则表示该人不参加执行任务,根据题意

可写出表达式:

a+b>=1 A和 B两人中至少去一人;

a+d!=2 A和 D 不能一起去;

a+e+f==2 A、E、F三人中要派两人去;

b+c==0或 b+c==2 B和 C 都去或都不去;

c+d==1 C和 D 两人中去一个;

d+e==0或 d==1 若 D不去,则 E也不去(都不去;或 D去 E随便)。

上述各表达式之间的关系为“与”关系。穷举每个人去或不去的各种可能情

况,代入上述表达式中进行推理运算,使上述表达式均为“真”的情况就是正确

的结果。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

//执行任务

#include<iostream>

using namespace std;

int main()

{

int a,b,c,d,e,f;

for(a=1;a>=0;a--)//穷举每个人是否去的所有情况

for(b=1;b>=0;b--) //1:去 0:不去

for(c=1;c>=0;c--)

for(d=1;d>=0;d--)

for(e=1;e>=0;e--)

for(f=1;f>=0;f--)

if(a+b>=1&&a+d!=2&&a+e+f==2&&

(b+c==0||b+c==2)&&c+d==1&&(d+e==0||d==1))

{

cout<<"a:"<<a<<endl;

cout<<"b:"<<b<<endl;

cout<<"c:"<<c<<endl;

cout<<"d:"<<d<<endl;

cout<<"e:"<<e<<endl;

cout<<"f:"<<f<<endl;

}

system("pause");

return 0;

}

【例题描述】打印三角形

使用循环结构打印如下图形:

*

***

*****

*******

参考程序如下所示:

Page 75: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

//打印三角形

#include <iostream>

using namespace std;

int main()

{

for(int i=1;i<=4;i++)//输出行数

{

for(int j=1;j<=4-i;j++)//控制每行输出的空格数

cout<<' ';

for(int j=1;j<=2*i-1;j++)//控制每行输出的*的个数

cout<<'*';

cout<<endl;

}

system("pause");

return 0;

}

【上机实践】打印菱形

使用循环结构打印如下图形:

*

***

*****

*******

*****

***

*

请完善下面的程序:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

//打印菱形

#include <iostream>

using namespace std;

int main()

{

for(int i=-3;i<=3;i++)//输出行数

{

int k=abs(i);

for( )//控制输出每行空格数

cout<<' ';

for( )//控制输出每行*号数

cout<<'*';

cout<<endl;

}

system("pause");

return 0;

}

【上机实践】打印数字菱形 1

使用循环结构打印如下图形:

4

333

22222

1111111

Page 76: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

22222

333

4

请完善下面的程序:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

//打印数字菱形 1

#include <iostream>

using namespace std;

int main()

{

for(int i=-3;i<=3;i++)//控制输出行数

{

int k=abs(i);

for( )//控制输出每行的空格数

cout<<' ';

for( ) //控制输出的数字

cout<<k+1;

cout<<endl;

}

system("pause");

return 0;

}

【上机实践】打印数字菱形 2

使用循环结构打印如下图形:

1

212

32123

4321234

32123

212

1

参考程序如下所示:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

//打印数字菱形 2

#include <iostream>

using namespace std;

int main()

{

for(int i=-3;i<=3;i++)//控制输出的行数

{

int k=abs(i);

for( )//控制输出的空格数

cout<<' ';

for( )//控制输出的数字

cout<< ;

cout<<endl;

}

system("pause");

return 0;

}

【例题描述】判断质数

Page 77: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

素数又称质数。指在一个大于 1的自然数中,除了 1和此整数自身外,没法被其他自然数整除的数。换句话说,只有两个正

因数(1 和自己)的自然数即为素数。比 1 大但不是素数的数称为合数。1 和 0 既非素数也非合数。合数是由若干个质数相乘而

得到的。所以,质数是合数的基础,没有质数就没有合数。所有的合数都可由若干个质数相乘而得到。

从键盘输入一个数,并判断该数是否是质数。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

//判断质数

# include <iostream>

# include <math.h>

using namespace std;

int main()

{

int number,i,k;

cin>>number;

k=sqrt(number); //k 为输入数的平方根,想一下为什么?

for(i=2;i<=k;i++) //将输入数用从 2 至 k 的一个一个数进行整除

if(number%i==0)

break; //只要能被整除,即跳出 for 循环

if(i>k)

cout<<number<<"是一个素数\n";

else

cout<<number<<"不是一个素数\n";

system("pause");

return 0;

}

【上机实践】求素数

编程求 10~500之间的全部素数。

这个很简单了,使用一个 for 循环枚举 10~500 之间的每一个数,再使用上

面的判断质数的程序判断即可。

我能想到的两个优化是:

(1)判断一个数 number 是不是质数,只需要从 2 依次试除到 number ,看

能否被这些数整除即可。

(2)因为偶数肯定不是素数,所以在 for循环时,可以直接跳过,这样可提

高一倍的速度。

数学家欧几里得最早在《几何原本》中证明了素数有无穷多个:

假设只有有限个素数 P1,P2,P3,…,Pn。令 N=P1×P2×P3×…×Pn。那

么,N+1是素数或者不是素数。

如果 N+1 为素数,则 N+1 要大于 P1,P2,P3,…,Pn,所以它不在那些假

设的素数集合中。

如果 N+1 为合数,因为任何一个合数都可以分解为几个素数的积;而 N 和

N+1 的最大公约数是 1,所以 N+1不可能被 P1,P2,P3,…,Pn整除,所以该合

数分解得到的素因数肯定不在假设的素数集合中。

因此无论该数是素数还是合数,都意味着在假设的有限个素数之外还存在着

其他素数。

对任何有限个素数的集合来说,用上述的方法永远可以得到有一个素数不在

假设的素数集合中的结论。

所以原先的假设不成立。也就是说,素数有无穷多个。

【例题描述】防护罩

魔法世界的所有街区均为边长为 1公里的正方形,如图 2.9所示整齐地排列,为了防止黑暗势力的魔法攻击,魔法学校欲建

Page 78: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

立一个以中心四个街区的交点为圆心,半径为 r(r≥ 2 )公里的圆形魔法防护罩,试计算防护罩所能保护的完整街区数 N。

图 2.9

由于圆的对称性,所以只需要计算四分之一圆中所包含的完整的街区数 N,将其中所包含的完整街区数以纵列划分为组,设

K为四分之一圆中共有 K组街区,显然 K为不超过 r的最大整数,如图 2.10所示。

图 2.10

则 N=4×(第一组内街区数+第二组内街区数+…+第 K组内街区数),问题是每组内的街区数如何计算呢?

现以第四组为例,作一直角三角形如图 2.11所示,可知斜边为 r,底边为组数,根据勾股定理,当组数为 a,则第 a组街区

数=22 ar 的最大取整数。

图 2.11

C++语言提供的取整函数有:

1.floor 函数

floor(x)返回的是 x的整数部分,方法是向负无穷大方向舍入。如:

floor(2.5) = 2

Page 79: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

floor(-2.5) = -3

2.ceil函数

ceil(x)返回的是不大于 x的最小整数,方法是向正无穷大方向舍入。如:

ceil(2.5) = 3

ceil(-2.5) = -2

参考代码如下所示:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

//防护罩

#include <iostream>

#include <math.h> //须加此头文件

using namespace std;

int main()

{

int N=0,i,temp;

double r,l;

cin>>r;

l=floor(r);//获得组数

for(i=1;i<=l;i++)

{

temp=sqrt(r*r-i*i);

N=N+temp;

}

cout<<N*4;

system("pause");

return 0;

}

【例题描述】完美数

魔法世界有一个毕达哥拉斯学派,他们将一个数如果恰好等于它的因子之和的数称为“完美数”。并且认为完美数具有神奇

的魔力。例如 6的因子为 1、2、3,而 6=1+2+3,因此 6是“完美数”。创始人毕达哥拉斯说:“6 象征着完满的婚姻以及健康

和美丽,因为它的部分是完整的,并且其和等于自身。”

请编程序找出 1000之内的所有完美数。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

//求完美数

#include <iostream>

using namespace std;

#define M 1000//定义寻找范围

int main()

{

int k0,k1,k2,k3,k4,k5,k6,k7,k8,k9;//保存因子,因子数一定不超过 10 个

int i,j,n,s;

for(j=2;j<=M;j++)//枚举每一个数

{

n=0;//n 指向因子保存的位置

s=j;

for(i=1;i<j;i++)

{

if((j%i)==0)

{

n++;

s=s-i;

switch(n)//判断因子存在何处

{

case 1:k0=i;break;

case 2:k1=i;break;

case 3:k2=i;break;

case 4:k3=i;break;

Page 80: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

case 5:k4=i;break;

case 6:k5=i;break;

case 7:k6=i;break;

case 8:k7=i;break;

case 9:k8=i;break;

case 10:k9=i;break;

}

}

}

if(s==0)//若减去所有因子后恰巧为 0,则是完美数

{

cout<<j<<"是一个完美数,它的因子是:";

if(n>1)

cout<<k0<<' '<<k1<<' ';

if(n>2)

cout<<k2<<' ';

if(n>3)

cout<<k3<<' ';

if(n>4)

cout<<k4<<' ';

if(n>5)

cout<<k5<<' ';

if(n>6)

cout<<k6<<' ';

if(n>7)

cout<<k7<<' ';

if(n>8)

cout<<k8<<' ';

if(n>9)

cout<<k9<<' ';

cout<<"\n";

}

}

system("pause");

return 0;

}

后世的大数学家欧拉曾推算出完美数的获得公式:

如果 p是质数,且 2p-1也是质数,那么(2

p-1)×2

p-1便是一个完美数。

例如 p=2是一个质数,2p-1=3也是质数,则(2

p-1)×2

p-1=3×2=6是完美

数。

例如 p=3 是一个质数,2p-1=7 也是质数,则(2

p-1)×2

p-1=7×4=28 是完

美数。

但是 2p-1 什么条件下才是质数呢? 事实上,当 2

p-1 是质数的时候,称其

为梅森素数。至今,人类只发现了 47个梅森素数,也就是只发现了 47个完全数。

【例题描述】除式还原 1

魔法学院的后山出现一块神秘石碑,石碑上有一个除式如图 2.12所示,据说解开此除式可以解开一个重大秘密。

Page 81: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

图 2.12

由除式本身尽可能多地推出已知条件:

1.被除数的范围是 10000到 99999,除数的范围是 10到 99,且可以整除;

2.商为 100到 999之间,且十位数字为 7;

3.商的第一位与除数的积为三位数,且后两位为 77;

4.被除数的第三位一定为 4;

5. 7乘以除数的积为一个三位数,且第二位为 7;

6.商的最后一位不能为 0,且与除数的积为一个二位数。

由已知条件就可以采用穷举的方法找出结果。

参考代码如下所示:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

//除式还原 1

#include<iostream>

using namespace std;

int main()

{

long int i;

int j,l;

for(i=10000;i<=99999;i++) //条件 1. i 为被除数

//if(i%1000-i%100==400) //条件 4. 被除数的第三位一定为 4

for(j=10;j<=99;j++) //条件 1. j 为除数

if(i%j==0 && (l=i/j)%100>=70 && l%100<80//条件 1.可整除,商的十位数为 7

&&l%10!=0&&l>100&&l<=999)//商的个数不能为 0,商 l 在 100 到 999 之间

if((j*(l%10))<100&&j*(l%10)>10) //条件 6.商的个数与除数的积为二位数

if(j*7%100>=70&&j*7%100<80) //条件 5.7 乘以除数的积的第二位为 7

if(j*(l/100)%100==77&&j*(l/100)>100)//商的第 1 位×除数的后 2 位为 77

cout<<i<<"/"<<j<<"="<<l;

system("pause");

return 0;

}

【例题描述】换零钱

楚继光想把 100元钱换成 10元、5元、1元这样的零钱,在这三种零钱中每种零钱都至少各有一张的情况下,共有多少种兑

换方案?

设 100元可以换 10元 x张、5元 y张,1元 z张,则:

100=10x+5y+z (x≥1,y≥1,z≥1)

分析上面的等式可以发现,1≤x≤9,1≤y≤19,1≤z≤99。

因为 x≥1,所以 5y的取值范围变成 5≤5y≤100-10×1,解得:1≤y≤18;

同理,z 的取值范围为 1≤z≤100-10×1-5×1,解得 1≤z≤85,这样使得内

层循环体执行的次数由原来的 9×19×99=16929次缩小到 9×18×85=13770次,

循环次数减少了 3159 次。另外,由于 1 元的零钱必为 5 的倍数,所以还可再做

优化。

Page 82: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

//换零钱基本算法

#include <iostream>

using namespace std;

int main()

{

int i,j,k,count=0;

for(i=1;i<=9;i++)

for(j=1;j<=18;j++)

for(k=1;k<=85;k++)

if((i*10+j*5+k*1)==100)

count++;

cout<<count<<endl;

system("pause");

return 0;

}

用不等式减少循环套数量:

对于不定方程 100=10x+5y+z,做如下变形:z=100-10x-5y。由于 z 是大

于等于 1的整数,所以不定方程 100=10x+5y+z解的个数就等于不等式 100-10x

-5y>0的解的个数,易得 1≤x<=9,1≤y≤18。故而可用二重循环求解。此时,

只需 9×18=162次循环体,效率提高了 105倍。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

//利用不等式优化算法

#include <iostream>

using namespace std;

int main()

{

int i,j,k,count=0;

for(i=1;i<=9;i++)

for(j=1;j<=18;j++)

if(i*10+j*5<100)

count++;

cout<<count<<endl;

system("pause");

return 0;

}

取 1张 10元的,则剩下的 90 元可以用 1~17张 5元和若干 1元的相加得到,

故共有 17种方案;

取 2张 10元的,则剩下的 80 元可以用 1~15张 5元和若干 1元的相加得到,

故共有 15种方案;

……

取 8张 10元的,则剩下的 20元可以用 1~3张 5元和若干张 1元的相加得到,

故共有 3种方案;

取 9张 10元的,则剩下的 10元可以用 1张 5元和 5张 1元的相加得到,故

共有 1种方案。

综上可得,总方案数为 1+3+5+…+17=81种,则问题就变成了求 9个数的和。

用单重循环运算 9次即可。此时程序效率提升了共 1881倍。

1

2

3

4

5

6

7

//利用排列组合优化

#include <iostream>

using namespace std;

int main()

{

int i,count=0;

Page 83: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

8

9

10

11

12

13

for(i=1;i<=9;i++)

count+=2*i-1;

cout<<count<<endl;

system("pause");

return 0;

}

观察数字序列 1 3 5 7 9 11…,可以发现相邻两个数的差恒为 2,这显然是

“等差数列”,记第一项为 a1,相邻两个数之间的差为公差,记为 d,记第 n 项

为 an,容易推导出:an=a1+(n-1)d,如上面第 3个数为 1+(3-1)×2=5。对于

等差数列,我们有如下的公式来求出其前 n项的和:

Sn=((a1+an)×n)/2 (1)

Sn=na1+((n×(n-1))/2×d (2)

Sn=nan-(n×(n-1))/2×d (3)

公式(1)用于求知道首项、末项和项数的等差数列的前 n 项和;

公式(2)用于求知道首项,公差和项数的等差数列的前 n 项和;

公式(3)用于求知道末项,公差和项数的等差数列的前 n 项和。

则无需循环,直接用任何一个公式就可求出和为 81。

1

2

3

4

5

6

7

8

9

10

//换零钱──利用数学公式

#include <iostream>

using namespace std;

int main()

{

cout<<(1+17)*9/2<<endl;

system("pause");

return 0;

}

【上机实践】换零钱 2

楚继光想把 N元钱换成 10元、5元、1元这样的零钱,在这三种零钱中每种零钱都至少各有一张的情况下,共有多少种兑换

方案(16≤N≤105)

由于钱的数字变成了任意数 N,问题变得复杂起来,如果用两重循环,105

的数据量肯定超时(1秒)。有什么好的办法吗?

设 N元可以换 10元 x张,5元 y张,1元 z张,x,y,z均不小于 1。则题目的解为不等式 N-10x-5y>0解的个数。

等差数列的项数为:可以换 10元的最大张数;等差数列的最末项为:当有 1张 10元时,可以换 5元的最大张数。由于是换

成 10元、5 元和 1元三种,每增加一张 10 元必定减少 2张 5元,所以等差数列的公差为 2。

N/10 N%10>5 表示还有多余的钱分配给 5元和 1元

a=

N/10-1 N%10≤5 表示没有多余的钱分配给 5元和 1元

N元钱,当有 1张 10元时,可以换 5元的最大张数 b为:

(N-10)/5 (N-10)%5>0 表示还有多余的钱分配给 1元

b=

(N-10)/5-1 (N-10)%5=0 表示没有多余的钱分配给 1元

等差数列的公差为 2,项数为 a,最末项为 b,使用等差数列的求和公式 3得

Sum=a×b-(a×(a-1))/2×2=a×b-a×(a-1)=a×(b-a+1)

参考程序如下所示:

Page 84: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

1

2

3

4

5

6

7

8

9

10

11

12

定义变量 n,a,b,c,sum,其中 sum 为最终答案,a,b,c 分别为 10 元,5 元,1 元的张数

输入变量 n 的值

计算 a 的值

计算 c 的值

如果剩下的钱不够一张 5 元和一张 1 元

则 a--

计算 b 的值

计算 c 的值

如果 c 的值不足一元钱

则 b--

计算 sum 的值

输出 sum 的值

或者对于输入的钱数 x,必定需要一张 10元、一张 5元和一张 1元的。为了简化,可以将这 16 元钱从 x中减去,剩下的钱

所需要的钱的张数均为大于等于 0。当 10 元的张数为 0(减去后)时,5 元张数的最大值为 (x-16)/5(整数相除自动去尾)。

然后可以一张 5元的换成 5张 1元的,即有(x-16)/5+1种换法。当 10元的张数为 1时,5元张数的最大值少 2,方法数也少 2。

一直到 5 元的张数为 1 或 2 时,10 元张数达到最大(若 10 元张数更大,5 元张数为-1 或 0)。若为奇数,例如输入 x=100,方

法的总和为 17+15+13+11+9+…+1,运用等差数列求和,count=(x+1)×(x+1)/4。若为偶数,由于整数相除时自动去尾,count

仍可以用原来的式子表示。

参考程序如下所示:

1

2

3

4

5

6

7

8

9

10

11

12

13

//换零钱 2 优化

#include<iostream>

using namespace std;

int main()

{

int count=0,x,i;

cin>>x;

x=(x-16)/5+1;//求 5 元面值张数的最大值,即 10 元张数=0 时的方法数量

count=(x+1)*(x+1)/4;//等差求和

cout<<count<<endl;

system("pause");

return 0;

}

第三部试读章节

第一章 链表

何谓链表

我们知道,一般用数组存放一组数据时,必须事先定义固定的长度(即元素个数)。这在某些问题的解决中,并不是特别的

适用。例如记录不同班级的学生数据时,由于各班人数不同,开辟过大的数组会导致内存浪费、开辟过小的数组导致数组元素不

够用。而链表可以根据需要动态开辟内存单元,是一种常见的重要数据结构。图 1.1 所示为最简单的一种链表结构。

Page 85: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

图 1.1

链表如同铁链一样,一环扣一环,中间是不能断开的。打个通俗的比方:幼儿园老师带领小朋友出来散步,老师牵着第一个

小朋友的手,第一个小朋友牵着第二个小朋友的手……这就是一个“链”,最后一个小朋友的手是空的。

老师即“头指针”变量,图 1.1 中以 Head 表示,它存放一个地址。链表中每一个元素称为“节点”,每个节点都应该包括两

部分:一为实际元素值,一为下一节点的地址。

最后一个元素不指向其他元素,它被称为“表尾”,以“NULL”表示,“NULL”在 C++语言里指向“空地址”。

很显然,这种链表的数据结构,必须要用指针变量才能实现。

简单静态链表

下面的代码是一个简单的静态链表,它由 3 个学生的数据(学号,成绩)的节点组成。请考虑:(1)head 的作用?(2)p

的作用?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

// 简单静态链表

#include <iostream>

#include <cstdio>

using namespace std;

struct student

{

long num;

float score;

struct student *next; //该指针指向 student 类型的结构体

};//注意必须有分号

int main()

{

struct student a,b,c,*head,*p;

a.num=34341; a.score=81.5;//赋值

b.num=34343; b.score=97;

c.num=34344; c.score=82;

head=&a;//将 a 的地址给 head

a.next=&b;

b.next=&c;

c.next=NULL;

p=head;

do //输出记录

{

cout<<p->num<<" "<<p->score<<endl;

p=p->next;

}while(p!=NULL);

getchar();

}

处理动态链表的函数

1. malloc

其函数原型为

Page 86: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

void *malloc(unsigned int size);

作用是在内存的动态存储区中分配一个长度为 size 的连续空间。此函数返回的是一个指向分配域起始地址的指针,如果此函

数未能成功地执行(如内存空间不足),则返回空指针 (NULL)。

2. calloc

其函数原型为

void *calloc(unsigned n,unsigned size);

作用是在内存的动态区存储中分配n个长度为 size的连续空间。函数返回一个指向分配域起始地址的指针;如果分配不成功,

返回 NULL。

3. free()

其函数原型为

void free(void *p);

作用是释放由 p 指向的内存区,使这部分内存区能被其他变量使用。

动态链表的准备工作

一个完善的动态链表程序应该具有以下基本功能:建立链表、插入节点、删除节点、打印链表、释放链表等。扩展的动态链

表程序还可能有获得链表长度、获得当前节点、查找节点位置、连接两个链表、比较两个链表等功能。下面将逐个实现其功能代

码。

为了程序的易读性和可扩展性,有时需要在程序开头先进行预定义处理。请务必领会下面的代码的用意,否则将影响对以后

代码的理解。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

#include <iostream>

#include <cstdlib>

#include <cstdio>

using namespace std;

typedef int ElemType; //以 ElemType 代表 int 型数据

typedef struct List *link; //以 link 代表链表指针

typedef struct List Lnode; //以 Lnode 代表链表节点

struct List

{

ElemType data;//此处仅以一个整型变量为例

struct List *next;

};

主函数的建立:下面的主函数只是一个简单调用各功能子函数的示范例子,读者可自行修改和添加代码以完成更复杂的任务。

请根据主函数的代码考虑各功能子函数的原型应如何建立。

1

2

3

4

5

6

7

8

9

10

11

12

13

int main()

{

int l;link head1;link head2;

head1=create(head1); //建立链表 1

head2=create(head2); //建立链表 2

connect(head1,head2);//连接两个链表

head1=insert(head1,888,5);//在位置为 5 处插入元素 888

head1=del(head1,3); //删除一个节点

display(head1); //打印链表

printf("\n lenth is %d\n", lenth(head1));//打印链表长度

printf("\n get is %d\n", get(head1,3)); //获得当前节点值

printf("\n locate 12 is %d", locate(head1,12));//查找元素 12 所在的位置

head=setnull(head);//释放链表

Page 87: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

14 }

链表的建立

由主函数调用 create()函数的方式可知,该函数应该返回一个节点的指针,输入的参数也应该是一个节点指针。代码如下

所示:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

link create(link Head)

{

ElemType newData;

link NewPoint;

/*先建立一个节点*/

Head=(link)malloc(sizeof(Lnode));

printf("please input number: \n");

scanf("%d",&newData);

Head->data=newData; //节点赋值

Head->next=NULL; //节点指向空地址

while(1) //继续添加节点

{

NewPoint=(link)malloc(sizeof(Lnode));//开辟一个节点空间

if(NewPoint==NULL) //如果开辟空间失败,则返回

break; //此判断语句在某些类型的竞赛中用处不大,可忽略

printf("please input number: input '-1' means exit\n");

scanf("%d",&newData);

if (newData==-1) //输入-1 则添加节点结束并返回 head

return Head;

NewPoint->data=newData;

NewPoint->next=Head;

Head=NewPoint;

}

return Head;

}

链表的显示

该子函数无返回值,输入参数为链表的头指针,请考虑:指针 p 的作用是什么?如果不用指针 p,直接用 Head 这个指针会

出现什么后果?

1

2

3

4

5

6

7

8

9

10

11

12

void display(link Head)

{

link p;p=Head;

if(p==NULL)

printf("\nList is empty\n");

else

do

{

printf("%d ",p->data);

p=p->next;

}while(p!=NULL);

}

Page 88: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

节点的插入

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

link insert(link Head,ElemType x,int i)

{

link NewPoint,p=Head;

int j=1;

NewPoint=(link)malloc(sizeof(Lnode));

NewPoint->data=x;

if(i==1) //如果插入位置为第一个节点时

{ NewPoint->next=Head; Head=NewPoint; }

else

{

while(j<i-1 && p->next!=NULL)

{ p=p->next; j++; }

if(j==i-1)

{

NewPoint->next=p->next;

p->next=NewPoint;

}

else printf("insert is Failure,i is not right!");

}

return Head;

}

节点的删除

例如下面这个链表:

(A)→(B)→(C)→(D)→(E)→…→NULL

因为链表中唯一能够找到元素的办法是通过它上一个元素的指针,所以如果我们将某个元素直接删去,这个元素所指向的元

素和它之后的所有元素都没有办法再找到了,为了解决这个问题,一般采取这样的办法:记录下要删除的元素之前的那个元素,

在删除元素之前,把这个元素的指针指向要删除的那个元素指针指向的元素,比如说现在要删除这个链表的元素 B,先找到它之

前的元素 A,在删除 B 之前将 A 的指针指向 C,然后再删除 B,这样在删除 B 之前,就已经把 B 从链表里面剔了出来,如图 1.2

所示。

图 1.2

1

2

3

4

5

6

7

8

9

link del(link Head,int i)

{

int j=1; link p,t; p=Head;

if(i==1)

{ p=p->next; free(Head); Head=p; }

else

{

while(j<i-1 && p->next !=NULL)

{ p=p->next; j++; }

Page 89: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

10

11

12

13

14

15

if(p->next!=NULL && j==i-1)

{ t=p->next; p->next=t->next; }

if(t!=NULL) free(t);

}

return Head;

}

上面的例子中操作符 free 用来删除一个变量,即在内存中释放某个变量所对应的地址,使得之后系统可以利用这块内存做

别的事情,这样比较节省内存空间。我们应养成及时释放无用的内存空间的好习惯。

获得节点元素值

1

2

3

4

5

6

7

8

9

10

11

12

ElemType get(link Head,int i)

{

int j=1;

link p; p=Head;

while(j<i && p!=NULL)

{ p=p->next; j++; }

if(p!=NULL)

return(p->data);

else

printf("data is error!");

return -1;

}

查找节点元素 X 的位置

1

2

3

4

5

6

7

8

9

10

int locate(link Head,ElemType x)

{

int n=0;link p;p=Head;

while(p!=NULL && p->data !=x)

{ p=p->next; n++; }

if(p==NULL)

return -1;

else

return n+1;

}

返回链表的长度

1

2

3

4

5

6

int lenth(link Head)

{ int len=0; link p; p=Head;

while(p!=NULL)

{ len++; p=p->next; }

return len;

}

连接两个链表

1

2

3

link connect(link Head1,link Head2)

{

link p; p=Head1;

Page 90: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

4

5

6

7

8

while(p->next !=NULL)

{ p=p->next; }

p->next=Head2;

return Head1;

}

比较两个链表是否相同

1

2

3

4

5

6

7

8

9

10

11

12

13

int compare(link Head1,link Head2)

{

link p1,p2; p1=Head1; p2=Head2;

while(1)

{

if((p1->next==NULL) &&(p2->next==NULL))

return 1;

if(p1->data!=p2->data)

return 0;

else

{ p1=p1->next; p2=p2->next; }

}

}

释放链表

1

2

3

4

5

6

7

8

9

10

11

link setnull(link Head)

{

link p;p=Head;

while(p!=NULL)

{

p=p->next;

free(Head);

Head=p;

}

return Head;

}

完整的链表程序

参考代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

#include <iostream>

#include <cstdlib>

#include <cstdio>

using namespace std;

typedef int ElemType;

typedef struct List *link;

typedef struct List Lnode;

struct List

{

ElemType data;

struct List *next;

};

link setnull(link Head)

Page 91: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

{

link p;p=Head;

while(p!=NULL)

{

p=p->next;

free(Head);

Head=p;

}

return Head;

}

link insert(link Head,ElemType x,int i)

{

link NewPoint,p=Head;

int j=1;

NewPoint=(link)malloc(sizeof(Lnode));

NewPoint->data=x;

if(i==1)

{

NewPoint->next=Head;

Head=NewPoint;

}

else

{

while(j<i-1 && p->next!=NULL)

{

p=p->next;

j++;

}

if(j==i-1)

{

NewPoint->next=p->next;

p->next=NewPoint;

}

else printf("insert is Failure,i is not right!");

}

return Head;

}

link create(link Head)

{

ElemType newData;

link NewPoint;

Head=(link)malloc(sizeof(Lnode));

printf("please input number: \n");

scanf("%d",&newData);

Head->data=newData;

Head->next=NULL;

while(1)

{

NewPoint=(link)malloc(sizeof(Lnode));

if(NewPoint==NULL)

break;

printf("please input number: input '-1' means exit\n");

scanf("%d",&newData);

if (newData==-1)

return Head;

NewPoint->data=newData;

NewPoint->next=Head;

Head=NewPoint;

}

return Head;

}

Page 92: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

int lenth(link Head)

{

int len=0;

link p;

p=Head;

while(p!=NULL)

{

len++;

p=p->next;

}

return len;

}

ElemType get(link Head,int i)

{

int j=1;

link p; p=Head;

while(j<i && p!=NULL)

{

p=p->next;

j++;

}

if(p!=NULL)

return(p->data);

else

printf("data is error!");

return -1;

}

int locate(link Head,ElemType x)

{

int n=0;link p;p=Head;

while(p!=NULL && p->data !=x)

{

p=p->next;

n++;

}

if(p==NULL)

return -1;

else

return n+1;

}

void display(link Head)

{

link p;p=Head;

if(p==NULL)

printf("\nList is empty\n");

else

do

{

printf("%d ",p->data);

p=p->next;

}while(p!=NULL);

}

link connect(link Head1,link Head2)

{

link p;

p=Head1;

while(p->next !=NULL)

{

p=p->next;

}

Page 93: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

p->next=Head2;

return Head1;

}

link del(link Head,int i)

{

int j=1;

link p,t;

p=Head;

if(i==1)

{

p=p->next;

free(Head);

Head=p;

}

else

{

while(j<i-1 && p->next !=NULL)

{

p=p->next;

j++;

}

if(p->next!=NULL && j==i-1)

{

t=p->next;

p->next=t->next;

}

if(t!=NULL)

free(t);

}

return Head;

}

int compare(link Head1,link Head2)

{

link p1,p2;

p1=Head1;

p2=Head2;

while(1)

{

if((p1->next==NULL) &&(p2->next==NULL))

return 1;

if(p1->data!=p2->data)

return 0;

else

{

p1=p1->next;

p2=p2->next;

}

}

}

int main()

{

int l;

link head1;link head2;

head1=create(head1);

printf("\nHead1 is\n");

display(head1);

head2=create(head2);

printf("\nHead2 is\n");

Page 94: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

display(head2);

l=compare(head1,head2);

printf("\nl is %d\n",l);

connect(head1,head2);

printf("\nHead1+Head2 is\n");

display(head1);

l=lenth(head1);

printf("\nlenth is %d\n",l);

l=get(head1,3);

printf("\n get is %d\n",l);

l=locate(head1,12);

printf("\n locate 12 is %d",l);

head1=insert(head1,888,5);

display(head1);

head1=del(head1,5);

display(head1);

head1=setnull(head1);

display(head1);

}

数组仿真链表

实际上,可以使用数组仿真链表的各项功能,当然数组仿真链表不具备链表动态开辟内存空间的特点。参考代码如下所示:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

//数组仿真链表的演示程序

#include<iostream>

#include <cstdio>

#define MAXN 100001//链表中最多能容纳的元素数+1

using namespace std;

int linklst_data[MAXN] ;//linklst_data 为链表中元素的数据

int linklst_point[MAXN] ;//linklst_point 为链表中元素的指针

int head = -1;/*链表的头指针*/

void del_by_data(int del_data)//删除一个含有 del_data 的元素

{

int p=head,pre=-1;//从头开始遍历链表

while(p != -1)

{

if(linklst_data[p] == del_data)//如找到了要删除的值,则进行删除操作

{

if(p == head)//如果删除的是头指针,则更新头指针

head = linklst_point[head];

else//这个 else 等同于 if (pre != 1)因为当 p!=head 时 p 前面已经有元素了

linklst_point[pre] = linklst_point[p];

linklst_data[p] = -1;//删除 p 指向的元素的值*/

linklst_point[p] = -1;

return ;/*结束删除操作*/

}

pre = p;

p = linklst_point[p];

}

Page 95: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

}

void add_front(int add_data)//在链表前段加入元素

{

int p = 1;

while(linklst_data[p] != -1 && p < MAXN) //找一空位存储数据,这里可以优化

++p;

linklst_data[p] = add_data; //将要加入的节点选好的空位赋值

linklst_point[p] = head; //将当前加入元素的指针指向 head

head = p; //使当前的元素成为链表头指针

}

void add_rear(int add_data)//在链表末尾加入元素

{

int p = 1,pre;

while(linklst_data[p] != -1 && p < MAXN )//找到空位

++p;

linklst_data[p] = add_data; //该空位赋值

if( head != -1 ) //链表不为空

{

pre = head;//找到链表中的最后一个元素*/

while(linklst_point[pre] != -1)//找到空指针

pre = linklst_point[pre];

linklst_point[pre] = p;//将当前链表中最后一个元素的指针指向要加入的元素

}

else//否则直接对 head 所指的元素赋值

head = p;

return ;

}

void output()

{

int p=head;

cout<<"list is: ";

while(p != -1)

{

cout<<linklst_data[p]<<" ";

p = linklst_point[p];

}

cout<<"\n";

}

void init()//初始化数组指针值

{

for(int i = 0;i < MAXN;i++)//链表中的空值设定为-1

{

linklst_point[i] = -1;

linklst_data[i] = -1;

}

}

int main()//演示链表的操作

{

int ins,data;

init();

while(1)

{

cout<<"1.insert a value in front \n";

cout<<"2.insert a value in rear \n";

cout<<"3.delete a value \n";

cout<<"4.quit \n";

cin>>ins;

switch(ins)

{

case 1:

Page 96: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

cout<<"please insert a value :";

cin>>data;

add_front(data);

break;

case 2:

cout<<"please insert a value :";

cin>>data;

add_rear(data);

break;

case 3:

cout<<"please insert a value :";

cin>>data;

del_by_data(data);

break;

default:

return 0;

}

system("cls");//清空屏幕,此命令只能在 Window 平台下使用

output();

}

return 0;

}

数组仿真链表的优化

在上面的程序中,每次加入一个新值都要在数组当中寻找一个空位来储存数据,这样做会有一个问题,就是当链表中加入了

n 个元素时,每一次都会用线性的时间复杂度去寻找空位,造成了很大的时间上的浪费,因此需要进行优化(本节内容涉及堆栈

的应用,请在学完堆栈的内容后再研究本节内容)。

用一个堆栈记录所有没有被加入到链表中的数组元素下标,栈的元素可以初始化为 1…MAXN-1,以方便使用。每次对链表

进行删除操作时,再将删除的数组元素所对应的数组下标入栈,下次再添加新元素进链表时,只需取出栈顶的数组下标即可,因

为这个下标对应的数组元素显然是空的。

这个优化方法将 O(n)寻找空位的过程直接变成了 O(1)。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

//数组链表的演示程序(栈优化)

#include<iostream>

#include <cstdio>

#define MAXN 100001

using namespace std;

int linklst_data[MAXN] ;

int linklst_point[MAXN] ;

int stack[MAXN];//用于记录没有加入链表的数组下标的栈

int head = -1;//链表的头指针

int stack_head = 0;//栈顶指针

void del_by_data(int del_data)//删除一个含有 del_data 的元素

{

int p=head,pre=-1; //从头开始遍历链表

while(p != -1)

{

if(linklst_data[p] == del_data)//找到要删除的值,进行删除操作

{

if(p == head)//如果删除的是头指针,则更新头指针

head = linklst_point[head];

else//这个 else 等同于 if (pre != 1),因为当 p!=head 时 p 前面已经有元素了

linklst_point[pre] = linklst_point[p];

linklst_data[p] = -1;//删除 p 指向的元素的值

Page 97: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

linklst_point[p] = -1;

stack[--stack_head] = p;//将删除元素的数组下标入栈

return ;

}

pre = p;

p = linklst_point[p];

}

return ;

}

void add_front(int add_data)//在链表前段加入元素

{

int p = 1;

p = stack[stack_head++];//直接取出栈中的空位

linklst_data[p] = add_data;//将要加入的节点选好的空位赋值

linklst_point[p] = head; //将当前加入元素的指针指向 head

head = p; //使当前的元素成为链表头指针

}

void add_rear(int add_data)//在链表末尾加入元素

{

int p = 1,pre;

p = stack[stack_head++];//直接取出栈中的空位

linklst_data[p] = add_data; //对要加入的节点的空位进行赋值

if( head != -1 ) //链表不为空

{

pre = head;//找到链表中的最后一个元素

while(linklst_point[pre] != -1)

pre = linklst_point[pre];

linklst_point[pre] = p;//将当前链表中最后一个元素的指针指向要加入的元素

}

else//否则直接对 head 所指的元素赋值

head = p;

}

void output()

{

int p=head;

while(p != -1)

{

cout<<linklst_data[p]<<" ";

p = linklst_point[p];

}

cout<<"\n";

return ;

}

void init()//初始化数组指针值

{

for(int i = 0;i < MAXN;i++)

{

linklst_point[i] = -1;

linklst_data[i] = -1;

stack[i] = i + 1;//从 1 到 MAXN 给栈加入数组下标

}

return ;

}

int main()//演示链表的操作

{

int ins,data;

init();

while(1)

{

Page 98: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

cout<<"1.insert a value in front \n";

cout<<"2.insert a value in rear \n";

cout<<"3.delete a value \n";

cout<<"4.quit \n";

cin>>ins;

switch(ins)

{

case 1:

cout<<"please insert a value :";

cin>>data;

add_front(data);

break;

case 2:

cout<<"please insert a value :";

cin>>data;

add_rear(data);

break;

case 3:

cout<<"please insert a value :";

cin>>data;

del_by_data(data);

break;

default:

return 0;

}

system("cls");//此命令只能在 Windows 平台下使用

output();

}

return 0;

}

指针仿真链表

作为比较,使用指针方式重写以上的程序。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

//使用指针模拟链表数据结构

#include<iostream>

#include <cstdio>

using namespace std;

struct node

{

int data;

node *next;

};

node *head={NULL};

void del_by_data(int del_data)//从链表头向后查找,找到 del_data 并删除

{

node *p = NULL,*pre = NULL;

p = head;

while(p != NULL)

{

if(del_data == p->data)

{

if(pre != NULL)

pre->next = p->next;

if(p == head)

head = p->next;

delete p;

Page 99: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

return ;

}

pre = p;

p = p->next;

}

return ;

}

void add_rear(int add_data)//在链表末尾加入一个元素

{

if(head != NULL)

{

node *add_point,*tmp_point;

add_point = new node;

add_point->data = add_data;

add_point->next = NULL;

tmp_point = head;

while(tmp_point->next != NULL)

tmp_point = tmp_point->next;

tmp_point->next = add_point;

}

else

{

head = new node;

head->data = add_data;

head->next = NULL;

}

}

void add_front(int add_data)//在链表前端加入一个元素

{

if(head != NULL)

{

node *add_point;

add_point = new node;

add_point->data = add_data;

add_point->next = head;

head = add_point;

}

else

{

head = new node;

head->data = add_data;

head->next = NULL;

}

}

void output()

{

node *p;

p = head;

while(p != NULL)

{

cout<<p->data<<" ";

p = p->next;

}

cout<<"\n";

}

int main()//演示链表的操作

{

int ins,data;

while(1)

{

cout<<"1.insert a value in front \n";

Page 100: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

cout<<"2.insert a value in rear \n";

cout<<"3.delete a value \n";

cout<<"4.quit \n";

cin>>ins;

switch(ins)

{

case 1:

cout<<"please insert a value :";

cin>>data;

add_front(data);

break;

case 2:

cout<<"please insert a value :";

cin>>data;

add_rear(data);

break;

case 3:

cout<<"please insert a value :";

cin>>data;

del_by_data(data);

break;

default:

return 0;

}

system("cls");//此命令只能在 Windows 平台下使用

output();

}

return 0;

}

指针与数组链表的比较

一个常见的编程问题:遍历同样大小的数组和链表,哪个比较快?如果按照某些教科书上的分析方法,你会得出结论,这两

者一样快,因为时间复杂度都是 O(n)。但是在实践中,这两者却有极大的差异。通过下面的分析你会发现,其实数组比链表要

快很多。

首先介绍一个概念:memory hierarchy (存储层次结构),电脑中存在多种不同的存储器,各存储器的平均存取速度相差很

大,如表 1.1 所示。

表 1.1

存储器名称 存取速度

CPU 寄存器(CPU Registers) 0~1 个 CPU 时钟周期

CPU L1 缓存(L2 CPU caches) 3 个 CPU 时钟周期

CPU L2 缓存(L2 CPU caches) 10 个 CPU 时钟周期

内存(RAM) 100 个 CPU 时钟周期

硬盘(Disk) 大于 1000000 个 CPU 时钟周期

各级别的存储器速度差异非常大,CPU 寄存器速度是内存速度的 100 倍! 这就是为什么 CPU 生产厂家发明了 CPU 缓存。 而

这个 CPU 缓存,就是数组链表和指针链表的区别的关键所在。

CPU 缓存会把一片连续的内存空间读入,因为数组结构是连续的内存地址,所以数组全部或者部分元素被连续存在 CPU 缓

存里面,平均读取每个元素的时间只要 3 个 CPU 时钟周期。而链表的节点是分散在堆空间里面的,这时候 CPU 缓存帮不上忙,

只能是去读取内存,平均读取时间需要 100 个 CPU 时钟周期。 这样算下来,数组访问的速度比链表快 33 倍!(以上皆为理论数

字,具体的数字视 CPU 型号及环境不同而略有差异)

因此,程序中尽量使用连续的数据结构,这样可以充分发挥 CPU 缓存的威力。 这种对缓存友好的算法称为 Cache-oblivious

algorithm, 有兴趣可以参考相关资料。再举一个简单例子,如表 1.2。

Page 101: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

表 1.2

//程序 1

#include <iostream>

#define N 1000

using namespace std;

int main()

{

int a[N];

int b[100][N];

int c[N];

for(int i=0;i<N;i++)

for(int j=0;j<N;j++)

for(int k=0;k<100;k++)

c[i]+=a[k]*b[k][j];

// B[k][j]非逐行连续读取

cout<<clock()<<endl;

system("pause");

}

//程序 2

#include <iostream>

#define N 1000

using namespace std;

int main()

{

int a[N];

int b[100][N];

int c[N];

for(int i=0;i<N;i++)

for(int k=0;k<100;k++)

for(int j=0;j<N;j++)

c[i]+=a[k]*b[k][j];

// B[k][j]为逐行连续读取

cout<<clock()<<endl;

system("pause");

}

虽然两者执行结果一样,算法复杂度也一样,但是你会发现第二种写法要快很多。

总结一下, 各种存储器的速度差异很大,在编程中绝对有必要考虑这个因素。 比如,内存速度比硬盘快 1 万倍,所以程序

中应该尽量避免频繁的硬盘读写;CPU 缓存比内存快几十倍,在程序中尽量多加利用。

为了验证理论的正确性,可以通过随机化读写的测试来比较两者的差异。

测试数据如下:

data1 100000 次随机插入删除操作,数据在[0,32767]之间;(删除操作较少)

data2 100000 次随机插入删除操作,数据在[0,256]之间;(删除较多)

data3 100000 次随机插入删除操作,数据在[0,32]之间;(删除比较频繁)

data4 1000 次随机插入删除操作,数据在[0,32767]之间;

data5 1000 次随机插入删除操作,数据在[0,256]之间;

data6 1000 次随机插入删除操作,数据在[0,32]之间;

data7 10 次随机插入删除操作,数据在[0,32767]之间;

data8 10 次随机插入删除操作,数据在[0,256]之间;

data9 10 次随机插入删除操作,数据在[0,32]之间;

Cena 测试结果如图 1.3 所示。

Page 102: 本书包含全部题目的源代码和测试数据,以及多达...本书包含全部题目的源代码和测试数据,以及多达110多页的扩展资料电子稿 扫描书后的二维码即可下载资源包,或者凭购书单号与我联系qq:110289732

图 1.3

以上测试在 Thinkpad T60 :2.0GHz Dou CPU,1GB RAM windows XP SP3 系统环境下进行。