序文 再帰は非常に重要なアルゴリズムの考え方です。フロントエンド開発者であっても、バックエンド開発者であっても、これを習得する必要があります。日常業務では、フォルダーのサイズをカウントしたり、XML ファイルを解析したりするために再帰アルゴリズムが必要です。これは非常に基本的かつ重要なため、面接官は面接中に再帰アルゴリズムを手作業で記述するように求めることがよくあります。この記事では、再帰アルゴリズムについて学びます〜
再帰とは何ですか? コンピューター サイエンスにおける再帰とは、問題を類似のサブ問題に繰り返し分割することで問題を解決する方法を指します。簡単に言えば、再帰は関数が自分自身を呼び出すときに発生します。私は Zhihu で再帰の例を見ました。非常に鮮明だと思います。見てみましょう: 「再帰の最も適切な比喩は辞書を引くことです。私たちが使う辞書はそれ自体が再帰的です。単語を説明するには、より多くの単語が必要です。単語を調べると、その単語の説明にまだ理解できない単語があることに気づき、2 番目の単語を調べ始めます。残念ながら、2 番目の単語にもまだ理解できない単語があるため、3 番目の単語を調べます。これを繰り返し、説明を完全に理解できる単語が見つかるまで続けます。その後、再帰は終了し、後退し、以前に調べた単語を 1 つずつ理解し始め、最終的に最初の単語の意味を理解します。」 ❞ 試しに、次のような再帰コードの例を見てみましょう。
再帰の特徴 実際、再帰には終了条件と自己呼び出しという 2 つの重要な機能があります。
上記のデモ コード例と組み合わせて、再帰の特性を見てみましょう。 再帰とスタックの関係 実際、再帰プロセスはスタックに出入りするプロセスとして理解できます。この比喩は、読者が再帰をよりよく理解できるようにするためのものです。上記のコード例では、sum(n=3) のスタック ダイアグラムを次のように計算します。 理解しやすくするために、次のように関数 sum(n=5) の再帰実行プロセスを見てみましょう。
再帰の典型的な応用シナリオ 再帰を使用して解決できる問題にはどのようなものがありますか? 言い換えると、再帰の一般的な適用シナリオは何ですか?
再帰的な問題解決のアイデア 再帰的な問題を解決するには、通常、次の 3 つのステップが必要です。
この再帰的な問題解決方法は理解するのが少し抽象的なので、階乗再帰の例を見てみましょう。 1. 関数の機能を定義する 関数を定義するには、その関数が何を行うかを知る必要があります。言い換えれば、元の再帰問題が何であるかを知る必要があります。たとえば、階乗問題を解く必要がある場合、定義する関数は次のように n の階乗になります。
2. 再帰終了条件を見つける 再帰の典型的な特徴は、終了条件が存在する必要があることです。つまり、無限ループ内で再帰自体を呼び出すことはできません。したがって、再帰的な思考を使用して問題を解決する場合は、再帰の終了条件が何であるかを調べる必要があります。たとえば、階乗問題では、n=1 の場合、それ以上再帰する必要がなく、ループから抜け出すことができます。次のように、n=1 を再帰の終了条件として使用できます。
3. 再帰関数の同値関係 再帰の「本来の意味」は、元の問題を類似した、より解決しやすいサブ問題に分解できること、つまり「元の問題とサブ問題が同じ関数関係で表現できること」です。再帰関数の等価関係式は、元の問題とサブ問題の関係を見つけ、その関数をいかにして明確に数式で表現するかということに等価です。階乗の式は f(n) = n * f(n-1) と表すことができるため、再帰階乗プログラム コードは次のように記述できます。
「注意してください」、再帰関数の同値関係はすべて階乗のように単純で、すぐに導出できるわけではありません。もっと触れて、もっと蓄積して、もっと考えて、もっと再帰的な質問を練習する必要があります〜 Leetcode ケース分析 典型的な LeetCode の再帰質問を分析してみましょう。 ❝元の質問のリンクはこちらです: https://leetcode-cn.com/problems/invert-binary-tree/❞ 「質問:」二分木を反転します。 入力:
出力:
私たちは、上記の再帰的な問題解決の 3 つのトリックに従います。 「1. 関数を定義する」 関数 function (つまり、元の再帰問題) はツリーを与えてそれを反転することなので、関数は次のように定義できます。
「2. 再帰終了条件を見つける」 このツリーを反転する必要がないのはどのような場合でしょうか? もちろん、現在のノードが null の場合、または現在のノードがリーフ ノードである場合です。したがって、終了条件を追加すると次のようになります。
「3. 再帰関数の同値関係」 元々の問題は、ツリーを反転することです。これをサブ問題に分割して、それぞれ左のサブツリーと右のサブツリーを反転することはできますか? 左のサブツリーを反転するというサブ問題は、左のサブツリーの左のサブツリーと左のサブツリーの右のサブツリーを反転することに分割できますか? 次に、リーフ ノードに到達するまで反転を続けます。まあ、理解するには写真を見てください。 まず、ルート ノードが 4 のツリーを反転する場合、「その左のサブツリー (ルート ノードは 2) と右のサブツリー (ルート ノードは 7) を反転」する必要があります。これは再帰的なプロセスです。 次に、ルート ノード 2 を持つツリーはリーフ ノードではないため、「左のサブツリー (ルート ノードは 1) と右のサブツリー (ルート ノードは 3) を反転」し続ける必要があります。ノード 1 と 3 は両方とも「リーフ ノード」であるため、返されます。これも再帰的なプロセスです。 同様に、ルート ノード 7 を持つツリーはリーフ ノードではないため、左のサブツリー (ルート ノード 6) と右のサブツリー (ルート ノード 9) を反転する必要があります。ノード 6 と 9 はリーフ ノードなので、これらも返されます。 左のサブツリー(ルートノードは2)と右のサブツリー(ルートノードは7)を反転した後、これらのステップは「返される」、つまり再帰的な戻りプロセスとなり、ツリーを反転するタスクが完了します。 明らかに、再帰関係は次のようになります。
したがって、次のコードを簡単に導き出すことができます。
ここでのコードでは、注意する必要があることが 1 つあります。ツリーの左と右のサブツリーを反転した後、左と右のサブツリーの参照位置も入れ替える必要があります。
したがって、Leetcode におけるこの古典的な再帰的質問の「最終的な解決コード」は次のようになります。
最終的なソリューションコードをLeetCodeに送信すれば合格します〜 再帰の問題
スタックオーバーフロー問題
「コード例は次のとおりです。」
「ランニング結果:」 スレッド「main」で例外が発生しました。java.lang.StackOverflowError が recursion.RecursionTest.sum(RecursionTest.java:13) で発生しました。 このスタックオーバーフローの問題を解決するにはどうすればよいでしょうか。まず、「再帰を最適化する」必要があります。本当に何度も再帰的に呼び出す必要がありますか。本当に必要な場合は、まず「JVM スタック スペース メモリを増やす」ことを少し試してください。それでも問題が解決しない場合は、再帰を放棄して「他のソリューションに最適化する」必要があります。 繰り返し計算を行うとプログラム効率が低下する 古典的なカエルのジャンプ問題を見てみましょう。カエルは一度に 1 段ずつジャンプすることも、一度に 2 段ずつジャンプすることもできます。カエルが n 段の階段をジャンプして上る方法が何通りあるか調べます。 ほとんどの読者は、この問題を解決するために次の再帰コードを簡単に思いつくでしょう。
しかし、LeetCode に提出したところ、問題が発生しました。制限時間を超過してしまったのです。 なぜタイムアウトしたのでしょうか? 再帰に時間がかかっているのはどこでしょうか? まず「再帰ツリー」を描いてみましょう: 元の問題f(10)を解くには、まずサブ問題f(9)とf(8)を解く必要がある。 次にf(9)を計算するには、まずサブ問題f(8)とf(7)を計算する必要があります。 再帰ツリーはf(2)とf(1)に到達すると終了します。 まず、この再帰の時間計算量を見てみましょう。「再帰の時間計算量 = サブ問題を解く時間 * サブ問題の数」
したがって、カエルスキップ再帰ソリューションの時間計算量は O(1) * O(2^n) = O(2^n) となり、指数関数的かつ爆発的な増加となります。「n が比較的大きい場合、タイムアウトは正常です。」 振り返って、この再帰ツリーをよく見ると、「繰り返し計算が多数」あることがわかります。たとえば、f(8) は 2 回計算され、f(7) は 3 回計算されます...つまり、この再帰アルゴリズムが非効率な理由は、繰り返し計算が多数あることです。 「それで、この問題をどうやって解決すればいいのでしょうか?」 繰り返し計算が多いので、まず計算結果を保存、つまりメモを作成します。次回必要になったときに、「メモ」で確認できます。メモがある場合は直接取得できます。メモがない場合は、再度計算できます。このようにして、繰り返し計算する時間を節約できます。これが「メモ付き解法」です。 メモ化による再帰的なソリューションを見てみましょう〜 一般的に、この「メモ」としては配列やハッシュマップが使用されます。 f(10)の解が「memo」で追加されたと仮定して、再帰ツリーをもう一度描画してみましょう。 「ステップ1」、f(10) = f(9) + f(8)、次のようにf(9)とf(8)の両方を計算してメモに追加する必要があります。 「ステップ2」、f(9) = f(8) + f(7)、f(8) = f(7) + f(6)、f(8)はすでにメモにあるので省略できます。f(7)とf(6)を計算してメモに追加する必要があります。 「ステップ3」、f(8) = f(7) + f(6)では、f(8)、f(7)、f(6)はすべてメモに記載されているので、すべて切り取ることができます。 したがって、メモ再帰アルゴリズムを使用すると、再帰ツリーは次のように裸のツリー トランクになります。 「メモ」を使用した再帰アルゴリズムの場合、サブ問題の数 = ツリー ノードの数 = n であり、サブ問題の解決は依然として O(1) であるため、「メモ」を使用した再帰アルゴリズムの時間計算量は O(n) です。次に、「memo」を使用した再帰アルゴリズムを使用して、カエルのステップ問題のタイムアウト問題をコーディングして解決します。コードは次のとおりです。
leetcode にアクセスして送信すると、図に示すように安定しています。 この問題には他の解決策がありますか? メモ化による再帰的な解決策だけですか? 実際、動的プログラミングを使用して解決することもできます。 この記事はWeChat公式アカウント「カタツムリを拾う少年」から転載したものです。以下のQRコードからフォローできます。この記事を転載する場合は、カタツムリを採る少年の公式アカウントまでご連絡ください。 |
<<: 8つの一般的なアルゴリズムのアイデアを説明する1つの記事
この記事は、公開アカウント「Reading the Core」(ID: AI_Discovery)か...
[[250982]] 2015年以来、人工知能の概念は初めて提案されて以来、市場から高く評価されて...
2010 年以前は、トレーニング コンピューティングの開発はムーアの法則に沿って 2 年ごとに 2 ...
この記事はLeiphone.comから転載したものです。転載する場合は、Leiphone.com公式...
「人工知能の女王」ジャスティン・カッセル氏が済南の中国重汽で「人工知能と世界の未来経済」について講演...
多くのディープラーニング手法は優れたマッティング結果を実現しますが、高解像度の画像を適切に処理するこ...
MIT-IBM Watson AI ラボの研究者たちは、電力網の問題のトラブルシューティングに人工知...
2019 年のベスト オープンソース プロジェクトを選択するために、Medium のネットユーザーが...
[[405206]]時が経つにつれて、技術は変化してきました。自動化に関しては、今年は徐々に成果が...
人工知能とデータサイエンス、機械学習のトレンドとデータ分析AIはますますあらゆるビジネス戦略の一部に...