Was ist die Laufzeit dieses rekursiven Algorithmus?

8

Ich habe das folgende (ungolfed) Haskell-Programm für die Code-Golf- Herausforderung erstellt, bei der die ersten Werte von A229037 berechnet wurden .n

Dies ist meine vorgeschlagene Lösung zur Berechnung des ten Wertes:n

a n | n<1        = 0 
    | n<3        = 1
    | otherwise  = head (goods n)

goods n = [x | x <- [1..], isGood x n]

isGood x n = and [ x - a(n-k) /= a(n-k) - a(n-k-k) || a(n-k-k) == 0 | k <- [1..n] ]

Beachten Sie, dass Haskell diese Werte nicht automatisch zwischenspeichert oder speichert.

Die OEIS-Seite für die Sequenz gibt die Tatsache an, dass , so dass das durch ersetzt werden könnte , da der Algorithmus niemals ein größer als .x n + 1ein(n)(n+1)/.2[1..][1..(n+1)/2]xn+12

Beim Versuch, Funktionsaufrufe zu zählen, habe ich die folgende Obergrenze , die Anzahl der Funktionsaufrufe, die der Algorithmus für eine Eingabe :nT.(n)n

T(n)=x=1(n+1)/.2k=1n2 T.(n- -k)+2 T.(n- -2k)x=1(n+1)/.2k=1n T.(n- -k)x=1(n+1)/.2k=1n4 T.(n- -1)x=1(n+1)/.24 n T.(n- -1)4 n T.(n- -1) n+122 n (n+1) T.(n- -1))

Ich habe die endgültige Formel in Mathematica eingefügt:

RSolve[{T[n] == 2*T[n - 1]*n*(n + 1), T[1] == 1}, T[n], n]

Und bekam nach einer kleinen Vereinfachung:T.(n) 2n n! (n+1)!

Das durchschnittliche Verhältnis zwischen diesem und der Ausführungszeit des Haskell-Programms für beträgt und die Standardabweichung der Verhältnisse liegt bei . (Seltsamerweise scheint das logarithmische Diagramm der Verhältnisse eine gerade Linie zu sein).2,0 10 39 6,0 10 39n[12,20]]2.010396.01039

Die Verhältnisse mit der ersten Linie, die , haben einen Mittelwert und eine Standardabweichung von bzw. , aber ihre Darstellung springt viel herum.4,8 10 6 1,8 10 6T.(n)4.81061.8106

Wie kann ich die zeitliche Komplexität dieses Algorithmus besser einschätzen?

Hier ist der Algorithmus in gültigem C (minus Vorwärtsdeklarationen), der meiner Meinung nach in etwa dem Haskell-Code entspricht:

int a(int n){
    if (n < 1) {
        return 0;
    } else if (n < 3) {
        return 1;
    } else {
        return lowestValid(n);
    }
}

int lowestValid(int n){
    int possible = 1; // Without checking, we know that this will not exceed (n+1)/2

    while (notGood(possible, n)) {
        possible++;
    }
    return possible;
}

int notGood(int possible, int n){
    int k = 1;

    while (k <= n) {
        if ( ((possible - a(n-k)) == (a(n-k) - a(n-2*k))) && (a(n-2*k) != 0) ) {
            return 1;
        } else {
            k++;
        }
    }
    return 0;
}

Die C-Version benötigt ungefähr 5 Minuten, um zu berechnen, und die Haskell-Version benötigt ungefähr die gleiche Zeit für .a ( 19 )ein(17)ein(19)

Die ersten Male der Versionen:

Haskell: [0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,1.0e-2,3.0e-2,9.0e-2,0.34,1.42,11.77,71.68,184.37,1815.91]
C:       [2.0e-6, 1.0e-6, 1.0e-6, 2.0e-6, 1.0e-6, 6.0e-6, 0.00003,0.00027, 0.002209, 0.005127, 0.016665, 0.080549, 0.243611, 0.821537, 4.56265, 24.2044, 272.212]
Michael Klein
quelle
Ich habe Tags und Titel geändert, um zu verdeutlichen, dass es sich um eine Algorithmusanalyse handelt, nicht um eine Frage der Komplexitätstheorie. "Angenommen, Multiplikation und Addition sind vernachlässigbar" - können Sie ? Wirklich ? Es ist immer noch besser zu sagen, was Sie zählen, weil Sie wahrscheinlich die meisten Dinge nicht zählen. Siehe auch unsere Referenzfrage .
Raphael
Haben Sie versucht, Ihr Ergebnis (mit einem konstanten Faktor) gegen die tatsächlichen Messlaufzeiten zu zeichnen? (Es ist oft informativer, das Verhältnis zu zeichnen und zu erraten, ob es zu etwas in konvergiert .) Trotzdem fällt es mir schwer, hier zu helfen, da der Ansatz für von den Einzelheiten von Haskell abhängt, die nicht jeder hier spricht . Wie wird dieses Mengenverständnis konkret bewertet? Wird auswendig gelernt? Sie erhalten möglicherweise bessere Antworten (oder wirklich alle!), Wenn Sie eine Pseudocode-Version einfügen, die so viel von dem enthüllt, was tatsächlich passiert, wie für eine strenge Analyse erforderlich ist. T.Ö(1)T.a
Raphael
Schließlich ist die Verwendung von Anpassungsmethoden zur Ableitung von Landau-Grenzen wahrscheinlich zwecklos. Eine solche Funktion kann nur gegen einen festen Satz von Funktionen passen. Ich würde vermuten, dass Mathematica dort im schlimmsten Fall Exponentialmodelle verwendet hat und daher einen schlechten Job bei der Erfassung des superexponentiellen Wachstums gemacht hat.
Raphael
@ Raphael Ihre Kommentare waren sehr hilfreich. Ich werde mehr darüber nachdenken, wenn ich etwas Zeit habe. Das kam auch von der Anpassung der Logarithmen der Werte an eine Linie, die mehr als alles andere ein Schuss im Dunkeln war. Ö(n22)
Michael Klein

Antworten:

2

Sie können Ihre Wiederholung als Insbesondere . Dies bedeutet, dass die Sequenz sehr schnell wächst, insbesondere Daher ist Dies bedeutet, dass Und somit Dies verbessert Ihre Bindung durch eine Quadratwurzel.

T.(n)=(n+1)(T.(n- -1)+2T.(n- -2)+T.(n- -3)+2T.(n- -4)+).
T.(n)(n+1)T.(n- -1)T.(n)
T.(n- -1)+2T.(n- -2)+T.(n- -1)[1+2n+1n(n- -1)+2n(n- -1)(n- -2)+]]=(1+Ö(1/.n))T.(n- -1).
T.(n)(n+Ö(1))T.(n- -1).
T.(n)=Ö((n+Ö(1))!),
T.(n)=Ö(nÖ(1)(n/.e)n).
Yuval Filmus
quelle