因为我们所谈到的算法主要都是确定的(除了第11章之外),因此可以直接开发测试用例来保证其行为正确。但是在第7章,我们遇到了麻烦,因为我们使用的是寻径算法来找到可能存在的解,我们对此却一无所知。例如,尽管可以编写出测试用例,来确定启发式的GoodEvaluator对于八数码是否可以正常工作。但是测试A*算法的唯一方法,却需要运行搜索并手工地查看搜索树,来验证是否选择了正确的移动。因此,测试A*算法是很复杂的,因为需要在一个特定问题和启发式的使用环境中去测试算法。我们有大量的测试用例用于寻径算法,但是在许多用例中,他们只是用来确保选择了一个“合理”的移动(无论是游戏还是搜索树),而不是确保选择了一个特定的解。
测试第9章中的算法就更是难上加难了,因为涉及了浮点计算。比如我们要测试凸包扫描,一开始我们打算使用一个穷举凸包扫描算法,算法复杂度为O(n4),来生成结果并和Andrew的凸包扫描算法进行比较。在我们的测试中,在[0,1]之间正态地随机生成了二维点集。然而,当数据集变大时,我们总是遇到两个算法的结果不相等的问题。难道是数据发现了一个重大的缺陷?最终我们发现穷举扫描所使用的浮点数计算得到的结果和凸包扫描的结果有细微的不同(尽管非常细微)。这只是侥幸?不幸的是,并不是这样的。我们也注意到线段扫描算法所产生的结果和穷举相交算法的结果也有些许不同。那么,哪个算法的结果才是“正确”的结果呢?其实并不是那么简单,因为用到了浮点数,因此我们需要找到一个一致性的概念来比较浮点数的值。特别地,我们定义了FloatingPoint.epsilon作为阈值,来解决无法辨识两个数是否完全相等的问题。当两个结果之间的差异接近阈值时(我们设定为10-9),还是会有无法预期的事情出现。将阈值完全删除也不能解决这个问题。最终,我们重新排序,并通过统计的方法来检查算法的结果,而不是仅仅去校验所有案例的返回结果和预期结果的比较。
表11-6综合了第9章谈到了计算几何问题。