题目描述
题目建立上一个作业的题目基础上,上一次作业是要求在一个一维序列里找一个最大连续子串,这次task最基础的要求是在一个二维表里找一个最大连续子矩形,但是这次作业有若干个升级版,分别要求可以加入运行参数[\h][\v][\a],其中\h选项代表给定的二维表是水平循环的,\v代表给定的二维表是水平循环的,\a表示结果可以突破矩形的限制,寻找一个最大的连通块,而非矩形。
我的思路
这次题目最难的一部分是实现/a的操作,相比之下其他要求实现比较简单,先从简单的说起。
从二维表里找一个最大的子矩形,这和一维的情况非常类似,这促使我们思考如何将这种情况转化到一维的情况,我们观察二维表中的任意一个子矩形。
我们可以发现子矩形只是选中了一些连续的子列,每个子列的始终位置都是相同的,而且每个子列的值都不会影响其他子列的值,这样我们把子列的值得的和作为一个元素,就得到了一个一维序列。
这时就可以使用在作业毅力的算法来处理这种状态,考虑到要遍历到所有情况,我们仅需要枚举行上每个行之间的区间,时间复杂度为O(n^2),然后再用homework-01里方法处理两行之间的数据即可,时间复杂度为O(m),总时间复杂度为O(n^2m),这样这道题目就比较好的解决了。不过我们并不能确定这个问题时间复杂度下界就是O(n^2m),找出一个O(nm)的算法还是非常有吸引力的,毕竟这和一维的情况存在更多的一致性。
有了上面的基础我们再考虑/v和/h的情况,这两种情况实际上是等价的,因此我们仅考虑/h的情况,/h选项表示二维表在水平方向上时循环连通的,我采用了在水平方向上扩展一次二维表的方法,扩展如下图
扩展之后我们在n*m的矩阵的基础上得到了一个n*2m的矩阵上进行同样的算法,这时还需要加一个限制,考察一行发现如果覆盖整个一行的列长超过m的话会在当前解中加入重复的元素,我们此时枚举列的起始位置,同时保证列长为m即可,这样的时间复杂度为O(n^2m^2),还是一个非常理想的时间复杂度。
最后我们来讨论一下/a选项,它要求找一个任意的最大联通块即可,这个问题难度很高(因为我想了好几天都没想到一个好的解法),题目要求的矩阵最大规模为32*32,初看这个规模显得非常小,但是对于这道题目我们难以找到一个多项式的解,今天在课上也确认了这确实是一个NP问题,解决这个问题有很多思路,这里我们简单讨论几种思路,从经验上我们可以发现如果一个非负的格子在最终解里,那么它相邻的非负格子也一定在最终解里,这样考虑的话就把我们生成矩阵中的所有非负连通块,最终解一定是某些块的集合加入它们之间相连的负的格子,我们枚举这样的集合,然后判断集合里的块是否能相连,具体方法为从一个块中向外遍历负格子,如果遍历的路径和的绝对值已经比该块大了,就停止遍历,可以使用记忆化来优化一下,如果能遍历的目标块就找一条最小的(同时要看这条路的绝对值和是否也比目标块小),这时把这两块连成一块,继续连接其他块,考虑每一种情况后取一个最优值,视为找到的一个解,这个方案的优点是能较快的找到一个好的近似解,缺点是代码复杂,难以实现,而且它目前仅能求一个近似解,并不能证明它是最优的,不满足题目要求。再考虑另一种方法,状态压缩动态规划,状压DP将每一行压为一个状态,这个状态用一个r进制数表示,由于本题要求找到的块具有连通性,那么我们需要一个m进制的数来维护连通性,但本题目有一定特殊性,如果把它视作插头Dp的话这是一个4插头DP也就是说如果相邻两个格子都被选中的话那么他们一定联通的(感谢Tony Shaw的指导),这样考虑的话仅需m/2进制就可以满足需求,我们仅需维护我们当前状态里没有不与当前行连通的块,并且用每一个只有一个块的状态去更新最终解,这样就能很好地解决问题。这个方案的优点是思路清晰,代码相对好写(实际上实现也相当困难),确定是时间复杂度高,我们假定矩阵的规模满足m < n,那么我们的复杂度为O(n(m/2)^(2m)),利用轮廓线优化可以得到O(nm(m/2)^m)的复杂度,题目限制是(n,m)<=(32,32),最坏情况下该算法的规模为32*32*16^32,这个复杂度实在难以接受,但是如果我们考虑一种特例——矩阵的一个维度特别小,那么这种方法就有很大的优势。
一些收获
经过一晚上的奋斗成功在VS2012下完成了performance analyze 和 unit test,第一次做这个非常吃力,但是还是颇有收获,先看看unit test。
写了三个测试方法,分别测试了三个选项以及基础情况的输出,使用了三个不同的测试样例,这次为了能更快进行一些初步的unit test实战,没有对数据进行容错处理,所以样例规模都很小,其中TestACondition我只是采用了找一个最大非负连通子块的方法来代替,所以最后的结果只是一个近似值,如果在assert宏里加入容限就能通过test。再看看测试的覆盖率。
这个工程我写了一部分函数来模块化,从上表看代码有效性还是很高的,但我初学单元测试,还不是很好的看懂覆盖图。
完成第一次单元测试后,个人感觉单元测试这个工具非常有用,但是需要很大的精力维护,它可以很好的维护代码的正确性,单元测试可能非常依赖于测试样例,管理好测试用例对于单元测试是一个重要的组成部分,编写单元测试可能还要遵循简短的方法,我在写单元测试时已经有了一个单元测试的单元测试,但我认为这样的代价是不值得的,好的单元测试应该逻辑清晰而简洁,具有非常好的可读性,以便于维护。
这次单元测试我但我耽误时间最多的是配置过程,这里有一个小细节,使用VS2012单元测试时原工程一定要导出一个DLL作为symbol才能正常连接,不过这短时间也让我熟悉了下VS中solution-projects的文件结构,感觉这个结构非常舒服,可以加入很多工具配合在一起使用同一管理,很赞一点设计。
最后看一下performance analyze
performance analyze做起来就太傻瓜化了,一键完成,不过还是学到了新工具,性能分析我使用了一个500*500的大矩阵,矩阵的元素为16位整型范围内,从上图可以看到主要的运算集中在算法模块,我在算法实现上确实牺牲一部分时间系能,程序还能采用空间换时间的方法继续优化,不过都是只是常数上的,而且会破坏代码结构,于是没有进一步优化。