posts - 257, comments - 1336, trackbacks - 63, articles - 8
  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理

2004年9月26日

        没有想到《数据结构、算法与应用》一书如此之强调测试,曾经以为只有在Kent Beck的书中,才会将测试摆在一个极其重要的位置。尽管书中对于测试与调试的讲述并不是太多,却将要点讲述得特别的清楚,让自己对于测试和调试有了些许的顿悟。
        测试是为了尽可能的发现错误,这个是一定要牢记的。为了实现这个目标,测试数据的选择就显得尤为重要了。毕竟能够作为一个函数的输入条件的数据集合实在是太大了。在选择测试数据的时候应该遵循以下三个覆盖原则:
        1、语句覆盖:选择足够多并且合适的测试数据使得每一条语句都能够被执行;
        2、分支覆盖:选择测试数据集使得每一个条件都出现true和false两种情况;
        3、子句覆盖:子句覆盖与分支覆盖有类似的地方,只是子句覆盖的测试粒度会更细,因为通常一个条件语句有可能会包含多个条件子句,那么子句覆盖就是让条件语句中包含的每一个条件子句都出现true和false两种情况。
        以上提到的这些覆盖原则是用在白盒测试中的。白盒测试是针对代码本身的,而与白盒测试相对应的则是黑盒测试。黑盒测试常用的方式是I/O类,也就是分析代码会出现哪几类输入和输出(通常输入类和输出类是一一对应的),然后根据输入类的不同选择合适的测试数据,并且检验输出类的正确性。
        测试是为了能够发现错误,那么定位错误并且修改错误的过程则是调试(debug)。调试也需要遵循几个原则:
        1、在修改错误的同时一定要避免引入新的错误;
        2、不能够通过异常来掩盖错误;
        3、增量测试和调试原则(incremental test and debug)。测试与调试应该从相对独立的函数开始,继而依次引入其他函数进行测试。在一个相对独立的函数测试通过之后,引入另外的一个函数进行测试,如果发生了错误,就可以很容易地确定错误出现在新引入测试的函数中了。

posted @ 2004-09-26 23:59 FantasySoft 阅读(453) | 评论 (0)编辑

        上次朋友的一个问题,让我重新翻开了那本尘封已久的《数据结构、算法与应用》。仅仅重读了第一章,我不得不再次为专注数据结构与算法研究的科学家们佩服得五体投地。
        让我佩服的问题其实很简单:生成一个list中的元素的全排列,也就是说input为:[a, b, c],output则是[abc, acb, bac, bca, cab, cba],当然list中的元素个数是不定的。是不是很简单的问题呢?不过比较愚钝的我,却没有办法想出问题的答案,尽管我知道应该使用递归来实现。
        想不出来就只好看书上是怎么实现的了。基于递归的算法,通常都需要一个递归等式(recursive component,递归部分)和一个递归终结的条件(base,基本部分)。那么对于以上的问题,递归部分和基本部分都是什么呢?假设递归函数为Perm(E),E为元素的集合,那么基本部分是比较容易想到的,就是当集合E中只有一个元素的时候,Perm(E) = e,e为集合E中唯一的元素;接下来的就是递归部分了,当集合E中的元素个数大于1的时候,那么:Perm(E) = e1.Perm(E1)+e2.Perm(E2)+e3.Perm(E3)...+en.Perm(En),E = e1 + E1= e2 + E2 = e3 + E3= en+ En。也就是说,en.Perm(En)表示从集合E中取出一个元素en,然后将剩下的元素进行排列,两者再相连就得到了以en元素作为前缀的所有排列了,那么将集合E中的所有元素遍历一次,得到以每一个元素作为前缀的排列,最后就可以得到所有的排列方式了。至此,递归函数所需要的两个必要部分都清楚了,那么代码该如何实现呢?以下是书上给出的代码:
template <class T>
void Perm(T list[], int k, int m)  //m为list数组index的最大值,而k则表示
                                      position,初始值为0

{
    
int
 i;
    
if (k ==
 m)
    
{
    
for (i = 0; i <= m; i++)            
           cout 
<<
 list[i];
        cout 
<<
 endl;
    }

    
else
        
for (i = k; i <= m; i++)
        
{
        Swap(list[k], list[i]); 
//通过交换,使得每个元素都能够成为前缀

        Perm(list, k+1, m);     //递归调用,得到作为后缀的元素的所有排列
        Swap(list[k], list[i]);
    }

}

Swap是一个自定义的inline函数,用作交换两个元素。尽管这样的代码很简洁,但是总觉得不是太爽了,毕竟有那么多的参数,而且似乎与算法中描述的不太像。大家再看一下python的实现:
def permute(seq):
    l 
=
 len(seq)
    
if l == 1
:
       
return
 [seq]
    
else
:
       res 
=
 []
       
for i in
 range(len(seq)):
          rest 
= seq[:i] + seq[i+1
:]
          
for x in
 permute(rest):             
             res.append(seq[i:i
+1+
 x)
       
return
 res

是不是会更为简洁,更容易理解,而且更符合算法的描述呢?在以上代码中,rest变量对应着取出元素i剩下的集合,for x in permute(rest)遍历集合rest产生的所有的排列,然后通过seq[i:i+1] + x 就可以得到以元素i作为前缀的排列了。当然这样的算法有明显的一个问题是,占用了较大的空间。因为当len(seq)>1的时候,每次函数的调用,都必须为res开辟新的空间,一旦递归嵌套的层次比较多,则需要比较大的空间了。
        排列的问题解决了,接着就是组合问题(外延则是集合的子集问题)了。或许太长时间没有去思考算法与数据结构的问题了,这个问题仍然让我无从下手。对算法与数据结构有心得的朋友多多指教了。//Bow

posted @ 2004-09-26 13:36 FantasySoft 阅读(1587) | 评论 (2)编辑