If k=Pnt Then Break; S:=S+[k];
For j:=2 to Pnt Do
If (Not(j In S))And(Visible(P[k],P[j]))And(Dis[k]+GetDis(j,k) WriteLn(Dis[Pnt]:0:2);{输出答案} End; Begin Assign(Input,FinName); Reset(Input); Assign(Output,FoutName); Rewrite(Output); Init; Main; Close(Input); Close(Output); End. 这个程序编译已经通过,现在我们进行静态查错。首先确定了函数GetMin,GetMax,GetMulti是正确的。因为这三个函数很短,也很简单,所以只要通过静态查错应该就可以了。接着往下看,发现在过程Init中有一行是Inc(Pnt,4);Inc(Pnt,6);明显不对。程序的本意显然是要增加Pnt和Lne的值,所以应该把Inc(Pnt,6)换成Inc(Lne,6)。再往下,又发现在函数Intersect中计算叉积时连续给M2赋了两次值,这样前面一次赋值就没有意义了,这明显是一个书写错误,后面的M2应改为M4。再往下就没有发现其他错误了。 接着进入动态查错,我们用的第一个例子当然是样例,输入样例后发现输出7.28,与答案一致。因此没有必要再进行模块测试。现在应该设计自己的测试用例了。 首先使用边界值分析法,这道题在建筑物个数上有两个边界:0和20。上边界测使用例设计比较繁琐,因此我们先尝试下边界0以及下边界附近的1。坐标也有两个边界:0和1000,我们可以结合建筑物个数的边界,得到如下两个测试用例: coner1.in 0 0 0 1000 1000 corner2.in 1 0 0 1000 1000 0 0 0 1000 1000 1000 对于corner1程序输出是1414.21,正确。对于corner2程序输出是2000.00,也是正确的。接下去我们采用等价分类法设计测试用例。 首先根据数据的排列方式进行分类,我们只使用一个矩形,然后把它的三个顶点的六种排列方式都进行尝试,这样共有6个测试用例,下面仅列出一个: corner3.in 1 0 0 1 1 0 0 0 1 1 1 答案应该全为2.00,但测到顶点排列方式为0 1 1 1 0 0时,程序输出了1.41,因为其他条件都不变,因此最有可能便是过程GetNextPoint出错。使用Go to cursor执行到过程GetNextPoint开头处,添加变量数组P到Watch窗口,再使用Trace into一步步执行下去。我们看到程序对P1、P2没有进行交换,对P2、P3进行了交换,但根据我们的算法,显然需要把(0,0)放到第一个位置。由此恍然大悟,应该在比较完P1、P2后,再比较P1、P3,最后才比较P2、P3。所以应该在第一句If-Then-Else语句后添上: If (P3.x 再次检验,答案无误,前面的测试用例也全部通过。我们继续下面的测试。 这次我们根据矩形之间的关系进行分类,两个矩形可以包含,相交,相切,相离。这里的相切是指两个矩形有一部分边重合但不包含,因为题目规定了两个不同建筑物可能十分接近,但永远不会重叠,因此排除相切这种情况,但可以使相离的两个矩形十分接近。这次我们使用两个矩形,有下面三个测试用例: corner9.in{包含} 2 1 0 1 3 0 0 0 3 3 3 1 1 1 2 2 2 corner10.in{相交} 2 1 0 1 1 0 0 0 1 2 1 1 0 1 1 3 1 corner11.in{相离,但十分接近} 2 1 0 1 1 0 0 0 1 1 1 1.0001 0 1.0001 1 3 1 10、11两个测试用例没有问题,第9个输出了3,有问题,答案应该是5才对。我们进入模块调试,数据读入及初始化没有问题,接下去进入Main继续跟踪,首先我们对第一个循环用Go to cursor一步执行完毕,检查数组Dis,发现除起点外共有4个点的值小于1e10。因为这是一个矩形包含的用例,按理应该只有两个点的值会小于1e10。检查这四个点,发现被包含的那个矩形有两个点也被计算在内。仔细一想就明白了,我们虽然加上了两条对角线,但是对于处于矩形内部的点还是无法排除,我们的程序选择的最短路径应该就是沿直线走,因为中间多了两个矩形顶点把它分为三条线段,这两个顶点又恰好在对角线上,所以这三条线段都不与对角线相交。程序就选择了这条“最短路径”走。 Stop Start 解决方案:因为起点和终点都不会在矩形内部,所以我们可以在读完所有顶点后,排除那些处于矩形内部的点。具体修改可见附录。 我们还可以按照起点和终点的位置关系进行分类,因为这牵涉到矩形的分布,情况比较复杂,所以我们只考虑起点和终点是否重合,因此还需添加一个测试用例: corner11.in 0 0 0 0 0 输出为0.00,正确。 最后我们使用错误推测法,这道题中最复杂的无疑是Intersect和InBuilding这两个函数。因此可以针对这两个函数,把它们分别作为一道小题目来测。可以参照上面讲的步骤设计测试用例,先用边界值分析法,后用等价分类法,具体的测试用例就不再给出。这样测试后的结果也是没有错误。整道题的测试到此就圆满完成了。 【附录】 一、修改后的程序 {说明:红色表示修改后的部分} Program Corner; Const FinName='corner.in';{输入文件名} FoutName='corner.out';{输出文件名} MaxBuilding=20;{最大建筑物} MaxPoint=82;{最多顶点数} MaxLine=120;{最多线段数} Zero=1e-8;{相对极小量,用于实数比较} Type Location=Record{坐标类型} x,y:Real; End; Line=Array[1..2]Of Location;{线段类型} Var Bld,{建筑物数} Pnt,{顶点数} Lne:Integer;{线段数} P:Array[1..MaxPoint]Of Location;{顶点序列} L:Array[1..MaxLine]Of Line;{线段序列} Dis:Array[1..MaxPoint]Of Real;{最短距离表} Function GetMin(Var a,b:Real):Real;{返回两数较小值} Begin If a>b Then GetMin:=b Else GetMin:=a; End; Function GetMax(Var a,b:Real):Real; {返回两数较小值} Begin If a>b Then GetMax:=a Else GetMax:=b; End; Function GetMulti(p0,p1,p2:Location):Real;{计算叉积} Begin GetMulti:=(p1.x-p0.x)*(p2.y-p0.y)-(p2.x-p0.x)*(p1.y-p0.y); End; Function Intersect(Var L1,L2:Line):Boolean;{判断两条线段是否相交} Var M1,M2,M3,M4:Real; Begin Intersect:=False; If (GetMin(L1[1].x,L1[2].x)<=GetMax(L2[1].x,L2[2].x)) And(GetMax(L1[1].x,L1[2].x)>=GetMin(L2[1].x,L2[2].x)) And(GetMin(L1[1].y,L1[2].y)<=GetMax(L2[1].y,L2[2].y)) And(GetMax(L1[1].y,L1[2].y)>=GetMin(L2[1].y,L2[2].y)) Then {通过快速排斥试验} Begin M1:=GetMulti(L1[1],L2[1],L1[2]);M2:=GetMulti(L1[1],L1[2],L2[2]); M3:=GetMulti(L2[1],L1[1],L2[2]);M4:=GetMulti(L2[1],L2[2],L1[2]); If (Abs(M1)>Zero)And(Abs(M2)>Zero) And(Abs(M3)>Zero)And(Abs(M4)>Zero) And(M1*M2>0)And(M3*M4>0) Then Intersect:=True; End; End; Procedure GetNextPoint(Var P1,P2,P3,P4:Location); {将顶点排序并计算第四个顶点坐标} Var T:Location; Begin If (P2.x Function InBuilding(Var p0:Location):Boolean;{判断点是否在矩形内} Var i,j,k:Byte; M1,M2:Real; Temp:Boolean; Begin

