zoukankan      html  css  js  c++  java
  • 用数学的方法来写算法续

    上周我写了一篇算法求解的文章 用数学的方法来写算法 文章的最后我说我这个算法的效率其实还可以提高一倍,好几天过去了,没有人发现这其中的奥秘,我今天把答案公布一下吧,说出来大家会觉得很简单,其实写算法也好,搞开发也好,技术这种东西无非就是一层窗户纸,说出来就没那么神秘。今天我就捅破这层窗户纸。

    原题是这样的:

    image

    下面是我上篇文章的解法

    设 数列的 起始数为 n ,从 n 开始 累加到 n + m 后的总和为 s

    其中 m 为大于0 的正整数。

    则 s = (n + n + m) * (m+1) / 2;

    这道题我们需要已知 s 和 m 的情况下求n

    那么 n  = s / (m+1) – m / 2;

    对分子,分母合并后 n = (2 * s – m * (m+1))/(2*(m+1));

    到这里,我们要解决的问题就是找到所有使等式右边可以整除的 m 就可以了。

    原文作者的思路和这个应该差不多,只是实现逻辑上有些繁琐,而且他是m从 1 到 s/2 这样穷举,

    其实我从上面的公式可以看出,这里m 和 n  是成反比的,随着m的增加,n会逐渐减小,当n 小于1时,我们就可以终止这个运算。

    下面我给出根据这个公式推导出来的算法实现的代码

      static void GetAllSerials(int s)
            {
                int n = 0;
                int m = 1;
     
                do
                {
                    int up = 2 * s - m * (m + 1); //分母
                    int down = 2 * (m + 1); //分子
                    n = up / down;
     
                    if ((up % down) == 0 && n > 0) //判读是否可以除尽,且 n 要大于0
                    {
                        for (int i = n; i <= n + m; i++)
                        {
                            if (i == n)
                            {
                                Console.Write(string.Format("{0}", i));
                            }
                            else
                            {
                                Console.Write(string.Format("+{0}", i));
                            }
                        }
     
                        Console.WriteLine();
                    }
     
                    m++;
                }
                while (n > 0);
            }

    计算 GetAllSerials(27545); 结果是:

    13772+13773
    5507+5508+5509+5510+5511
    3932+3933+3934+3935+3936+3937+3938
    2750+2751+2752+2753+2754+2755+2756+2757+2758+2759
    1961+1962+1963+1964+1965+1966+1967+1968+1969+1970+1971+1972+1973+1974
    770+771+772+773+774+775+776+777+778+779+780+781+782+783+784+785+786+787+788+789+
    790+791+792+793+794+795+796+797+798+799+800+801+802+803+804
    359+360+361+362+363+364+365+366+367+368+369+370+371+372+373+374+375+376+377+378+
    379+380+381+382+383+384+385+386+387+388+389+390+391+392+393+394+395+396+397+398+
    399+400+401+402+403+404+405+406+407+408+409+410+411+412+413+414+415+416+417+418+
    419+420+421+422+423+424+425+426+427+428

    当计算 27545 时,我的算法循环次数为 235 次

    进一步优化

    下面我就来谈谈如何进一步优化。其实上周那篇文章的评论中已经有人发现了奇数和偶数的问题,但还是没有深入的进行推导演化,最终无法解开这个谜团。

    首先我们先看 m 为 奇数时,即 m = 2k + 1 (k>=0) ,将 2k+1 带入上面我们已经导出的公式

    s = (n + n + m) * (m+1) / 2;  则

    s = (2n + 2k + 1) * (2k + 2) /2 = (2n + 2k +1)*(K+1) = 2(n+k)*(k+1) + k + 1

    由于 2(n+k)*(k+1) 肯定是偶数,这时我们发现当 k 为 奇数时 s 肯定是偶数,而k为偶数时,s肯定是奇数

    由于 s 是已知的,所以我们只需要遍历 m为3,7,11 … 或者m 为 1,5,9… 这两种序列中的一种序列。这个规律诺贝尔在上一篇的回复中已经发现,可以减少运算量1/4。

    然后我们再看 m 为偶数的情况,即 m = 2(k+1) (k >= 0)

    s = (2n + 2(k+1))*(2(k+1)+1)/2 = (n + k + 1) * (2k + 3)

    这时我们发现,m 为偶数时,s的奇偶情况由n 和 k 共同决定,这种情况,我们用上面分析m为奇数时的思路已经不好用,我们必须换一种考虑问题的方法。

    我们发现要使n 为一个整数值, s 必须可以被2k+3整除,也就是说 s 必须能被大于等于3的奇数整除,我们可以把问题转换为如何快速的找到这些可以被s整除的奇数上。

    如何快速找到这些奇数呢?这个地方又要用到一些数学知识了。

    我们知道任何一个大于等于3的奇数都可以分解为1个或n个大于等于3的质数的乘积。

    比如 3 = 3 , 5 = 5, 7=7, 9 = 3*3, 11=11,13=13,15=3*5

    我上面说的这个定理是很容易证明的,证明过程我就不写了,有兴趣的人可以自己去证明。

    这样我们的问题就化解为寻找这个质数的组合,把一个大于等于3的奇数分解为n个大于等于3的质数的乘积,其中n > 0

    质数在整数序列中占的比例很小,这可以让我们减少大量的计算量。(素数我们可以预先找出来放到一个数组中)

    找到这个质数的组合后,我们从小到大来相乘,并设置一个上限,这个上限诺贝尔 已经推导出是 2s 的平方根,这样可以以非常小的计算量算出所有满足条件的m。

    这里我就不给出具体实现了,我给出了思路,有兴趣的同学可以自己去实现,如何快速找到这个素数组合,如何快速通过这个组合找到有效的m,可能还得费点脑筋。我这里抛砖引玉,剩下的留给大家去发挥吧。

  • 相关阅读:
    Java中接口对象实现回调
    推荐算法之计算相似度
    mahout入门实例2-Mahout单机开发环境介绍(参考粉丝日志)
    mahout入门实例-基于 Apache Mahout 构建社会化推荐引擎-实战(参考IBM)
    windows下gvim使用及常见命令
    一道C语言的问题(转)
    android开发手记一
    数据结构之有关图的算法(图的邻接表示法)
    Week of Code:GG
    HDU 5587:Array
  • 原文地址:https://www.cnblogs.com/eaglet/p/1737933.html
Copyright © 2011-2022 走看看