算法总结
我大致的把一些基础的内容总结了一下,大家可以参考一下,
把一些掌握了的再巩固一下,没掌握的了解一下。
可能会有一些知识点我忘了列出来,大家可以补充一下
*代表我认为比较重要的,!代表我认为较难的
图论:
**一些基本的图论知识和概念,DFS,BFS
*Shortest path
*MST (包括MST的一些扩展,!!kth-MST,!!树形图,!度限制MST,!比率MST)
*割顶,割边,强连通分支
!*网络流
!费用流
*二部图基数匹配
!二部图权匹配
!!任意图匹配
!欧拉回路
DP:
动态规划很灵活,没有什么固定的知识点,但还是有一些经典的模型应该掌握一下
*LCS
!*LIS(in O(nlogn))
*树形dp
!基于行/列状态压缩的dp
!!四边形不等式
数据结构:
**队列,栈,链表
*图和图的各种表示
*并查集
*堆
**树,二叉树
二叉搜索树
!*区间树
数论:
**基本的数论知识,模算术
**extended euclidean algorithm
*求质数表
!大数分解和素数判定
!中国剩余定理
计算几何:
**向量几何的基本知识和演算,叉积,点积
*线段交点
*凸包
*点在多边形内的判定
!多边形的交和并
!!!Voronoi图
组合数学:
**加法原理,乘法原理
*母函数
*容斥原理
!polya公式
附录:
1.算符优先法求解表达式求值问题
const maxn=50;
var
s1:array[1..maxn] of integer; {s1为数字栈}
s2:array[1..maxn] of char; {s2为算符栈}
t1,t2:integer; {栈顶指针}
procedure calcu;
var
x1,x2,x:integer;
p:char;
begin
p:=s2[t2]; dec(t2);
x2:=s1[t1]; dec(t1);
x1:=s1[t1]; dec(t1);
case p of
'+':x:=x1+x2;
'-':x:=x1-x2;
'*':x:=x1*x2;
'/':x:=x1 div 2;
end;
inc(t1);s1[t1]:=x;
end;
procedure work;
var c:char;v:integer;
begin
t1:=0;t2:=0;
read(c);
while c< >';' do
case c of
'+','-': begin
while (t2 >0) and (s2[t2]< >''('') do calcu;
inc(t2);s2[t2]:=c;
read(c);
end;
'*','/':begin
if (t2 >0) and ((s2[t2]='*') or (s2[t2]='/')) then calcu;
inc(t2);s2[t2]:=c;
read(c);
end;
'(':begin inc(t2); s2[t2]:=c; read(c); end;
')':begin
while s2[t2]< >''('' do calcu;
dec(t2); read(c);
end;
'0'..'9':begin
v:=0;
repeat
v:=10*v+ord(c)-ord('0');
read(c);
until (c<'0') or (c>'9');
inc(t1); s1[t1]:=v;
end;
end;
while t2 >0 do calcu;
writeln(s1[t1]);
end;
2.带权中位数
我国蒙古大草原上有N(N是不大于100的自然数)个牧民定居点P1(X1,Y1)、P2(X2,Y2)、 …Pn(Xn,Yn),相应地有关权重为Wi,现在要求你在大草原上找一点P(Xp,Yp),使P点到任 一点Pi的距离Di与Wi之积之和为最小。
即求 D=W1*D1+W2*D2+…+Wi*Di+…+Wn*Dn 有最小值
结论:对x与y两个方向分别求解带权中位数,转化为一维。
设最佳点p为点k,则点k满足:
令W为点k到其余各点的带权距离之和,则
sigema( i=1 to k-1) Wi*Di < = W/2
sigema( i=k+1 to n) Wi*Di < = W/2
同时满足上述两式的点k即为带权中位数。
3.求一序列中连续子序列的最大和
begin
maxsum:=-maxlongint;
sum:=0;
for i:=1 to n do begin
inc(sum,data);
if sum >maxsum then maxsum:=sum;
if sum< 0 then sum:=0;
end;
writeln(maxsum);
end;
小议竞赛的准备和考试技巧
1、良好的心态。无论竞赛多么重要,都不要在考试的时候考虑考试之前或以后的事,这很重要……
2、充足的睡眠和营养。竞赛之前睡好觉,吃好饭,多吃甜食(据我们老师说在吃甜食后15分钟和2小时会各出现一次血糖高峰,会有比较好的竞技状态)。还有,宁可撒尿也不要口渴……口渴会严重影响思路……而尿素有兴奋作用,有利无害……
3、正确的时间安排。一般来说应该先想完所有的题再开始做,但有的题想不出来的时候一定要给想出来的题留出时间。
4、算法的学习。一般的DFS/BFS、贪心、各种DP、二分法、排序、lr论文中的各种奇特算法、最短路、最长路、图的DFS/BFS、最大匹配,最大最小匹配、最佳匹配、差分限制系统、最长不xx子序列、高斯消元、数论算法……
5、数据结构的学习。Hash、并查集、邻接表、边表、堆、树状数组和线段树及它们的多维形式、链表、单词查找树……
6、关于混分:超时的搜索/DP往往能比错误的贪心得到更多的分。
7、数学很重要。比如母函数……
8、专用的方法胜于通用的方法。
9、好的题目往往不能直接用经典算法解决。
10、真正难题的标程往往很短。
11、如果n很大,用汇编写的O(n^2)的程序绝对不如用QB写的O(n)的程序快。
12、如果n很小,利用压缩存储提高速度的O(n^2)的算法有可能比一般的O(n)算法快。
13、如果一个数学问题很复杂,那么看结果找规律有可能比数学推导快。
14、不要总把logn忽略掉。
15、即使是多项式算法,有时也可以加入很有效的剪枝。
16、做一道好题胜过做n道烂题,但如果不做烂题,可能会影响做好题的速度。
常用数据结构简介
1、线性表
显然是最常用的:P 也是最简单的,而且有O(1)的查找第k元素
有序的线性表有很多一般线性表没有的性质,关键时刻用处很大……
2、链表
个人很讨厌,但的确很有用,毕竟有O(1)的插入/删除
3、堆
虽然有很多很好的堆,但由于我的懒惰,至今我只使用过简单的二叉堆。
堆的主要用处应该在于动态地查找最大/小元素、实现优先队列,比如Dijkstra和一些模拟题。
4、并查集
划分等价类大概非他莫属了……这也是至今为止,我唯一觉得源代码比文字解说更容易看懂的数据结构……
5、Treap
这是我目前感觉实现最简单的一种平衡二叉树……
Treap=Tree+heap,一个Treap是一个按照主要关键字有序的排序二叉树,同时也是按照一个随机关键字有序的二叉堆。随机关键字的作用是维持Treap的平衡。
在Treap中插入一个节点时,先生成该节点的随机关键字,然后按照主要关键字,把Treap当作一般的排序二叉树进行插入,最后进行一些旋转使Treap仍然是一个合法的堆。
6、Hash
用来查找应该比平衡二叉树高效,竞赛中用起来很爽……
7、树状数组及类似结构
按我理解,这些东西的核心思想也许是“任何自然数都可以表示成若干个不同的2的幂的和”,由于n以内2的幂只有logn个,存储一些和2的幂有关的东西有时可以简化计算。比如O(nlogn)的一种求最近公共祖先的联机算法,就是保存每个节点的所有2的幂阶祖先。
8、单词查找树
当 年我很崇拜这个东西……理论上单词查找树的各种操作的复杂度都达到了下界,但后来发现它的实际效果实在很差。即使真的只是用来查找单词,效果有时也不如平 衡二叉树。单词查找树查找的复杂度是O(单词长度),而平衡二叉树的复杂度是O(logn)*比较复杂度。不难证明如果单词完全随机,比较复杂度趋于常 数。由于单词查找树实现起来比较麻烦,复杂度系数比较大,因此实际效果还是平衡二叉树更好些……当然,如果单词足够随机,最好的或许是hash……总而言 之,单词查找树实在不好使,不推荐:(
9、线段树
如果不加离散化,编程并不复杂,求求面积体积蛮有用的……
10、后缀树
至今仍然不会编:(可用于单一源串和多个目标串的模式匹配,但竞赛中往往可以用同时匹配多个目标串的有限自动机代替……
应用举例:
图:
1、静态稀疏图,一般用链表实现的边表。
2、静态稠密图,通常用邻接矩阵也无伤大雅
3、动态图,想象中的图/边表+邻接矩阵/邻接矩阵/平衡二叉树实现的边表/……
字典:
1、线性表,实现最简单,效率很差
2、hash,效率高但依赖hash函数取法,空间耗费大,个人感觉实现起来不爽
3、单词查找树,写起来不太难,效率较低而极稳定
4、平衡二叉树,一般来说,竞赛的时候就算了吧……但平时写程序用用蛮不错……
队列:
1、线性表,大概是用得最多的吧。
2、链表,空间可以动态分配,但……平时我们用的队列大多数都是longint队列吧……longint和指针一样大,用链表不太划算……
3、堆,做优先队列的一个不错的选择。不过二叉堆维护的复杂度比简单的队列高,需要注意……
Editor……
“拆查集”,就是给一个图,不停地删点和求当前连通分支情况。可以把操作反过来用并查集……
最后说一句……恰当地运用线性表和链表能达到平衡二叉树所达不到的效果……
NOIP中的测试数据设计
程序编写完毕之后,我们都希望知道:它是否正确?是否满足题目的要求?还存在什么问题没有?……不能写完了就把它丢在一边,置之不理。我们还需要对它进行 检查、调试。特别是近年来,奥林匹克分区联赛的评分都采取“黑箱操作”制,只根据问题本身检查程序是否正确,不对程序本身进行分析;只看程序的运行结果, 不看程序的具体实现;不象其它学科竞赛那样给过程分。所以,在信息学分区联赛中对程序进行调试显得尤其重要。调试程序离不开测试数据。我们需要根据题目要 求,设计一些有针对性的数据(输入数据),以及程序运行这些数据之后应该得到的结果(期望结果)。用这些数据(输入数据)来运行程序,看程序的运行结果是 否与我们的期望结果一致;如果程序的运行结果与我们的期望结果不一致,那么,说明程序可能存在错误,我们需要对它进行修改。
测试数据在程序的调试过程中起着关键性的作用。一个好的测试数据不但要能够准确地发现错误,还要能够发现尽可能多的错误。我们在设计测试数据时,需要注意以下一些原则:
(1)测试数据必需包含输入数据和期望结果两部分。
(2) 测试数据既要能够检查用户在合法操作下程序的运行情况,又要能够检查用户在非法操作下程序的运行情况。我们在编写程序的时候,由于思维定势的影响,往往都 会只考虑程序的输入按照我们的期望进行,都会无意识地假设用户的操作是合法的,从而忽略了用户的非法操作,忽略了当程序的输入不是按照我们的期望进行,发 生无效输入的时候程序的运行情况。在程序的实际运行中,许多错误恰恰是由用户的非正常操作或接口数据异常造成的。所以,我们在设计测试数据时,测试数据既 要包括有效的和期望的输入,也要包括无效的和不期望的输入。
(3)测试数据要能够检查程序是否进行了“画蛇添足”的操作。我们平时在编写程序的时 候,往往都会认为程序的功能越多越好,久而久之,都容易养成“画蛇添足”的习惯。在信息学竞赛中,我们必须认真分析题目的要求,明确我们的程序应该实现的 功能;我们设计的测试数据,不但要考虑程序是否做了应该做的操作,也要考虑程序是否做了不应该做的操作。程序的运行不能有负面作用。
(4)测试数 据要有针对性。第一,测试数据要针对错误本身,是为了发现错误而不是为了让自己增加对程序的“满意度”。 第二,测试数据必须针对题目,必须对题目中的每一句话每一个要求进行检测。第三,测试数据应针对数据结构。第四,测试数据必须针对算法,针对重点算法的缺 点,针对易犯的错误。第五,测试数据应针对题目设计者的意图,多想一些评测时可能出现的数据,尽可能使自己的程序面向评委。
(5)测试数据要有广泛性,应能普遍地发现错误。
下面结合信息学奥林匹克分区联赛,简单介绍几种常用的测试数据设计方法。
1、等价类划分。
把程序的输入域(所有可能的输入数据的集合)划分成若干个等价类(即若干个部分或集合),每一个等价类中的任一输入数据,它对程序的作用代表了该等价类中 其它输入数据对程序的作用。换句话说,如果从某个等价类中任选一个输入数据运行程序,不能发现程序有错误,那么,用该等价类中的其它输入数据运行程序,也 不可能发现程序有错误。
用等价类划分方法设计测试数据,通常可以按照如下步骤进行:
(1)划分等价类。
认真阅读题目,明确我们将要编写的程序应该实现的功能,从题目中找出隐含的输入条件(通常是一句话或一个语句),然后将每一个输入条件划分为两个或多个等价类,并将其列表,格式为:
输入条件 合理等价类 不合理等价类
表中合理等价类是指各种正确的输入数据,不合理等价类是指其他错误的输入数据。关于等价类的划分,以下提供几条前人的经验供参考。
①如果某个输入条件规定了取值范围,则可规定一个合理等价类和两个不合理等价类。合理等价类为输入值在规定的范围内,不合理等价类为输入值小于这个范围的最小值和大于这个范围的最大值。如下图
例如输入值是学生的成绩,范围是0~100,我们可以确定一个合理等价类为“0≤成绩≤100”,两个不合理的等价类分别为“成绩<0”和“成绩>100”。
②如果规定了输入数据的一组值,而且程序对不同的输入值做不同的处理,则每个允许的输入值是一个合理等价类,此外还有一个不合理等价类(任何一个不允许的输入值)。
例如,输入条件说明学生的性别可以是男和女两者之一,则分别取这两个值作为两个合理等价类,另外把这两个值之外的任何值作为不合理等价类,一共分为三个等价类。
③如果规定了输入数据必须遵循的规则,可确定一个合理等价类(符合规则)和若干个不合理等价类(从各种不同角度违反规则)。
④如果已划分的等价类中各元素在程序中的处理方式不同,则应将此等价类进一步划分为更小的等价类。
(2)确定测试用例。
测试用例是测试数据的具体实例。根据已划分的等价类,按以下步骤设计测试用例:
①为每一个等价类编号。
②设计覆盖合理等价类的测试用例。这一步要求每一个合理等价类都被测试用例覆盖。在设计过程中,为了减少测试用例的数量,我们选择的每一个测试用例,都要使其尽可能多地覆盖尚未被覆盖过的合理等价类。
③设计覆盖不合理等价类的测试用例。为了更为全面地检查程序可能出现的错误,确保程序万无一失,我们在设计覆盖不合理等价类的测试用例时,必须针对每一个不合理等价类,分别设计测试用例。要求每一个测试用例只能覆盖一个不合理等价类。
2、边界值分析。
在程序设计和代码设计中,往往容易忽略问题的输入域边界或输出域边界,以至造成一些错误,因此,在设计测试数据时,对边界处的处理应给予足够的重视。为检查程序对问题边界附近的处理而专门设计的测试用例,在实际的程序调试中,常常可以取得良好的测试效果。
使用边界值分析方法设计测试数据时一般与等价类划分方法结合起来。但它不是从一个等价类中任选一个例子作为代表,而是将测试边界情况作为重点目标,选取正好等于、刚刚大于或刚刚小于边界值的测试数据。下面提供一些原则供参考:
(1)如果输入条件规定了值的范围,可以选择正好等于边界值的数据作为合理的测试用例,同时还要选择刚好越过边界值的数据作为不合理的测试用例。
例如,输入条件说明学生成绩的取值只能是整数,范围是:0≤成绩≤100。则可以选择正好等于边界值的数据0和100作为合理的测试用例,同时还要选择刚好越过边界值的数据-1和101作为不合理的测试用例。
(2)如果输入条件指出了输入数据的个数,则按最大个数、最小个数、比最小个数少1、比最大个数多1等情况分别设计测试用例。
例如,一个输入文件可以包括1~255个记录,则分别设计有1个记录(最小个数)、255个记录(最大个数)、以及0个记录(比最小个数少1)和256个记录(比最大个数多1)的输入文件的测试用例。
(3)对每个输出条件分别按照以上原则(1)或(2)确定输出值的边界情况。
由于输出值的边界与输入值的边界没有互相对应的关系,所以要检查输出值的边界,并不是每一个问题都可以检查;要产生超出输出值之外的结果也不一定能够做到。但必要时还需试一试。
(4)如果问题的描述中给出的输入域或输出域是个有序集合(如顺序文件、线性表、链表等),则应选取集合的第一个元素和最后一个元素作为测试用例。
3、错误推断。
也 叫错误猜测法,所谓猜错,就是猜测我们的程序中哪些地方容易出现错误,并据此设计测试数据。这种方法主要依赖于我们的直觉与经验,它通常作为其它方法的一 种辅助手段。也就是用其它方法设计测试数据,然后再根据猜测法补充一些测试用例。它的基本思想是列出程序中可能发生的错误情况,根据这些情况选择测试用 例。
4、因果图。
前面介绍的等价类划分法和边界值分析法等都没有考虑到输入情况的组合。这样虽然各种输入条件可能出错的情况已经看到了, 但多个输入情况组合起来可能出错的情况却被忽视了。因果图法就是一种利用图解方法分析输入的各种组合情况,从而设计测试数据的方法。利用这种方法能够设计 出高效的测试数据。
以上介绍的四种测试数据设计方法并不是孤立的,我们在具体的测试数据设计中,要将它们结合起来使用,让我们的测试数据能够更全面地对程序进行检查,确保我们编写的程序万无一失,做到最好。
FIFO分支定界算法初探
分支定界(branch and bound)算法是一种在问题的解空间树上搜索问题的解的方法。但与回溯算法不同,分支定界算法采用广度优先或最小耗费优先的方法搜索解空间树,并且,在分支定界算法中,每一个活结点只有一次机会成为扩展结点。
利用分支定界算法对问题的解空间树进行搜索,它的搜索策略是:
1.产生当前扩展结点的所有孩子结点;
2.在产生的孩子结点中,抛弃那些不可能产生可行解(或最优解)的结点;
3.将其余的孩子结点加入活结点表;
4.从活结点表中选择下一个活结点作为新的扩展结点。
如此循环,直到找到问题的可行解(最优解)或活结点表为空。
从活结点表中选择下一个活结点作为新的扩展结点,根据选择方式的不同,分支定界算法通常可以分为两种形式:
1.FIFO(First In First Out)分支定界算法:按照先进先出原则选择下一个活结点作为扩展结点,即从活结点表中取出结点的顺序与加入结点的顺序相同。
2. 最小耗费或最大收益分支定界算法:在这种情况下,每个结点都有一个耗费或收益。如果要查找一个具有最小耗费的解,那么要选择的下一个扩展结点就是活结点表 中具有最小耗费的活结点;如果要查找一个具有最大收益的解,那么要选择的下一个扩展结点就是活结点表中具有最大收益的活结点。
下面通过一个具体实例加深大家对FIFO分支定界算法的认识。
例:装箱问题 (2001年全国青少年信息学(计算机)奥林匹克分区联赛初中组复赛试题第四题)
[问题描述]
有一个箱子容量为v(正整数,0≤v≤20000),同时有n个物品(0≤n≤30),每个物品有一个体积 (正整数)。要求从 n 个物品中,任取若干个装入箱内,使箱子的剩余空间为最小。
[样例]
输入:
10 一个整数,表示箱子容量
3 一个整数,表示有n个物品
4 接下来n行,分别表示这n个物品的各自体积。
8
5
输出:
1 一个整数,表示箱子剩余空间。
分 析:根据题目要求,要从n个物品中任取若干个装入箱内,使得箱子的剩余空间最小,换句话说就是要尽可能地装满箱子。只要我们找到一种装载方案,在箱子的容 量范围内,它的装载值为最大,即装入箱子的这些物品的体积之和为最大,那么箱子的容量与最优装载值之差就是问题的答案(最小剩余空间)。
要寻找这 样一个最优装载值,我们不妨先考察一个简单的例子。比如:箱子容量v=10,物品个数n=3,第1个物品的体积tiji[1] =4,第2个物品的体积tiji[2] =8,第3个物品的体积tiji[3] =5。利用分支定界算法求解,首先要将问题的解空间组织成一棵树,如图。对于每一个物品,只有装入箱子和不装入箱子两种可能,我们用结点的两个孩子分别表 示这两种可能,规定左孩子表示相应的物品装入箱子,右孩子表示相应的物品不装入箱子。树中结点左上角的数字为该结点对应的权值。例如A的左孩子B表示将第 1个物品装入箱子,B的权值4表示在结点B处,箱子中已装入物品的体积之和为4;而A的右孩子C表示第1个物品不被装入箱子,C的权值0则表示在结点C 处,箱子中已装入物品的体积之和为0。同样,D、F都表示将第2个物品装入箱子,D的权值12表示在结点D处,箱子中已装入物品的体积之和为12,……这 样,解空间树中每一条从根结点到叶子结点的路径就表示一种可能的装载方案(暂时不考虑装载过程中必须满足的条件)。
在搜索过程中,我们设置变量best来保存当前的最优装载值。搜索从根结点A开始, A是当前的扩展结点,此时活结点表中只有一个元素-1(标记本层尾部),best=0。按照分支定界算法的搜索策略,首先产生当前扩展结点A的孩子结点B 和C;其次,在产生结点B和C的过程中,根据一定的条件(限界函数),抛弃不可能产生可行解(或最优解)的结点;再次,将剩下的A的孩子结点加入活结点 表;最后,从当前的活结点表中取出下一个活结点作为新的扩展结点。如此重复,直到活结点表为空。
接下来还有一个问题,在产生当前扩展结点的孩子结点的过程中,孩子结点能否产生可行解(或最优解),怎样进行判断?即需要什么样的限界函数。
仔 细分析题目要求,我们很容易发现,题目中隐含着一个基本条件:装入箱子中的所有物品的体积之和不能超出箱子的容量,即:所装物品的体积之和<=箱子 容量。另外,在对解空间树进行搜索的过程中,如果发现某子树不可能产生比当前最优解还要优的解,我们就没有必要对这棵子树进行搜索。这样处理可以加快搜索 速度,提高程序的性能。令Z为解空间树第i层的一个结点,best的定义如前,ew为当前所
装物品的体积之和。以Z为根的子树中没有叶结点的装载值会超过ew+r,其中r= 为剩余物品的体积之和
。因此,当ew+r<=best时,没有必要去搜索Z的右子树。
根据上面分析,可归纳算法程序如下:
1.输入基本数据;
2.建立活结点表;
3.基本变量初始化;
4.搜索解空间树。
其中,第4步需要进一步求精。
While true do
{检查左孩子}
if 左孩子的权值<=箱子容量 then
if 当前装载体积>最优装载值(best) then
best=当前装载体积
endif
If 不是叶子结点 then
将左孩子结点加入活结点表中
endif
{检查右孩子}
if 可以有一个更好的叶子结点 then
将右孩子结点加入活结点表中
endif
endif
从当前活结点表中取下一个结点作为新的扩展结点
if 到达层的尾部 then
if 活结点表为空 then
输出问题的解
结束对解空间树的搜索
endif
添加尾部标记
从当前活结点表中取下一个结点作为新的扩展结点
产生新的层号i
产生新的r(剩余物品的体积之和)
endif
end{while}
第2步活结点表的建立,可以使用队列来实现。分支定界算法的解空间比回溯算法大得多,因此在建立队列的时候,最好使用链队列,这样可以提高程 序的性能。QBASIC语言不支持指针这种数据类型,如果用QBASIC语言进行编程,链队列的建立将会变得比较困难。所以,下面的参考程序,我们将以 PASCAL语言给出。
PASCAL语言参考程序如下:
program c2001_4(input,output);
type{链队列抽象数据类型说明}
queueptr=^queuenode;
queuenode=record
data:integer;{数据域}
next:queueptr{指针域}
end;
linkedquetp=record
front,rear:queueptr
{front为队头指针,rear为队尾指针}
end;
var
v,n,i,j,x,ew,wt,best,r:integer;
p,s:queueptr;
q:linkedquetp;
tiji:array[1..30]of integer;
flag:boolean;
procedure init_linkedqueue(var q:linkedquetp);
{设置一个空的链队列}
begin
new(q.front);q.rear:=q.front
q.front^.next:=nil
end;
procedure add(var q:linkedquetp;x:integer);
{在已知链队列q中插入队尾元素x}
begin
new(p);p^.data:=x;p^.next:=nil;
q.rear^.next:=p;q.rear:=p
end;
function delete(var q:linkedquetp):integer;
{若链队列q不空,则删除队尾元素并返回该元素,否则返回-2}
begin
if q.front=q.rear
then delete:=-2
else begin
s:=q.front^.next;
q.front^.next:=s^.next;
if s^.next=nil then q.rear:=q.front;
x:=s^.data;
dispose(s);
delete:=x
end
end;
function empty(q:linkedquetp):boolean;
{若链队列q为空队列,则返回函数值“true”,否则返回函数值“false”}
begin
if q.rear=q.front
then empty:=true
else empty:=false
end;
begin
{输入}
write('Please input V:');readln(v);
write('Please input N:');readln(n);
writeln('Please input tiji:');
for i:=1 to n do
readln(tiji);
init_linkedqueue(q);{建立活结点表(表中记录着各活结点对应的权值)}
add(q,-1);{向活结点表添加元素-1,标记本层尾部}
i:=1;ew:=0;best:=0;r:=0;flag:=true;{i:扩展结点的层;ew:扩展结点的权值;best:目前最优装载值;r:剩 余物品的体积之和;flag:循环控制变量(用来标识是否结束循环,若活结点表为空,则flag=false,退出循环)}
for j:=2 to n do
r:=r+tiji[j];
while flag do
{搜索解空间树}
begin
wt:=ew+tiji;{产生左孩子的权值}
if wt<=v{可行的左孩子}
then begin
if wt>best
then best:=wt;
if i<n{若不是叶子结点,则添加到活结点表中} then add(q,wt)
end;
{检查右孩子}
if (ew+r>best) and (i<n){可以有一个更好的叶子}then add(q,ew);
{将右孩子添加到活结点表中}
ew:=delete(q);{从活结点表中取下一个活结点作为新的扩展结点}
if ew=-1{到达层的尾部}
then begin
if empty(q){活结点表为空}then
begin
writeln('best=',v-best);{输出最优剩余空间值}
flag:=false{结束循环的条件成立}
end;
add(q,-1);{添加尾部标记}
ew:=delete(q);{ 从活结点表中取下一个活结点作为新的扩展结点}
i:=i+1;{改变层号}
r:=r-tiji;{改变剩余物品的体积之和}
end
end
end.
结束语:分支定界算法和回溯算法是在问题的解空间上搜索问题的解的两种基本方法,回溯算法通常采用深度优先的方法搜索解空 间树,而分支定界算法则采用广度优先或最小耗费优先的方法搜索解空间树。很多能够使用分支定界算法解决的问题,都可以使用回溯算法加以解决。大家在学习的 时候,对同一个问题要多尝试不同的方法,将它们加以比较,这样不但可以找到较好的解决方案,还可以加深大家对不同算法思想的理解。