この再帰的アルゴリズムの実行時間はどれくらいですか?


8

A229037 の最初の値を計算するというコードゴルフチャレンジのために、次の(無記号の)Haskellプログラムを作成しました。n

これは、番目の値を計算するために提案するソリューションです。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] ]

Haskellはこれらの値を自動的にキャッシュまたはメモしないことに注意してください。

シーケンスのOEISページはであることを示しています。したがって、アルゴリズムはより大きい到達しないため、で置き換えることができます。。x n + 1a(n)≤(n+1)/2[1..][1..(n+1)/2]xn+12

関数呼び出しをカウントしようとして、次の上限を導出しました。これは、アルゴリズムが入力取る関数呼び出しの数です。nT(n)n

T(n)=∑x=1(n+1)/2∑k=1n2 T(n−k)+2 T(n−2k)≤∑x=1(n+1)/2∑k=1n T(n−k)≤∑x=1(n+1)/2∑k=1n4 T(n−1)≤∑x=1(n+1)/24 n T(n−1)≤4 n T(n−1) n+12≤2 n (n+1) T(n−1))

最終的な式をMathematicaにプラグインしました:

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

そして、少し簡略化した後:T(n)≤ 2n n! (n+1)!

これとHaskellプログラムの実行時間の平均比率は、で あり、比率の標準偏差は約です。(奇妙なことに、比率の対数プロットは直線のように見えます)。2.0 ⋅ 10 39 6.0 ⋅ 10 39n∈[12,20]2.0⋅10396.0⋅1039

定義する最初の線との比率は、それぞれと平均と標準偏差を持っていますが、そのプロットは大きく飛び回っています。T(n)4.8⋅1061.8⋅106

このアルゴリズムの時間の複雑さをより適切に把握するにはどうすればよいですか?

以下は、有効なC(マイナスの宣言を差し引いたもの)のアルゴリズムです。これは、Haskellコードとほぼ同等であると私は考えています。

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;
}

Cバージョンは計算に約5分かかり、Haskellバージョンはとほぼ同じです。a (19 )a(17)a(19)

最初のバージョン:

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]

タグとタイトルを変更して、これがアルゴリズム分析であり、複雑性理論の問題ではないことを明確にしました。「掛け算と足し算が無視できると仮定する」- できますか?ほんと?あなたがほとんどのものを数えていない可能性があるので、あなたが数えているものを言う方が良いです。参照用の質問もご覧ください。
— ラファエル

実際のメジャー実行時間に対して結果を(一定の係数で)プロットしてみましたか?(それは比率をプロットし、それが中に何かに収束した場合に推測することは、しばしば、より有益だ。)のためのansatz以来、私はここに助けにハードそれを見つける、言ったここではない、誰もが話している、ハスケルの細目に依存。具体的には、このセットの理解度はどのように評価されますか?されるメモ化されていますか?厳密な分析に必要なだけ実際に発生することを公開する疑似コードバージョンを含めると、より良い答え(または実際には!)が得られる場合があります。O(1)Ta
— ラファエル

最後に、フィッティング法を使用してランダウ境界を導出することはおそらく無駄です。そのような関数は、固定された関数のセットに対してのみ適合します。Mathematicaがそこで最悪の指数関数モデルを使用していたため、超指数関数的な成長を捉えるのはうまくいかなかったと思います。
— ラファエル

@Raphaelあなたのコメントはとても役に立ちました。時間があれば、もっと詳しく調べます。また、は値の対数を直線に当てはめたものであり、これは何よりも暗闇の中でのショットでした。O(n22)
— Michael Klein

回答:


2

繰り返しは書くことができます 特に、です。つまり、シーケンスは非常に急速に成長し、特に したがって、 これは、 およびそう これにより、平方根によって境界が改善されます。

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) +O(1))!)、T(n)=O(nO(1)(n/e)n)。
T(n−1)+2T(n−2)+⋯≤T(n−1)[1+2n+1n(n−1)+2n(n−1)(n−2)+⋯]=(1+O(1/n))T(n−1).
T(n)≤(n+O(1))T(n−1).
T(n)=O((n+O(1))!),
T(n)=O(nO(1)(n/e)n).
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.