一年又这么快的过去了,回想一年,又这么浑浑噩噩的混了一年。
最近身边的工作,大家都在调性能,当然少不了算法。不得已,时隔4年,我又默默的拿起了算法书。
今天要讨论的事分治法。
分治法的设计思路是,将一个难以解决的大问题,分割成一些规模较小的相同问题,方便各个击破,分而治之。
分治法是否可行的判断标准:1,可分解规模小的问题;2,规模小的问题可解;3,规模小的问题可反求原解。
大家可以想象递归技术,递归实际是对分治法的巧妙利用。
今天就讨论使用递归的方法求解整数划分的问题。
问题描述如下:
将正整数n表示成一系列正整数之和,n = n1 + n2 + ... + nk, 其中n1 >= n2>=... >=nk >= 1, k>=1。
正整数n的这种表示称为正整数n的划分。正整数n的不同划分个数称为正整数n的划分数,记做p(n).
例如正整数6有如下11中不同的划分,p(6) = 11.
6;
5 + 1;
4 + 2, 4 + 1 + 1;
3 + 3, 3 + 2 + 1, 3 + 1 + 1 + 1;
2 + 2 +2, 2 + 2 + 1 + 1, 2 + 1 + 1 + 1 + 1;
1 + 1 + 1 + 1 + 1 + 1。
假设我们将正整数n的所有不同的划分中,将最大加数n1小于等于m的划分个数定义为q(n, m)。q(6, 3)表示正整数6中,最大加数小于等于3的排列个数。这里应该是7。
那么如何来定义q(n,m)呢?
我们这里讨论的事分治,递归,那么显然这里我们要用到递归。那么如何来分析这个问题呢?
如果要递归,必须有个终结者,用于最后的返回。然后就是n和nk直接的关系。
(1)首先我们需找终结者。分析的方法主要以m为特殊值来进行。
m = 1
q(n, 1) = 1, n >= 1;
最大加数小于等于1的划分只有一种, 1 + 1 + 1 + ... +1
当m >= n, 由于n的划分中不可能出现,最大加数大于n的情况,所以得出
q(n, m) = q(n, n),m > = n
q(n, n) = q(n, n - 1) + 1, 这个等式也很好理解,q(n, n - 1),表示n排列中最大加数小于等于n - 1,那所有排列中只剩下一个了,就是n本身,也就是表示中的1。
这里需要注意的事q(n, n) = q(n, n -1) + 1的表达式,是在n和m相同情况下成立,这里并没有找到q(n,m)的通用关系,及m < n的时候
(2)寻找n和nk直接的关系
当m < n时,q(n, m)有如何呢,这里表示最大加数小于等于m的所有排列。根据上面的p(6)的例子。我们很容易推导出q(n, m) = q(n, m - 1) + x,而x是什么呢。
比如我们定义n为6,m为4,那么q(6, 4) = q(6, 3) + x, 这里我们知道q(6,4)所有排列中除掉q(6, 3)就剩下,以4开头的排列了。4开头的排列有多少呢?应该是所有2的排列,既所有6 - 4的排列。
这样我们就推导出通用的关系式了,q(n, m) = q(n, m - 1) + q(n - m, m),这个红色的m如何得到的?当然q(n,m)表示小于等于m的组合,那么q(n - m)里面当然不能大于m了。
总结下:
q(n, m) = 1, n = 1, m =1
= q(n, n) n < m
= 1 + q(n, n -1) n = m
= q (n, m - 1) + q(n - m, m) n >m >1
最后我们写出递归的伪代码
int q(int n, int m) { if ((n < 1) || (m < 1)) return 0; if ((n == 1) || (m == 1)) return 1; if (n < m) return q(n, n); if (n == m) return q(n, m - 1) + 1; return q(n, m - 1) + q(n - m, m) }