再帰木エクスプローラー

再帰の定義を選び、n を設定すると、呼び出しの総数と、すでに解かれた問題を再び解いている呼び出しの数がわかります。

インタラクティブシミュレーションを読み込んでいます...

1を数えて計算するフィボナッチ 🖖

n を 20 に設定しましょう。木全体の呼び出し回数は 21,891 回です。そのうち 10,946 回がベースケースに到達し、残りの 10,945 回は足し算のステップです。1 を返すのは fib(1) のみで、fib(0) は 0 を返します。つまり、葉のうち 6,765 個が 1 を持ち、4,181 個は何も持たないということです。F(20) は 6,765 であり、これはまさに 1 の数です。再帰が返す値は、これらを足し合わせたものにすぎません。n が変わっても同じです。ベースケースのカードは常に F(n+1) を示し、その中の 1 の数は常に F(n) となります。

21,891回の呼び出しを持つ二つの木。反復するのは一方だけ 🖖

単純な fib(20) と 10,946 要素のマージソート。この二つの呼び出し回数は全く同じです。21,891 回の呼び出しは、10,946 回のベースケースと 10,945 回の結合ステップに分かれます。六枚のカードのうち三枚は同じ値を示します。どちらの定義も内部ノードで二つに分岐するため、分岐の仕方が違いを生んでいるわけではありません。違いは独立した部分問題の数です。21 対 21,891。メモ化テーブルを使うと、フィボナッチの木は 21 単位の作業に圧縮されます。しかしマージソートは 21,891 のままです。マージソートの半分ずつは単一の配列の異なる部分を切り出したものであり、「5,473 個の要素の列」に対するキャッシュされた答えは、全く別の 5,473 個の要素には使えないからです。

たった一つの「引かれる1」が φⁿ と 2ⁿ を分ける 🖖

ハノイとフィボナッチはどちらも二度再帰し、到達する深さも 20 と 19 でほとんど同じです。n = 20 のとき、ハノイは 2,097,151 回の呼び出しを行います。一方、フィボナッチは 21,891 回です。これは 95.8 分の 1 にすぎません。コード上の唯一の違いは、二番目の再帰の引数です。ハノイは n−1 で自分自身を二度呼び出しますが、フィボナッチは n−1 の後に n−2 で呼び出します。このたった一つの引き算が、指数の底を 2 から φ = 1.618 へと下げます。そして、その差は広がり続けます。n = 30 になると、2,147,483,647 に対して 2,692,537。実に 798 倍の差に達します。

全プロセスの詳細解説

  1. 単純なfib(10)が177回の呼び出しを行う場合の呼び出し回数の閉形式 6 ステップ

    単純な fib(10) は 177 回、fib(11) は 287 回の呼び出しを行うとツールは示しています。呼び出し回数の閉形式(一般項)を求めて証明してください。そして、一秒間に 108 回の呼び出しを処理できるコンピュータにおいて、単純なフィボナッチ数列が一秒間でどこまで計算できるか導き出してください。

    1. それぞれの回数に 1 を足すと、178 と 288 になります。どちらも偶数であり、その半分は 89 と 144 です。これは F11 と F12 に一致します。したがって、F(1) = F(2) = 1 としたとき、推測される式は C(n) = 2F(n+1) − 1 となります。

    2. この二倍という数字がどこから来るのかは、木を見るとわかります。すべての内部ノードは正確に二回の呼び出しを行い、葉は一度も呼び出しを行いません。したがって、L 個の葉を持つ木には L − 1 個の内部ノードがあり、ノードの総数は 2L − 1 となります。ツールによれば、n = 10 でのベースケースは 89 個です。だから 2 × 89 − 1 = 177 なのです。

    3. まずはベースケースです。fib(0)fib(1) は再帰せずに値を返すため、C(0) = C(1) = 1 となります。そして、2F(1) − 1 と 2F(2) − 1 はどちらも 1 に等しくなります。

    4. 次に帰納法のステップです。n − 1 と n − 2 においてこの式が成り立つと仮定します。n での呼び出しは、一つのノードとその下にある二つの部分木を足したものです。そこに含まれる二つのフィボナッチ数は、数列の定義そのものによって自然に一つにまとまります。n = 11 とすると 2 × 144 − 1 = 287。ツールが表示する値と完全に一致します。

    5. ここで木から離れましょう。ビネの公式は φ = 1.6180、|ψ| < 1 として F(m) = (φm − ψm)/√5 を与えます。そのため F(m) は φm/√5 を最も近い整数に丸めた値となり、C(n) ≈ 2φn+1/√5 と近似できます。一秒間に 108 回の速度であれば、一秒間でちょうど 108 回の呼び出しを行えます。これにより、φn+1 は 1.1180 × 108 に固定されます。

    6. 対数をとります。ln(1.1180 × 108) = 18.5323、ln φ = 0.4812 なので、n + 1 = 38.51 となり、n = 37.5 と求められます。端数分の呼び出しは完了しないため、切り捨てます。

    解答

    n = 37。ツールを見れば前後の値を確認できます。n = 37 では 78,176,337 回、n = 38 では 126,491,971 回の呼び出しです。これ以降は n が 1.44 増えるごとに作業量が二倍になります。ln 2 / ln φ = 1.44 だからです。一方、n = 37 のとき、独立した部分問題のカードには 38 と表示されます。三十八の質問に答えるために七千八百万の呼び出しを行う。この一行こそが、動的計画法を学ぶ最大の理由です。

  2. 二本より遅く育つ三本の枝 6 ステップ

    1〜3段ずつ上る階段は各ノードで自分を三回呼び、ハノイの塔は二回呼びます。n=20の階段上り(3分岐)を読み込み、二つの木のどちらが速く育つかを求め、分岐数が指標としてどれだけ役に立つかを判断してください。

    1. 漸化式は絵からではなく定義から読み取ります。ノード自身の呼び出しが1回、そこから n − 1、n − 2、n − 3 で3回、そして2以下なら再帰せずに返ります。

    2. 呼び出し回数が幾何級数的に増えると仮定して C(n) ≈ A xⁿ を代入します。両辺を x の n − 3 乗で割ると、先頭の1は低次の雑音として置き去りになり、三次方程式が残ります。その唯一の実根が 1.8392868、トリボナッチ定数です。

    3. 同じ手順を残る二つの定義に当てはめます。フィボナッチは n − 1 と n − 2 で自分を呼ぶので x² = x + 1 となり、根は φ = 1.6180340 です。ハノイは n − 1 で二回呼ぶので x = 2 となり、解くものは何もありません。

    4. 信じる前に確かめます。n を29にして呼び出し回数のカードを読み、30にしてもう一度読み、割ります。階段は1.83929、フィボナッチは1.61803、ハノイはちょうど2。三つとも小数第五位まで一致します。

    5. 根が教えてくれないのは回数そのものです。1.8393²⁰ は196,331ですが、カードの表示は128,287。前に0.65という係数が付いています。ハノイの呼び出しはちょうど 2ⁿ⁺¹ − 1 で、係数は2です。ベースケースがこの定数を決め、漸化式が根を決めます。そして最後に勝敗を決めるのは根だけです。

    6. その最後は、ここではすぐに訪れます。n = 20 でハノイの呼び出しは階段の16.3倍、n = 30 では37.8倍になります。この比は8.3ステップごとに倍になります。ln 2 を ln(2 ÷ 1.8393) で割ると8.27だからです。

    解答

    速く育つのはハノイです。2 対 1.8393。三本目の枝は、階段にとって払えないほどの負担ではありませんでした。分岐数は増加率の上限であり、それに届くのは子がすべて一段だけ小さいときだけです。階段は三回の呼び出しのうち二回を、2および3だけ小さい引数に使っています。それらの部分木は十分に軽く、増えたはずの一本分にまで積み上がることがありません。

    手元に残す価値があるのは手順のほうです。誰も先に解いていない定義にも使えるからです。子の数を数え、それぞれが引数をいくつ減らすかを見て、最大の減少分を指数とする x を、残りの減少分の x の和と等しいと置き、最大の実根を取る。ひとつだけ注意が要ります。これは素朴な木についての事実であり、その素朴な木は誰も書かないものだということです。独立した部分問題のカードは、n = 20 で階段が21、ハノイも21を示します。メモ化を入れれば指数そのものが消え、増加率は問題でなくなります。

学習の道すじ

秒ではなく仕事量を数える

この次に 経路探索

例題