找到Padovan数字的优化方法

时间:2016-10-18 16:24:18

标签: c# algorithm optimization

查找Padovan number的最佳方式是什么? 这就是我目前所拥有的。 即使它返回正确的结果,我想知道什么是最快的方法。

        long Sum = 0;
        public long Get(int number)
        {
            Sum = 0;      
            if (number == 0 || number == 1 || number == 2)
                return 1;
            return GetPadovanAlt(number);
        }

        public long GetPadovanAlt(int n)
        {
          if(n == 0 || n == 1 || n == 2)
          return 1;
          return GetPadovanAlt(n - 2) + GetPadovanAlt(n - 3);

        }

6 个答案:

答案 0 :(得分:2)

使用递归,您所做的工作超出了您的需要。当您计算GetPadovan(42)时,您将触发递归的二叉树,其中包括GetPadovan(12)之类的子调用。在递归树的许多分支上将多次调用GetPadovan(12)。加上像if (n == 12) then print("*");这样的东西来看看有多少。

当您计算特定的Padovan数字时,请将其存储并检索存储的第二次和后续呼叫的号码。或者切换到非递归计算:从Padovan(3)开始并进行处理,跟踪四个数字:P(n),P(n-1),P(n-2)和P(n-3)当你继续。

ETA:运行快速程序,GetPadovan(42)调用GetPadovan(12) 1897次。递归绝对不是最快的方式。

答案 1 :(得分:2)

Ol' For Loop可能不像递归一样华丽 但是,如果它能够胜任,那么就没有理由瞧不起它。

代码也会计算负数 并且它可以处理超过156的数字。(因为Int64对于更大的padovans来说是小的)

public static BigInteger GetPadovan(int n)
{
    if (n > 156 || n < -316) return GetBigPadovan(n);
    return GetSmallPadovan(n);
}

public static BigInteger GetBigPadovan(int n)
{
    if (n == 0 || n == 1 || n == 2) return 1;

    BigInteger padovan = 0, prev1 = 1, prev2 = 1, prev3 = 1;

    if (n > 2)
    {
        for (var i = 2; i < n; i++)
        {
            padovan = prev2 + prev3;
            prev3 = prev2;
            prev2 = prev1;
            prev1 = padovan;
        }
    }
    else if (n < 0)
    {
        for (var i = 0; i > n; i--)
        {
            padovan = prev3 - prev1;
            prev3 = prev2;
            prev2 = prev1;
            prev1 = padovan;
        }
    }

    return padovan;
}

public static long GetSmallPadovan(int n)
{
    if (n == 0 || n == 1 || n == 2) return 1;
    if (n > 156) return 0;

    long padovan = 0, prev1 = 1, prev2 = 1, prev3 = 1;

    if (n > 2)
    {
        for (var i = 2; i < n; i++)
        {
            padovan = prev2 + prev3;
            prev3 = prev2;
            prev2 = prev1;
            prev1 = padovan;
        }
    }
    else if (n < 0)
    {
        for (var i = 0; i > n; i--)
        {
            padovan = prev3 - prev1;
            prev3 = prev2;
            prev2 = prev1;
            prev1 = padovan;
        }
    }

    return padovan;
}

新的更快递归:

static public long GetPadovanRecursive(int n, long prev1 = 1, long prev2 = 1, long prev3 = 1)
{
    if (n > 156) return 0;
    if (n > 2) return GetPadovanRecursive(--n, prev2 + prev3, prev1, prev2);
    if (n < 0) return GetPadovanRecursive(++n, prev3 - prev1, prev1, prev2);
    return prev1;
}

缓慢递归:

static public long GetPadovanSlowRecursive(int n)
{
    if (n == 0 || n == 1 || n == 2) return 1;
    if (n < 0 || n > 156) return 0;
    return GetPadovanSlowRecursive(n - 2) + GetPadovanSlowRecursive(n - 3);
}

使用二项式方法:

Padovan sequence

public static BigInteger GetPadovanBinomial(int n)
{
    BigInteger result = 0;
    int k = n + 2;

    for (int m = k/3; m <= k/2; m++)
    {
        result += GetBinomialCoefficient(m,(k-m*2));
    }
    return result;
}

public static BigInteger GetBinomialCoefficient(int n, int k)
{
    BigInteger result = 1;
    for (int i = 1; i <= k; i++)
    {
        result *= n - (k - i);
        result /= i;
    }
    return result;
}

使用组合方法

这利用了一个特殊的事实,即P(n)是写入(n + 2)的方式的数量,作为2&amp;的总和。 3。

static BigInteger GetPadovanSumCombos(int n)
{
    if (n < 0) return 0;
    int m = n + 2;
    int min3 = m % 2;
    int max2 = (min3 == 0) ? m / 2 : (m / 2) - 1;
    BigInteger result = 0;

    var factorials = new BigInteger[min3 + max2 + 1];
    factorials[1] = 1;
    for (int i = 2; i <= min3 + max2; i++) factorials[i] = i*factorials[i-1];

    for (int m2 = max2, m3 = min3; m2 >= 0; m2 -= 3, m3 += 2)
    {
        if (m2 == 0||m3 == 0) result += 1;
        else if (m3 == 2) result += (((m2+1) * (m2 + 2)) / 2);
        else result += factorials[m2 + m3] / (factorials[m3] * factorials[m2]);
    }

    return result;
}

使用代码段进行测试:

for (var i = -20; i <= 20; i++) Console.Write("{0} ", GetPadovan(i)); 

返回:

7 -7 4 0 -3 4 -3 1 1 -2 2 -1 0 1 -1 1 0 0 1 0 1 1 1 2 2 3 4 5 7 9 12 16 21 28 37 49 65 86 114 151 200

比较方法的速度:

var timer = new System.Diagnostics.Stopwatch(); timer.Stop();
var padovan = new BigInteger();
int num = 72;

timer.Restart(); padovan = GetPadovanSlowRecursive(num); timer.Stop();
Console.WriteLine("GetPadovanSlowRecursive({0}):\t{1}\t{2,12:F6} ms", num, padovan, timer.Elapsed.TotalMilliseconds);

timer.Restart(); padovan = GetPadovanRecursive(num); timer.Stop();
Console.WriteLine("GetPadovanRecursive({0}):\t{1}\t{2,12:F6} ms", num, padovan, timer.Elapsed.TotalMilliseconds);

timer.Restart(); padovan = GetPadovanBinomial(num); timer.Stop();
Console.WriteLine("GetPadovanBinomial({0}):\t\t{1}\t{2,12:F6} ms", num, padovan, timer.Elapsed.TotalMilliseconds);

timer.Restart(); padovan = GetPadovanSumCombos(num); timer.Stop();
Console.WriteLine("GetPadovanSumCombos({0}):\t{1}\t{2,12:F6} ms", num, padovan, timer.Elapsed.TotalMilliseconds);

timer.Restart(); padovan = GetPadovan(num); timer.Stop();
Console.WriteLine("GetPadovan({0}):\t\t\t{1}\t{2,12:F6} ms", num, padovan, timer.Elapsed.TotalMilliseconds);

返回:

GetPadovanSlowRecursive(72):    448227521       13283,251300 ms
GetPadovanRecursive(72):        448227521           0,278300 ms
GetPadovanBinomial(72):         448227521           0,486300 ms
GetPadovanSumCombos(72):        448227521           0,722400 ms
GetPadovan(72):                 448227521           0,365900 ms

答案 2 :(得分:2)

由于您特别询问C#和优化,我提供的代码我认为会为您设置正确的方向。首先是程序的输出:

From https://oeis.org/A000931
Ref: 1  0  0  1  0  1  1  1  2  2  3  4  5  7  9  12  16  21  28  37  49  65  86  114  151  200  265
Rec: 1  0  0  1  0  1  1  1  2  2  3  4  5  7  9  12  16  21  28  37  49  65  86  114  151  200  265
Qik: 1  0  0  1  0  1  1  1  2  2  3  4  5  7  9  12  16  21  28  37  49  65  86  114  151  200  265

Recursive method:   709.238100 ms   checksum: 35676949
Quick     method:     0.004800 ms   checksum: 35676949

正如您所看到的,递归和“快速”方法之间存在很大差异。有几个原因。首先,递归需要相当多的额外工作来移动地址和为每个函数调用打开和关闭堆栈的值(并且有很多调用)。其次,此代码在调用quick方法时使用静态分配的数组(非线程安全)作为工作区。您可以在方法内部分配数组,但这需要少量的额外时间。

快速方法只是输入for循环来直接计算静态数组中的值。您可以创建一个类并仅初始化前四个数组值一次。您还可以跟踪计算的最后一个值,以便在计算较大值时从该位置开始,或使用“n”作为索引直接检索预先计算的值。

以下是代码(创造性地命名为ConsoleApplication1; - ))

using System;
using System.Diagnostics;

namespace ConsoleApplication1 {
    class Program {
        static public long GetPadovanRecursive(int n) {
            if (n == 0) { return 1; }
            if (n == 1) { return 0; }
            if (n == 2) { return 0; }
            if (n == 3) { return 1; }
            return GetPadovanRecursive(n - 2) + GetPadovanRecursive(n - 3);
            }

        static int N_Max = 8192;
        static long[] pdvn = new long[N_Max];

        static public long GetPadovanQuick(int n) {
            Debug.Assert(n < N_Max);
            if (n == 0) { return 1; }
            if (n == 1) { return 0; }
            if (n == 2) { return 0; }
            if (n == 3) { return 1; }

            pdvn[3] = 1;
            for (int i = 4; i <= n; i++) {
                pdvn[i] = pdvn[i - 2] + pdvn[i - 3];
                }
            return pdvn[n];
            }


        static void Main(string[] args) {
            const int Count = 64;
            Stopwatch stp = new Stopwatch();

            // Sanity check
            Console.WriteLine("From https://oeis.org/A000931");
            Console.WriteLine("Ref: 1  0  0  1  0  1  1  1  2  2  3  4  5  7  9  12  16  21  28  37  49  65  86  114  151  200  265");
            Console.Write("Rec: ");
            for (int i = 0; i < 27; i++) {
                Console.Write("{0}  ", GetPadovanRecursive(i));
                }
            Console.WriteLine();
            Console.Write("Qik: ");
            for (var i = 0; i < 27; i++) {
                Console.Write("{0}  ", GetPadovanQuick(i));
                }
            Console.WriteLine();
            Console.WriteLine();

            long sum = 0;
            stp.Start();
            for (int i = 0; i < Count; i++) {
                sum += GetPadovanRecursive(i);
                }
            stp.Stop();
            Console.WriteLine("Recursive method: {0,12:F6} ms   checksum: {1}", stp.Elapsed.TotalMilliseconds, sum);


        sum = 0;
        stp.Restart();
        for (var i = 0; i < Count; i++) {
            sum += GetPadovanQuick(i);
            }
        Console.WriteLine("Quick     method: {0,12:F6} ms   checksum: {1}", stp.Elapsed.TotalMilliseconds, sum);
        }
    }
}

答案 3 :(得分:1)

Just as a member of the Fibonacci sequence can be computed rapidly raising a 2×2 matrix to the nth power (offsets vary between definitions) and taking the top-left element as the result, the same applies to the sequence of Padovan numbers:

F: 1 1   P: 0 0 1
   1 0      1 0 1
            0 1 0 (or its transpose)

Not fluent in math, code for those that can more easily think in, say, C#:

const long BIG_LIMIT = 1 << 30; // even 1L<<31 is too large
/** computes the <code>n</code>th Padovan number.<br/>
 * (index 0 for the first <code>1</code> in the run of three
 *  ("The triangle interpretation"))<br/>
 * This uses exponentiation of <code><br/>
 * 0 0 1 <br/>
 * 1 0 1 <br/>
 * 0 1 0 </code>by <code>n+5</code>
 * (returning the top-left element) */
static BigInteger padovan(long n) {
    if (n < 3)
        return (n < 0) ? 0 : 1;
// add offset from "non-negative" to "triangle" definition
    n += 5;
    long b, bit = highestOneBit(n),
        a = b = 0, c = 1; // consecutive Padovan numbers

    BigInteger p1 = a, p2 = b, p3 = c;
// raise to the nth power by squaring&multiplying
    while (0 < BIG_LIMIT) { // <= 0: use BigInteger from start
        long bc,
            A  = a*a + 2*(bc= b*c),
            B  = b*(2*a+b) + c*c;
        if (1 == (bit >>= 1))
            return 0 == (n&1) ? A : B; 
        long C  = 2*(a*c + bc) + b*b;
        //  D  = a²+2ab+b²+2bc+c² = (a+b+c)² - 2ac
        if (BIG_LIMIT < B) {
            if (0 == (n&bit)) {
                p3 = C;
                p2 = B;
                p1 = A;
            } else {
                p3 = A + B;
                p2 = C;
                p1 = B;
            }
            break;
        }
        if (0 == (n&bit)) {
            c = C;
            b = B;
            a = A;
        } else {
            c = A + B;
            b = C;
            a = B;
        }
    }

// raise to the nth power by squaring&multiplying
    while (true) {
        if (1 == (bit >>= 1))
            return 0 == (n&1)
                ? p1*p1 + 2 * p2*p3
                : p2*(2*p1+p2) + p3*p3;
        BigInteger
            p23= p2*p3,                // common to A & C
            s2 = p2*p2,                // common to B & C
            A  = p1*p1 + 2 * p23,
            B  = 2*p1*p2 + s2 + p3*p3,
            C  = 2*(p1*p3 + p23) + s2;
        if (0 == (n&bit)) {
            p3 = C;
            p2 = B;
            p1 = A;
        } else {
            p3 = A + B; // D = A + B
            p2 = C;
            p1 = B;
        }
    // ok, so the matrix squaring is multiplied out.
    // and common subexpressions are "eliminated"
    //  at the source code level.
    // and "the single step" uses explicit renaming
    //  and one addition.
    // So what - "most optimized" was called for,
    //  premature or not
    }
}
/** @return the most significant bit set - zero, if none. */
static long highestOneBit(long i) {
// Hacker'sDelight, Figure 3-1
    i |= (i >>  1);
    i |= (i >>  2);
    i |= (i >>  4);
    i |= (i >>  8);
    i |= (i >> 16);
    i |= (i >> 32);
    return i - (i >> 1);
}

Conversion to BigInteger left above as an experiment… (the expressions have been chosen with (Java's) BigInteger implementation in mind)(For arguments in the two digit range, an "addition loop" was faster in my experiments. Too lazy to figure out how to take advantage of that.)

答案 4 :(得分:0)

只能通过调用一种方法来完成

public long GetPadovanAlt(int n)
    {
      if(n == 0 || n == 1 || n == 2)
      return 1;
      return GetPadovanAlt(n - 2) + GetPadovanAlt(n - 3);

    }

答案 5 :(得分:0)

上面应用的方法是递归编程。它运作良好,但最终需要花费大量时间来获得更大的数值。

迭代方法的工作速度要快得多。

这是一个用来演示的java程序......

class PadovanNumber {

public static long findPadovanNumRec (int n) {
    if ((n == 0) || (n == 1) || (n == 2))
        return 1;
    return findPadovanNumRec(n - 2) + findPadovanNumRec(n - 3); 
}

public static void main (String args[]) {
    int num = (Integer.valueOf(args[0])).intValue();
    //long timeRecStart = System.currentTimeMillis();   
    //long answerRec = findPadovanNumRec(num);
    //long timeRecEnd = System.currentTimeMillis(); 
    //System.out.println("Padovan Number Rec " + args[0] + " = " + answerRec + " Time Taken = " + (timeRecEnd - timeRecStart));
    long timeIterStart = System.currentTimeMillis();    
    long answerIter = findPadovanNumIter(num);
    long timeIterEnd = System.currentTimeMillis();  
    System.out.println("Padovan Number Iter " + args[0] + " = " + answerIter + " Time Taken = " + (timeIterEnd - timeIterStart));
}

public static long findPadovanNumIter (int n) {
    long sum = 0;
    long sumn1 = 1;
    long sumn2 = 1;
    long sumn3 = 1;
    for (int i = 0; i <= n; i++) {
        if ((i == 0) || (i == 1) || (i == 2))
            sum = 1;
        else {
            sum = sumn2 + sumn3;
            sumn3 = sumn2;
            sumn2 = sumn1;
            sumn1 = sum;
        }
    }
    return sum;
}

}

希望这会有所帮助。 : - )