再帰思考の型入れ子構造を解き明かす
15分38秒 | アルゴリズム再帰FE
基本情報技術者試験の頻出テーマを解説した音声コンテンツです。
トランスクリプト(字幕テキスト)
やあ、大晦日ですね。ええ、本当に。あっという間ですね。 この時期になるとあの鏡餅とか、おせち料理が入っている重箱とか。 はいはいはい。ああいうこう、物が入れ子になってる光景ってよく目にしますよね。見かけますね。 実はその入れ子っていう考え方がプログラミングの世界にあるすごくパワフルでちょっとこう、 頭がこんがらがりそうになる概念を理解するのに最高のヒントになるんです。ええー。 今回の資料はその再帰、再帰と呼ばれるプログラミング手法の解説スクリプトですね。 はい。プログラムが自分自身を呼び出すっていう言葉だけ聞くと、それって永遠に終わらないループじゃないの?って思っちゃうような不思議な考え方で。 まあ、そう思いますよね、普通は。 これを今日は身近な例え話を聞きながら、その本質を掴んでいきたいなと。 ええ。そして今日一番お伝えしたいのは、これがプログラマーだけの特別なテクニックじゃないっていうことなんです。 ほう。実はあなたが普段何か大きな問題を解決しようとするとき、無意識のうちにこの再帰的な考え方を使ってる可能性が結構あるんですよ。 へえー、無意識にですか。それは興味深いですね。今回はその正体に光を当てて、なぜこれが強力なツールなのかを解き明かしていければと思います。 なるほど。では早速その考え方の入り口から見ていきましょうか。はい。 資料では再帰を理解するための最高のイメージとしてマトリョーシカ人形を挙げています。ええ。 あの大きな人形を開けると、中から全く同じ形のちょっと小さな人形が出てきて、それを開けるとまた…で続いていくあれですね。 まさに。あれが再帰の本質をもう完璧に捉えてるんです。ほう。 大きな問題を解決するためにいきなり全体に取り組むんじゃなくてですね。はい。 その問題と全く同じ構造を持った一回り小さな問題を見つけ出して、それに解決を委任すると。 委任する。ええ、この委任の連鎖が、まあ再帰的アプローチなんですね。 人形を一つ開けるっていう行為が、プログラムが自分自身を呼び出す、つまり自分より少し小さな自分にあとは頼んだって仕事を丸投げするイメージです。 ああ、面白いですね。なんか大きなプロジェクトをタスクに分解していくのに似てる気もします。 うんうん。例えばウェブサイトを作るっていう一番大きな人形があって、それをパカッと開けるとデザイン、開発、公開っていう人形が出てくる。 ああ、なるほど。いや、でもこれだと形が違いますかね?お、素晴らしい指摘です。 デザインとウェブサイトを作るは同じ構造の問題ではないですもんね。まさにそこが重要なポイントなんです。 あなたの言う通りそれは単なるタスクの分解。再帰が成立するためには分解した後の小さな問題が元の問題と同質である必要があるんです。 同質。ええ、マトリョーシカ人形ってサイズは違ってもどれも形は同じじゃないですか。 確かに。それと同じで、解決すべき問題の構造が同じじゃないといけないんですね。 なるほど、構造が同じというのがキーワードなんですね。イメージが少し具体的になりました。 ええ。では実際の計算例として資料に載っている5の階乗を見ていきましょうか。これは数学で習う5×4×3×2×1のことですよね。 はい。普通に計算すれば答えは120です。でもこれを再帰の考え方で解いてみましょう。はい。 まず5の階乗を計算するという問題を次のように言い換えるんです。5に4の階乗の結果をかければ答えが出ると。 ああ、なるほど。そこで問題が4の階乗を計算するという、より小さな同じ構造の問題にすり替わったわけですね。 その通りです。5の階乗の計算プログラムは、自分で4以下の計算は一切しないんです。しない。 おい、4の階乗。君の計算結果を教えてくれと、自分と全く同じ能力を持つ一つ小さなプログラムを呼び出すだけなんです。 はあー。そして呼び出された4の階乗プログラムもまた同じように、4に3の階乗の結果をかければいいと考えて3の階乗を呼び出す。 これが続いていきます。なるほど、どんどん小さくなるのは理解できたんですが、ええ。 あの正直に言うと、それって普通のループ処理、プログラミングでよく使うforループで5から1まで順番にかけていくのと何が違うんですかね? ああ、素晴らしい視点です。なんだか、わざわざ難しく遠回りしてるようにも聞こえるんですが。 まさにそこが多くの人が最初に抱く当然の疑問なんですよ。やっぱりそうですか。 ええ。おっしゃる通り、階乗のようなこういう単純な一直線の計算だとループ処理の方が直感的ですし、コンピュータの内部的な効率も良い場合が多いんです。 あ、そうなんですね。 はい。なのでこの例はあくまで再帰の仕組みを説明するための、まあ古典的な教材だと思ってください。 じゃあ再帰の本当の価値っていうのはどこにあるんですか? 再帰の真価が発揮されるのは、問題が一直線じゃなくてこう木の枝のように分岐していく構造を持っている場合なんです。 木の枝のように。ええ、例えばこのフォルダの中にあるすべてのファイルを探すという問題を考えてみてください。 はい。フォルダの中にはファイルだけじゃなくて、別のフォルダ、サブフォルダも入ってますよね。 ああ、なるほど。そのサブフォルダの中にもさらにサブフォルダが入ってるかもしれない。 そうです。これってフォルダを調べるっていう処理の中に、またフォルダを調べるっていう同じ構造の処理が出てくる、まさに入れ子構造じゃないですか。 確かに。これをループで書こうとすると階層がいくつあるかわからないので、ものすごく複雑なコードになるんです。 うわあ、考えただけでも大変そう。 でも再帰なら、フォルダを調べるっていう関数を一つ作るだけで、もしサブフォルダを見つけたら自分自身を呼び出してそのフォルダを調べさせると書くだけでいい。 へえー。どんなに深い階層でもすごくエレガントに解決できるんです。 ははあ、なるほど。階乗の例では見えなかった再帰のパワフルさがそのファイル検索の例で一気にわかりました。 遠回りに見えて、実は複雑な問題への最短ルートだったりするわけですね。まさに。 さて、話を階乗に戻しましょうか。はい。 先ほどこのプロセスが延々と続いていくと言いましたが、ここで一つ根本的な問題が出てきますよね。 ですよね。さっきからそれが気になってました。5が4を呼び、4が3を呼びって続けていくと、1、0、-1ってどこまでも止まらないんじゃないですか? ええええ。これではプログラムが無限に自分を呼び出し続けて、最終的にはエラーで止まっちゃいますよね。おっしゃる通りです。 マトリョーシカもいつかは一番小さな人形にたどり着くわけですけど、この計算はどうやって止まるんでしょう? そこが再帰を設計する上でもう勝敗を分ける最も重要な部分です。勝敗を分ける。 だからこそ再帰には必ずこれ以上分解できない最小単位とその場合の答えを明確に定義した停止条件が不可欠になるんです。 停止条件。マトリョーシカで言えばこれ以上開けられない中身が詰まった一番小さな人形のことですね。 それがないと存在しない人形を延々と開けようとしてしまいますから。命綱みたいなもんですね。 階乗の例だとその停止条件は何になるんですか?1の階乗は1であるという、これはもう誰もが知っている数学的なルールです。はい。 プログラムの中に、もし計算を頼まれた数字が1だったら、もうそれ以上自分を呼び出すのはやめてただ1という答えを返しなさいとはっきり書き込んでおく。 これがアンカーになって無限の連鎖を断ち切るんです。なるほど。一番奥深くの一番小さな人形にたどり着いて、 その中に入っていた答えは1ですっていう紙を見つけたみたいな。ええ、そんなイメージです。 資料には順番に箱を閉じて戻ってくる感じとありますが、この後何が起こるんですか? ここからが再帰のもう一つの面白い側面、帰り道のプロセスです。帰り道。 今まで問題を分解するために坂道を転がり落ちてきたわけですけど、今度はその道を逆の順番で登りながら答えを組み立てていくんです。 答えの組み立てですか。はい。具体的に見ていきましょう。お願いします。 まず一番深く最後に呼び出された1の階乗の計算が停止条件に従って1という答えを返します。はい。 この答えは自分を呼び出した張本人、つまり2の階乗の計算に返されるんです。 あ、2の階乗は2に1の階乗の結果をかけるっていう計算をしようとしてずっと待ってたわけですもんね。そうなんです。 そこで初めて1という具体的な数字を受け取れるんだ。その通りです。 そして1を受け取った2の階乗の計算は、そこで初めて2×1を実行して2という答えを確定させます。ふむふむ。 そして今度はこの2という答えを自分を呼び出した3の階乗に返すのです。 ということは、次にその2を受け取った3の階乗が3×2で6を計算してそれを4の階乗に返す。わあ、面白い。 そうなんです。まるで伝言ゲームみたいに答えがどんどん大きく育ちながら戻っていくんですね。まさに雪だるま式に組み立てられていく。 4の階乗は4×6で24を計算してそれを返します。 そして最終的に一番最初にすべてを発注した5の階乗の計算までその24という答えが戻ってくるわけです。 そこで初めて5×24が実行されて120という最終結果が得られると。ああ、その帰り道のプロセス、すごく腑に落ちました。 よかったです。行きはひたすらお願いするだけで、本当の計算は一番底にたどり着いてから答えが戻ってくるときに一気に片付けられていく感じなんですね。 これは目から鱗です。ええ、この行きで分解し帰りで組み立てるという往復のイメージを持つことが再帰を理解する鍵ですね。なるほど。 そして資料にある実践的なアドバイスですが、もしあなたが今後コードを読んでいて、ある関数の中でその関数自身の名前が出てきたらそれはもう十中八九再帰です。 ああ、はい。その時にまず探すべきなのがどこで終わるのか、つまり停止条件はどこに書かれているかということですね。 おっしゃる通り。まず出口がどこにあるかを確認してから迷宮全体の流れを追うと。その通りです。 停止条件がなければプログラムは破綻しますから、必ずどこかにあるはずなんです。もしこの条件なら処理を終えるという部分を見つければ、 その再帰が何を目指していて、どこで底を打つのかがみえてきます。 でもさっきのファイル検索の例みたいに便利な一方で、階乗の例ではループの方が良いという話もありました。ええ。 それに、なんとなく処理が複雑でコンピュータに負荷がかかりそうなイメージもあるんですが、再帰を使う上での注意点とかデメリットみたいなものはあるんですか? 良い質問ですね。デメリットも明確に存在します。最大の注意点はメモリの使用量です。メモリ。 先ほどの帰り道の話を思い出してください。5の階乗が4の階乗を呼び出し、4の階乗は答えが返ってくるまで待機していますよね。はい、待ってますね。 コンピュータはその待機状態を記憶しておくためにメモリ上に作業スペースを確保するんです。ふむふむ。 再帰が深くなればなるほど、つまり3の階乗、2の階乗と呼び出しが重なるたびに、その作業スペースがどんどん積み重なっていくんです。あー。 これをコールスタックと呼びますが、あまりにも深い再帰を行うと、このスタックがメモリの上限を超えて溢れてしまうことがあります。溢れる。 スタックオーバーフローというプログラマーにとっては悪夢のようなエラーが発生します。うわあ、それは怖いですね。 際限なくマトリョーシカ分けていったら部屋が人形で埋め尽くされてしまった、みたいな状況ですか?まさにそんなイメージです。 なので再帰を使うのは、先ほどのファイルシステムのように問題の構造そのものが再帰的で、コードが劇的にシンプルになる場合に限るのが賢明ですね。 なるほど。単純な繰り返しで済むならループ処理を選ぶべきです。なるほど、頭の中がかなり整理できました。 つまり一番大事なのは解きたい問題の構造が入れ子になってるかを見極めること。ええ。 そして使うと決めたら絶対に停止条件を忘れないこと。さらにあまりに深くやりすぎないか注意すること。この辺りがポイントという理解で合っていますか? 完璧な要約です。再帰は単なるプログラミングのテクニックではなくて、物事をどう捉えどう分解するかというより普遍的な思考の型なんです。 思考の型。これは分割統治法という古くからある問題解決のアプローチの根幹をなす考え方でもあります。 分割して統治する。ええ、解決できないほど大きな敵に直面したとき、それを同じ構造を持つより小さな敵の集まりに分割できないかと考えるわけです。はい。 そして最も弱くて簡単に倒せる敵、つまり停止条件ですね。そこから一つずつ片付けていき、その勝利を積み重ねて最終的に大きな敵全体を支配下に置く。はあー。 この考え方はプログラミング以外に様々な分野に応用できるんです。例えば企業の組織構造もそうかもしれませんね。 会社全体という大きな単位があって、その中に事業部という少し小さな単位がある。はいはい。 事業部の中には部があり、課があり、それぞれが利益を追求するという同じ構造を持っているとみなせればこれも再帰的な構造だと捉えられますね。 まさにその通りです。あるいは自然界に見られるフラクタル図形。フラクタル。 海岸線とか雪の結晶、あとロマネスコっていう野菜の形とか、一部分を拡大すると全体と同じ形が繰り返されていますよね。あー、ありますね。 あれも再帰的な構造の、まあ美しい現れです。この視点を持つと物事の自己相似性とか階層構造の本質がクリアに見えてくることがあります。 面白いなあ。一つのプログラミング概念から組織論や自然科学にまで話が繋がっていくんですね。 ええ。さて、そろそろ時間ですが、この分解して最小単位から組み立てるという再帰的パターン、あなたの仕事や日常生活の中に他にどこで見つけられるでしょうか? うんうん。例えば大きなプロジェクトを計画する時、最初に最も簡単なタスク、つまり停止条件に当たるものを終わらせてそこから自信をつけて組み上げていくとか。 ああ、いいですね。あるいは新しいスキルを習得する時に基本の型、最小単位ですね。それを完璧にマスターしてその組み合わせで応用技を編み出していく。 意識してみるとあなたの周りにもたくさんのマトリョーシカが隠れているかもしれません。
このコンテンツは Web society で視聴・学習できます。