为直观起见,当输出一组组合数后,若最后一位为1,也应作一
次回溯(若不回,便由上述回溯条件处理)。
算法如下:
procedurecomb2(n,r,a[m]:
integer);
varj,ri:
integer;
begin
ri=1;a[1]=n;
repeat
if(ri<>r){没有搜索到底}
thenif(ri+a[ri]>r){是否回溯}
thenbegin
a[ri+1]:
=a[ri]+1;ri:
=ri+1
end;
elsebegin
ri:
=ri-1;a[ri]:
=a[ri]-1{回溯}
end;
elsebegin
forj:
=1tordowrite(a[j]);
writeln;
if(a[r]=1){是否回溯}
thenbegin
ri:
=ri-1;a[ri]:
=a[ri]-1{回溯}
end;
elsea[ri]:
=a[ri]-1end;
until(a[1]<>r-1)
end;
例9:
八皇后问题(详见清华版《PASCAL程序设计》P165)
练习:
清华版《PASCAL程序设计》P172习题7.19跳马问题和7.20迷宫问题、四色问题
例10:
第三届初中组二、3
4.贪婪法
一种可以快速得到满意解(但不一定是最优解)的方法。
方法的“贪
婪”性反映在对当前情况,总是作最大限度的选择。
即满足条件的均
选入,然后分别展开,最后选得一个问题解。
这方法不考虑回溯,也不考虑某次选择并不符合最优条件,但最终能得到最优结果的选择。
例11:
用贪婪法求解“货郎担问题”。
所谓货郎担问题是指,给定一个无向图,并已知各边的权,在这样的图中,要找一个闭合回路,使回路经过图中的每一个点,而且回路各边的权之和为最小,如右图所示。
如
a,b,c,d,e,f这6个点,坐标分别为(0,0)、(4,3)、
(1,7)、(15,7)、(15,4)、(18,0),这是最优解。
(算法程序略)
求解步骤一般如下:
(1).计算各点间距离(邻接矩阵);
(2).距离关系表排序;
(3).贪婪法选择边,入选的边应符合如下两条件:
每个端点不能
联系两条以上的边;不会使入选的边形成回路,除非入选正好是边数等于端点数。
为此引入端点关系表
(4).如果由⑶得不到解,应调整距离关系表中距离相同的边的次
序,再试;
(5).若有解,则按端点关系表给出回路的轨迹。
例12:
旅行路线选择。
设有n个城市(或景点),今从某市出发遍历各城市,使之旅费最少(即找出一条旅费最少的路径)。
例13:
背包问题。
从n件不同价值、不同重量的物品中选取一部
分,在不超过规定重量的原则下,使该部分价值最大。
例14:
第三届初中组一、8;第七届高中组四、2
5.分治法
是应用十分广泛的一种算法设计方法,其基本思想是把一个规模为n的问题分成若干个规模较小的问题,使得从这些较小问题的解易于构造出整个问题的解。
例15:
找一个集合的极大元和极小元。
集合S含有n个元素,其中n=2k(k>1)。
算法思路如下:
procedureMAXIM(S);
begin
1.if||S||=2{集合S的元素个数为2}thenbegin
2.设S={a,b};
3.return(MAX(a,b),MIN(a,b))
end
elsebegin
4.把S分为两个子集Si和S2,各有一半元素;
5.(max1,min1)—MAXMIN(Si);
6.(max2,min2)—MAXMIN(S2);
7.return(MAX(max1,max2),MIN(min1,min2))
end
end;
注意到,需要在集合S的元素间进行比较的步骤只是步骤3(在此比较两个元素)及步骤7(在此把max1与max2及min1与min2进行比较),设T(n)是用MAXMIN在一个具有n个元素的集合中找极大元和极小元时,需要在S的元素间进行比较的次数。
显然,T
(2)=1,如果n>2,则T(n)是在大小为n/2的集合上两次调用MAXMIN(第5、6步所需的比较次数加上第7步的两次比较所得的总次数,即
元素间进行比较的次数来说,这种算法是最优的。
例16:
设有n个选手的循环比赛(n=2m),要求每名选手要与其他n-1名选手都赛一次。
每名选手每天比赛一次,循环赛共进行n-1天,要求每天没有选手轮空。
例如m=3,见下表,用分治法将(2m*2m)矩阵
AB
分成四块,每块是(2m-1*2m-1)的矩阵,它应是对称的(),再对A
BA
与B均是(2m-1*2m-1)的矩阵分成四块,直至(2*2)的矩阵定出每个元素的值,再按对称关系构造出比赛表。
算法程序略。
选
手
、送
第一
天
第二
天
第三
天
第四
天
第五
天
第六
天
第七
天
1
2
3
4
5
6
7
8
2
1
4
3
6
5
8
7
3
4
1
2
7
8
5
6
4
3
2
1
8
7
6
5
5
6
7
8
1
2
3
4
6
5
8
7
2
1
4
3
7
8
5
6
3
4
1
2
8
7
6
5
4
3
2
1
四•排序
1.插入排序
设待排序的记录为(Ri,R2,…,Rn),插入排序的基本思想是把记录
Ri(2
图4.1直接樋人推序
得长度为I的一系列记录也是排好序的。
(1).直接插入排序
若记录Ri,R2,…,Ri-i已按关键码有序,即对应的关键码有KgK2«y
Ki-i,将K与Ki-i,Ki-2,…,Ki依次比较,一旦Ki大于或等于某个Kj(Kjw
i-1)时,把Rj+1,Rj+2,…,Ri-1后移一一个位置,并将Rj插在第j+1个位置上。
在直接插入排序中,若待排序的记录已有序,总共比较C=n-1
次。
直接插入排序可用顺序存储和链接存储。
囲乳2链接存储的直接捕人排序
例17:
清华版《PASCAL程序设计》P171习题7.8
(2).二分法插入排序
在直接插入算法中,确定Ri(i=2,…,n)插入位置的方法是将它
与前面i-1个记录依次比较,由于前i-1个记录已按关键码有序,
可以用二分法较快地找到Ri的插入点。
首先取m
1i1
2
比较Rm与Ri的关键码。
若Ri的关键码小于Rm的关键码,则在
[R1,R2,…,Rm-l]范围内再用二分法,否则在[Rm+1,…,Rn]范围内再用二分法。
如此反复,直至最后找到Ri的插入位置。
当n较大时,二分法插入排序比较次数比直接插入排序的最多比
较次数丄」少得多,但比直接插入排序的最少比较次数
2
(n-1)大。
二分法插入排序只能采用顺序存储。
2.选择排序
又称为直接选择排序。
在待排序的n个记录中先选出关键码最大的记录,将它送到第n个位置上,然后再从其余n-1个记录中选出关键
码最大的记录送到第n-1个位置上,…,直至选择了n-1个记录。
选择排序比较次数共有C=(n-1)+…+2=n(n-1)/2(详见清华版
《PASCAL程序设计》P145-147)
3.冒泡排序
这种排序方法从表的一端开始,依次比较相邻两个记录的关键码
R[i].key和R[I=1].key,若R[i].key>R[l+1].key,则交换R[i]和R[i+1]。
假设从表的左端开始,当i从0到n-2对表扫描一趟后,具有最大关
键码的记录将被移到最右边的位置上。
作n-1趟扫描将完成对n个记录的排序,但完成n个记录的排序不一定都要进行n-1趟扫描,如果在某趟扫描中没有发生交换,则排序工作已完成,此后的扫描便不必进行。
如果待排序的诸记录在排序前已按关键码排好序,贝U用冒泡排序算法只需一趟扫描便完成排序,比较次数为C=n-1。
如果待排序的记录完全反序,即已由大至小排序,则比较次数C=n(n-1)/2(详见清华版《PASCAL程序设计》P148-150)
4.希尔排序
在直接插入排序和冒泡排序中,只比较相邻的记录,一次比较至多
将记录移动一个位置。
希尔排序是对此两种排序的推广。
算法先将记录按某个增量d分成若干组,每组中记录的下标相差d。
对每组中的
记录用某种方法(前两种)进行排序,然后再用一个较小的增量对记录进行分组,在每组中再进行排序,…,当增量减至1时,整个记录
被分成一组,排序完成。
例如:
设待排序的记录的关键码为:
9432409099804621692864738554
取增量序列为:
5,3,1,当增量d=5时,整个记录被分成五组:
擁纽中的元累分别为匕
第一组194*90*64
第二组戲・46占3
翳三组r40t2US5
第四组;90,仙,54
第五组*9,48各组摆呼后”结集为:
若d=3血牛记录被分就三组倍组排序后的结果孙_
t—}—rI—I—Innf~1tI
4:
26215412抽M須738590肌畀94
再取增量为1,排序结果为:
2128324046546469738085909499
5.快速排序
选取表中某个记录的关键码K作为基准,将表划分成左、右两个
子表:
左子表中各记录的关键码都小于等于K,右子表中各记录的关
键码都大于等于K,然后用同样的方法递归地处理这两个子表。
比较次数C
(1)=0,C
(2)=2+C
(1)=2,C(3)=3+C
(2)=5,…,
C(n)=n+C(n-1)=(n+2)(n-1)/2
170
+
&03
061
512
908
0«7
Z75
653
4Z6
B12
765
琴
i
170
503
1X.
061
512
908
0B7
275
esa
426
612
J
703
j
j
503
170
IM
061
512
908
OS7
275
6&3
426
«12
?
65
703
170
商
061
T*
r
0B7
j
512
伽
503
«12
765
703
[170
061
375
C$7
][S08
512
653
426
S03
612
765
70S]
ffiA-3快議排序的划井过稈
上图说明了13个记录的表进行第1次划分的过程。
取表的中间一个记录的关键码275作为基准,划分时用两个指针变量i和j扫描表,
变量i从表头向右扫描,直至遇到一个关键码大于或等于基准的记录;变量j从表尾向左扫描,直至遇到一个关键码小于或等于基准的记录;接着交换Ri和Rj。
指针i和j继续向两端前进,进行比较、交换。
当i和j交叉(即i处于j的右边)时,表中各记录都与基准比较过,表被分成左、右两部分,左边各记录的关键码小于或等于右边各记录的关键码。
6.堆排序
是对选择排序的改进。
为了避免在选择排序中出现的每趟选择最大关键码的记录时的某些重复的比较,可以采用树形选择方法。
设待排
记录的关键码)都大于或等于它的子女结点的值,因此堆的根在树中具有最大的关键码。
这样得到的序列将是非递减的。
7.归并排序
是把两个或两个以上已排好序的表组合在一起,产生一个新的排好
序的表。
方法如下:
(1).若两个表有一个为空,则无须归并
(2).若两个表都非空,则比较p和q(两个表的头指针)所指结点
的关键码,将较小者插入第三个表中,并前进相应的指针。
重复这一步;
(3).若有一个表先到达表尾,则把另一个表的剩余部分插入到第
三个表中。
例18:
第五届初中组五
8.各种排序方法的比较
直接插入排序在记录数目较少且是基本有序时是最佳的;选择排序是比较次数不依赖记录的初始排列,且记录移动较少;冒泡排序的性能较差;希尔排序是对插入排序和冒泡排序的推广,在记录移动时可能消去多个反序,算法时间可能较短;快速排序是目前所有排序中平均性能最好的方法,但在最坏情况下年耗时间最多;堆排序在n较大时非常有效;归并排序可以看成是插入排序的扩充。
例19:
第六届初中组1、10
9.其它排序方法
例20:
第七届初中组四、1
五.查找
查找又称为检索,它往往是程序中最耗费时间的操作,使用何种存储结构和查找算法对程序的性能有本质的差别,因此,在讨论各种查找算法时都应指明所用的存储结构。
{定义记录类型}
{值为关键字的域}
{记录的其它域}
END;sqlist=ARRAY[0..n]OFelement;
1.顺序查找又称为线性查找,对给定的关键码值K,从表的一端开始,依次检查表中每个记录的关键码,直至找到所需灌到达表的另一端。
FUNCTIONseqsrch(r:
sqlist;K:
keytype):
integer;
Beginr[0].key:
=K;i:
=n;whiler[I].key<>Kdoi:
=i-1return(i)
end;
{K为给定值,函数seqsrch值为关键码等于K的记录在表r中的序号,值为零时表明查找不成功}
(参考清华版《PASCAL程序设计习题与选解》P35习题7.23)
2.二分查找
又称为折半查找。
当表中记录按关键码有序时(有序表),可以采
用二分查找法。
先确定待查记录的范围(区间),然后逐步缩小范围直
到找到或找不到该记录为止。
用两个指针low和hig分别指示待查元
素所在范围的下界和上界,并用指针mid指示中间元素
lowhig
mid。
在开始进行查找时,low和hig的初值分别指向
2
第1个和第n个元素。
FUNCTIONbinsrch(r:
sqlist;K:
keytype):
integer;
Begin
low:
=1;hig:
=n;
whilelow<=higdo{判别区间大小}
begin
mid:
=(low+hig)div2;
case
k>r[mid].key:
low:
=mid+1;
k=r[mid].key:
return(mid);{查找成功}
khig:
=mid-1
end;return(O)
end;
(参考清华版《PASCAL程序设计习题与选解》P35习题7.24)例21:
第七届初中组一、19;第六届初中组一、14
3.分块查找
又称为索引顺序查找,是顺序查找的改进方法。
是把表分成若干块,
每块中记录的存储顺序是任意的,但块与块之间必须按关键码有序,
4.14并块杳掳的减段累引
即第一块中任一记录的关键码都小于第二块中各记录的关键码,如此类推。
这种查找方法要求除表本身外,尚需建立一个索引表,索引表中的一项对应于表的一块,它含有这一块中的最大关键码的指向块内第一个记录位置的指针,索引表中各项按关键码有序。
分块查找的过程分两步进行:
先查找索引表(可以用上述两种方法)确定要找的记录在哪一块;然后再在相应的块中查找。