web-dev-qa-db-ja.com

Cの二重階乗方程式のテール再帰

次の問題の末尾再帰ソリューションの実装が困難です。

上記の階乗にも依存する二重階乗には別の再帰関係があります(n <20の場合)。

enter image description here

私はこの方程式の再帰的な関係を実装する必要があります-これは、上記のコードとして機能しました

long long factorial(int n) {
    if (n < 0)
        return 0;
    if (n < 1)
        return 1;

     return n * factorial(n - 1);
}

long long doublefactorial(int n) {
    if (n < 0)
        return 0;
    if (n < 2)
        return 1;
    return factorial(n) / doublefactorial(n - 1);
}

今、私はtail recursionを使用して同じ問題を実装する必要があります。私はそれを理解できないので、誰かがこれを行う方法を教えてもらえますか? (階乗関数も末尾再帰的な方法で実装する必要はありません)

テストケース:

  • 5 !! = 15
  • 10 !! = 3840
  • 18 !! = 185,794,560
  • -10 !! = 0
3
Ronen Dvorkin

以下は、階乗関数の末尾再帰バージョンです。

long factorial(int n, int factor)
{
    if (n == 0)
        return factor;

    return factorial(n-1, factor * n);
}

factorial(5, 1); // 120

次に、より単純なロジックを使用した末尾再帰二重階乗を示します。

long doublefactorial(int n, int factor)
{
    if (n < 0)
        return 0;
    if (n < 2)
        return factor;

    return doublefactorial(n-2, factor * n);
}

printf("%d ", doublefactorial(5,1)); // 15
printf("%d ", doublefactorial(10,1)); // 3840
printf("%d ", doublefactorial(18,1)); // 185794560 
1
Guy_g23

数学を少し拡張すると、階乗関数の結果が最終結果の分子と分母の間で反復していることがわかります。

enter image description here

このコードはPythonでそれを行います

def _factorial(n, m):
    if n < 0:
        return 0
    Elif n == 0:
        return 1.0 * m
    return _factorial(n - 1, n * m)

def factorial(n):
    return _factorial(n, 1)

def _doublefactorial(n, m, is_even):
    if n < 0:
        return 0
    Elif n < 2:
        return 1.0 * m

    if is_even:
        m *= factorial(n)
    else:
        m /= factorial(n)

    return _doublefactorial(n - 1, m, (not is_even))

def doublefactorial(n):
    return _doublefactorial(n, 1, True)

そしてCでは:

unsigned int _factorial(const unsigned int n, const unsigned int m) {
    if (n < 0) {
        return 0;
    } else if (n == 0) {
        return m;
    }
    return _factorial(n - 1, n * m);
}

unsigned int factorial(const unsigned int n) {
    return _factorial(n, 1);
}

double _doublefactorial(const unsigned int n, const double m, const char is_even) {
    double value = m;

    if (n < 0) {
        return 0;
    } else if (n < 2) {
        return m;
    }

    if (is_even) {
        value *= factorial(n);
    } else {
        value /= factorial(n);
    }

    return _doublefactorial(n - 1, value, !is_even);
}

double doublefactorial(const unsigned int n) {
    return _doublefactorial(n, 1, 1);
}
1
thecohenoam

この double factorial 関数の定義は不完全です:0などの初期値が必要です!! = 1。繰り返しの定義から、p !!は1からppと同じパリティを持つ:

  • 5 !! = 1 * 3 * 5 = 15
  • 6 !! = 2 * 4 * 6 = 48
  • 10 !! = 2 * 4 * 6 * 8 * 10 = 3840

階乗を計算し、前の数値の二重階乗の結果で除算して二重階乗を計算すると、整数型の範囲が限定され、階乗関数が指数関数的に増加するため、19より大きい数値では失敗します。二重階乗も急速に増加しますが、その対数は階乗関数のそれの半分の速度で増加します。

これは再帰関数です:

unsigned long long doublefactorial(int n) {
    if (n < 0)
        return 0;
    if (n < 2)
        return 1;
    return n * doublefactorial(n - 2);
}

次に、ヘルパー関数を使用した末尾再帰実装を示します。

unsigned long long doublefactorial_helper(int n, unsigned long long res) {
    if (n < 2)
        return res;
    return doublefactorial(n - 2, res * n);
}

unsigned long long doublefactorial(int n) {
    return doublefactorial_helper(n, n >= 0);
}

最初の関数を末尾再帰関数に変換するトリックは、結果を待ってnを乗算する代わりに、更新された中間結果を再帰関数に渡します。乗算は逆の順序で実行されますが、同じ結果が生成されます(モジュロULLONG_MAX+1)。

0
chqrlie