1程序中整数的除法
C语言中,两个整数相除的符号为’/’。若dd和d为两个整数,则dd / d的值是dd除以d的商。若是需要a除以b的余数,则此余数可表示为a % b(%只能应用于两整数)。
一般的,在程序中,dd、d为两整数,根据
q = dd / d
r = dd % d
可得以下结论:
- (dd – l) / d = q,l = [0, r]。
- m / d = q,m = [dd, (q + 1) * d)。
- 对于余数r,只跟除数有关系。r = [0, d)。可用随机数和%来表达概率。
在程序中的一些递归程序中,递归程序的退出分支往往为1个元素,所以这个时候采用折半(如每次都以最小、最大序列下标之和除以2)递归就是这个道理。
2求最大子向量和中的分治思想
《Programming Pearls,编程珠玑》的“算法设计技术”一章中,主要用四种算法解决了求向量的任意子向量中的最大和问题。觉得第三种算法即分治法挺有味道的,是Michael Shamos的通宵之作。
笔记个人对理解分治法在求最大和子向量时的几个层次。
(1)分治算法的宏观性
对于求解向量x[0…n-1]任意子向量中的最大和问题,可以用以下宏观思想把握。
将x[0…n-1]划为以下所示任意的a,b两部分:
那么x[0…n-1]具最大和的子向量在a或b向量中(本身或者子向量),或c(跨跃a与b,包括x[0…n-1]整个向量)。然后采用相同的办法分别求a,b,c向量的最大子向量和。
根据“分治”二字的含义,其实可以将一个问题分成m个小问题来解决,就看在程序满足操作否了。
(2)二分
在《Programming Pearls,编程珠玑》中将x[0…n-1]分为大小相当的两部分,然后递归的求解。即要解决规模为n的问题,可递归解决两个规模近似为为n/2的子问题,然后将它们的答案进行合并以得到整个问题的答案。之所以是n/2,是因为这样的递归过程才有意义:可以折半到单个元素之上,也只有在元素为1个时,再次折半的值才为0( 0对于数组来说仍有意义 )。这就是为什么要将x[0…n-1]递归的分成近似两个n/2大小序列来解决问题的原因。二分后求解最大和的子向量可在以下a,b,c三部分中查找。
(3)求解c
分治算法在具有n个元素的序列x[0…n-1]之上不好看出怎么求解c。我将在《Programming Pearls,编程珠玑》中用分治算法解决最大和子向量问题的递归程序执行了一遍(当抽象不出作者所描述的情况时,我老是直接执行代码)。
执行这个段代码需要对递归调用过程有所了解。
第一,递归函数的内容:是求得lmax和rmax(去执行以下这两个值对应的代码)。
第二,递归语句:return max(lmax, rmax,maxsum(l, m), maxsum(m+1, u) )。以父序列求得的lmax和rmax之和去跟左右子序列的最大值做比较,将大者返回作为最终的结果。
第三,递归退出分支:if ( l == u) return x[l]。即将单个元素返回。
示范性的执行了一下这个函数,得到递归过程如下:
Figure1:递归调用过程描述
可以发现,程序执行到最终会牵扯到单个元素之上。根据递归分支,程序会将单个元素值返回和父序列的lamx + rmax比较得到大者,然后层层返回。
以7个元素为例可以清楚的看到求C的方法:
Figure2:分治法的原理例子
如此,由这4个向量,推理到x[0…n-1]序列,仔细推敲求lmax和rmax的代码就就可以知道,其实lmax + rmax的值就是跨跃于a,b两子向量的具最大和子向量的和。而return max(lmax, rmax, maxsum(l, m), maxsum(m+1, u) )在返回的最后一层中(即l = 0, m = (0 + n-1)/2, u = n-1),maxsum(l, m)和maxsum(m+1, u)分别是在a,b向量中返回的具最大和的子向量的和。由此而使问题得解。
3求解向量具最大和的子向量和
问题:具n个浮点数的向量x,求任何相邻子向量中的最大和。此问题的解决思路从《Programming Pearls,编程珠玑》第八章中读到。
从感觉上讲,从这一章获取到的关于“方向”的关键词为“模式匹配”。关于算法的关键词有“保存状态”,“累积”,“预处理、数据结构”,“分治”,“扫描”。专门描写算法的书应该对这些算法有详细、系统的记载。
(1)从数学上归纳算法
从数学上讲,必须直接求到x[i..j]的和,0 <= i <= j <n。然后直接或间接的比较它们,找出最大和。
算法1:maxsofar在程序开始时初始化为0。在求x[i..j]的和之前初始化sum =0。在求得的x[i..j]和之后就取maxsofar = max(sum, maxsofar)。
算法2:Grenander观察到算法1的O(n^3)时间度在n足够大的时候程序要花费很多的时间才能够执行完,所以紧接着有设计了算法2。算法2采取保存状态的方法编写程序,将x[i..j-1]的和用来求x[i…j]的和,maxsofar =max(maxsofar, sum),sum为x[j…k]的和,j = [i..u], k = [j, u]。如此就减少了一重循环,而降程序的复杂度变为O(n^2)。私下想想,在逻辑上就是这么个理儿。
算法3:算法3具有和算法2一样的复杂度O(n^2)。此算法首先采用一个数据结构来预处理x[0..n-1]。用cumarr[i]的每一个元素来分别保存x[0..i]的和。有了这个数据结构之后,只要给出具体的i,j整数对利用cumarr[j] – cumarr[i-1]就可以得到x[i…j]子向量的和。为了遍历完所有的子向量,故而设立两层循环。
算法4:扫描算法。算法是显得十分的精巧。统计学家Jay Kadane在几分钟内就给出了此算法的提纲。顿时觉得学数学的人十分具有神秘感,思维很灵敏,反应很快。书中采用抽象和归纳的方式向读者介绍扫描算法。如果对这段描述扫描法的文字有所不解的话就可以直接执行那几行代码。然后就会发现,区区两行代码就将完成了所有的工作。只要maxendinghere的值大于0,就有可能在加上x[i]之后大于maxsofar的值,但只要maxendinghere值小于0后,maxendinghere对应的序列就再无意义(将maxendinghere此时的序列看成一个元素,那么一个小于0的元素是不可能在求最大和子向量和中充当第一个或者最后一个元素的)。此时maxendinghere对应与后序列来说是开始元素,对于前序来说是最后一个元素,由于maxendinghere小于0,所以它不再有存在的意义。此算法是求解求解最大和子向量的和的最好算法,复杂度为O(n),这是多么的理想。
(2)宏观法
感觉书中求解最大和子向量的分治算法总有几分宏观的味道。它是以整个序列为分析对象而非从数学表达式之上(也或者是从特殊实例(如前面所述的四个元素)中归纳总结出来的)。具体理解已经笔记在前。
对于分治算法求解具最大和子向量和的算法的时间复杂度可以这么看:一趟分值算法中的复杂度为O(n),然后递归深度为O(logn) [底为2](但递归调用次数远大于logn,由于递归调用时函数层层返回时表现为满二叉树,所以调用递归函数的次数应该接近n)。书中叙述此时间复杂度为O(nlogn)。
笔记关于求子向量最大和的描述和伪码在《Programming Pearls, 编程珠玑》第八章“算法设计技术”一章中。不明时可再下载查阅。
书中跟数学表达式相关的叙述以总结抽象的形式介绍,以宏观法如分治法介绍算法的时候也呈现抽象的思潮,直接从n开始。所以平时锻炼抽象、归纳的思维很有必要。可多看概率论、线代、高数即math一类的书。
此次笔记记录完毕。