アルゴリズム: Javascript をエレガントに使用して構造ツリーを再帰的に描画する方法

アルゴリズム: Javascript をエレガントに使用して構造ツリーを再帰的に描画する方法

[[376839]]

再帰と末尾再帰

簡単に言えば、再帰とは関数が自分自身を呼び出すことです。プログラミング言語のアルゴリズムとして広く使用されています。中心となる考え方は、大規模で複雑な問題を段階的に元の問題に似たより小さな問題に変換して解決することです。一般的に、再帰には境界条件、再帰的な前進フェーズ、および再帰的な戻りフェーズが必要です。境界条件が満たされない場合は再帰が前進し、境界条件が満たされた場合は再帰が戻ります。

しかし、有能なプログラマーとして、再帰アルゴリズムは通常のループなどの一般的に使用されるアルゴリズムよりも効率が低いことも知っておく必要があります。したがって、より優れたアルゴリズムがない場合、または特定の状況で再帰の方が適切な場合を除き、再帰は可能な限り避けるべきです。再帰呼び出しプロセス中、システムはスタックを開いて、各レイヤーの戻りポイント、ローカル量などを保存します。再帰回数が多すぎると、スタック オーバーフローが簡単に発生する可能性があります。

このとき、末尾再帰を使用する必要があります。つまり、関数内のすべての再帰呼び出しは、関数の最後に表示されます。末尾再帰の場合、呼び出しレコードは 1 つしかないため、「スタック オーバーフロー」エラーは発生しません。

  1. 関数階乗(n) {
  2. (n === 1)の場合は1を返します
  3. n * 階乗(n - 1)を返します
  4. }
  5.  
  6. 階乗(5) // 120

最大で n 個のコールスタックを保存する必要があり、その複雑さは O(n) です。末尾再帰を使用する場合:

  1. 関数階乗(n, 合計 = 1) {
  2. if (n === 1) 合計を返します
  3. 階乗を返します(n - 1, n * 合計);
  4. }
  5.  
  6. 階乗(5) // 120

この時点では、保存する必要がある呼び出しスタックは 1 つだけであり、複雑さは O(1) です。今回の事例を通じて、その本質が徐々に理解できたでしょうか?次に、再帰アプリケーションのよく使われる事例をいくつか紹介し、その後、この記事のタイトルで説明したツリーを実装していきます。

再帰の一般的な応用例

1. 配列の合計

指定された配列 arr について、arr 内のすべての項目の合計を計算します。

  1. 関数sumArray(arr, total) {
  2. (arr.length === 1)の場合{
  3. 合計を返す
  4. }
  5. 戻る 合計(arr、合計 + arr.pop())
  6. }
  7.  
  8. arr = [1,2,3,4]とします。
  9. 合計配列(arr, arr[1]) // 10

このメソッドは、配列パラメータと初期値(配列の最初の項目)を関数に渡し、反復処理を通じて配列の合計を実装します。

2. フィボナッチ数列

フィボナッチ数列は黄金比数列とも呼ばれ、1、1、2、3、5、8、13、21、34、... という数列を指します。数学では、フィボナッチ数列は次のように再帰的に定義されます。F(1)=1、F(2)=1、F(n)=F(n-1)+F(n-2)(n>=3、n∈N*)。フィボナッチ数列は、現代物理学、準結晶構造、化学などの分野に直接応用されています。次に、js を使用して n 番目のフィボナッチ数を見つけるメソッドを実装します。

  1. //フィボナッチ数列
  2. 関数階乗1(n) {
  3. (n <= 2)の場合{
  4. 戻り値1
  5. }
  6. 階乗1(n-1) + 階乗1(n-2)を返す
  7. }
  8.  
  9. // 末尾再帰最適化後
  10. 関数factorial2 (n, 開始 = 1, 合計 = 1) {
  11. (n <= 2)の場合{
  12. 合計を返す
  13. }
  14. 階乗2を返す(n -1, 合計, 合計 + 開始)
  15. }

末尾再帰最適化関数からは、関数自体が呼び出されるたびに更新された初期値と最終結果が渡され、バックトラックによって最終結果が得られることがわかります。

3. 階乗

階乗については上で説明しました。確認したい場合は、上にスクロールしてください。

4. 省と市のカスケード多階層連携

省市カスケード多階層リンク方式の本質は、構造化されたデータ構造を生成することです。elementやantdには対応する実装があるので、ここでは詳しく紹介しません。

5. ディープコピー

ディープコピーの例は誰もがよく知っているので、ここでは単純な実装のアイデアを示します。

  1. 関数クローン(ターゲット) {
  2. if (typeof ターゲット === 'オブジェクト' ) {
  3. cloneTarget を Array.isArray(target) とします。[] : {};
  4. for (constキー ターゲット){
  5. cloneTarget[キー] = clone(target[キー]);
  6. }
  7. cloneTargetを返します
  8. }それ以外{
  9. ターゲットを返します
  10. }
  11. };

6. はしご問題

全部で n 段の階段があります。一度に上れるのは 1 段か 2 段だけです。この階段を上るには、何通りの方法があるでしょうか。

  1. n =1; 結果 = 1 --> 1  
  2. n =2; 結果 = 2 --> 11 2  
  3. n =3; 結果 = 3 --> 111 12 21  
  4. ...
  5. 最初のステップが 1 ステップ歩くことである場合、上記の規則は、残りのステップを歩くには n-1 通りの方法があることを示しています。
  6. 最初のステップが 2 歩歩くことである場合、上記の規則は、残りのステップを歩くには n-2 通りの方法があることを示しています。
  7. 全部でfn(n-1) + fn(n-2)通りの方法がある
  8. 関数ステップ(n) {
  9. n <= 1の場合{
  10. 戻り値1
  11. }
  12. ステップ(n-1) + ステップ(n-2)を返す
  13. }

7. オブジェクトデータのフォーマット

この質問は、私が Alibaba で面接を受けたときの筆記試験の質問でした。質問は、「サーバーがネストされたオブジェクトを返す場合、オブジェクトのキー名の大文字と小文字は不明です。キー名を小文字で統一するにはどうすればよいですか?」というものでした。

  1. obj = {
  2. a: '1'
  3. b: {
  4. c: '2' ,
  5. D: {
  6. E: '3'  
  7. }
  8. }
  9. }
  10. 以下のように変換されます。
  11. obj = {
  12. a: '1'
  13. b: {
  14. c: '2' ,
  15. d: {
  16. e: '3'  
  17. }
  18. }
  19. }
  20.  
  21. // コード実装
  22. 関数keysLower(obj) {
  23. reg = new RegExp( "([AZ]+)" , "g" );
  24. for (letキー  obj){
  25. if (obj.hasOwnProperty(キー)) {
  26. temp = obj[キー]とします。
  27. if (reg.test(キー.toString())) {
  28. // 変更された属性名をtempに再割り当てし、変換された属性をオブジェクト obj に追加します。
  29. temp = obj[ key . replace (reg, function (result) {
  30. 結果を返す.toLowerCase()
  31. })] = obj[キー];
  32. // 以前大文字だったキー属性を削除します
  33. obj[キー]を削除します
  34. }
  35. // 属性がオブジェクトまたは配列の場合は、関数を再実行します
  36. if (typeof temp === 'object' || Object.prototype.toString.call( temp ) === '[object Array]' ) {
  37. keysLower( temp );
  38. }
  39. }
  40. }
  41. objを返します
  42. };

具体的なプロセスや考え方はコード内に注釈が付けられています。興味があれば、自分で勉強することもできます。

8. ディレクトリを走査/削除する

ここではディレクトリを削除するために node を使用します。既存の node API にはディレクトリを削除する機能がありますが、ディレクトリ内にファイルやサブディレクトリがある場合、fs.rmdir && fs.rmdirSync ではそれらを削除できません。そのため、まずディレクトリ内のファイルを削除してから、フォルダを削除する必要があります。

  1. 関数deleteFolder(パス) {
  2. var ファイル = [];
  3. if(fs.existsSync(path)) { // ディレクトリが存在する場合
  4. ファイル = fs.readdirSync(パス);
  5. files.forEach(関数(ファイル,インデックス) {
  6. var curPath = パス + "/" + ファイル;
  7. if(fs.statSync(curPath).isDirectory()) { // ディレクトリの場合は再帰的に
  8. フォルダーを削除します(現在のパス);
  9. } else { // ファイルを削除する
  10. fs.unlinkSync(curPath);
  11. }
  12. });
  13. fs.rmdirSync(パス);
  14. }
  15. }

9. フラクタルグラフィックを描く

再帰を使用すると、グラフィックスの自由度が高まりますが、これが最善の選択ではないことに注意してください。


いくつかのツールと再帰的な思考を使用することで、上記のフラクタル パターンを実現できます。

10. 配列のフラット化

配列のフラット化とは、次の例に示すように、ネストされた配列を単一の配列に拡張することを意味します。

  1. a = [1,2,3, [1,2,3, [1,2,3]]] とする
  2. // は
  3. a = [1,2,3,1,2,3,1,2,3]とする
  4. // 具体的な実装
  5. 関数flat(arr = [], 結果 = []) {
  6. arr.forEach(v => {
  7. Array.isArray(v) の場合
  8. 結果 = result.concat(flat(v, []))
  9. }それ以外{
  10. 結果.push(v)
  11. }
  12. })
  13. 結果を返す
  14. }
  15.  
  16. 平らな

もちろん、これは私が実装した方法の 1 つに過ぎず、他にも実装方法はたくさんあり、皆さんもぜひ試してみてください。

再帰を使用してカスタムスタイルの構造ツリーを描画する

上記の紹介を通じて、再帰とその応用の基本的な概念を皆さんは理解できたと思います。次に、再帰を使用して構造ツリーを描く方法を段階的に説明します。効果画像:

この図は、ディレクトリ構造に基づいて生成されたディレクトリ ツリー図です。多くのアプリケーション シナリオで広く使用されています。次に、その実装プロセスを見てみましょう。

  1. 定数 fs = require( 'fs' )
  2. 定数パス = require( 'パス' )
  3. // ディレクトリをトラバース/ディレクトリツリーを生成する
  4. 関数treeFolder(パス、フラグ = '|_' ) {
  5. var ファイル = [];
  6.      
  7. if (fs.existsSync(パス)) {
  8. ファイル = fs.readdirSync(パス);
  9. files.forEach(関数(ファイル,インデックス) {
  10. var curPath = パス + "/" + ファイル;
  11. if(fs.statSync(curPath).isDirectory()) { // 再帰
  12. // obj[file] = treeFolder(curPath, {});
  13. console.log(フラグ、ファイル)
  14. ツリーフォルダ(curPath, ' ' + フラグ)
  15. }それ以外{
  16. // obj[ '--' ] = ファイル
  17. console.log(フラグ、ファイル)
  18. }
  19. })
  20. //オブジェクトを返す
  21. }
  22. }
  23.  
  24. ツリーフォルダー(path.resolve(__dirname, './test' ))

Test は、次のように構築したテスト ディレクトリです。

わずか 12 行のコードで構造ツリーを生成する小さなアプリケーションを実装しました。再帰は興味深いと思いませんか? この関数では、最初のパラメーターはディレクトリの絶対パスで、2 番目は識別子です。識別子によって、生成されたブランチのスタイルが決まります。さまざまなスタイルをカスタマイズできます。

<<:  古典的なソートアルゴリズムヒープソートの簡単な分析

>>:  時代遅れにならないで、機械学習プラットフォームこそが未来だ

ブログ    
ブログ    
ブログ    

推薦する

2020年はAI関連ビジネスの発展にとって重要な年となる

今日、人々は仮想世界で触れることができるほぼすべてのものを作成し、さらに構築してきました。人工知能は...

マスク氏は5年以内に人間の言語を無意味にするだろうと言っているが、今回は狂気ではないかもしれない

イーロン・マスク氏は、わずか5年で人間の言語を無意味にすることができる技術に取り組んでいると述べてい...

自動運転や人工知能はあなたの将来の生活にどのような変化をもたらすでしょうか?

[[324253]] 01 自動運転車社会科学者は、郊外化、汚染、自由、家族旅行、命の喪失、救われ...

自己教師あり学習の概要と3つの主要分野における現状

近年、教師あり学習によるディープラーニングも大きな成功を収めています。画像分類から言語翻訳まで、その...

AI アシスタントの人気が高まっていますが、次に購入するスマートフォンはなぜ電話なのでしょうか?

大きな模型ブームが到来し、アイアンマンのジャービスが最も忙しい「マーベルヒーロー」(手動の犬の頭)に...

類似画像検索エンジンを効率的に開発するにはどうすればよいでしょうか?

翻訳者 | 朱 仙中校正 | 梁哲、孫淑娟プロジェクト紹介類似画像検索とは、関連するあらゆる画像を検...

アルトマン:解雇されて戻ってくるのは辛かったが、OpenAIにとっては良いことだ

1月8日、OpenAIのCEOサム・アルトマン氏は、タイム誌編集長とのインタビューで、昨年末に同社と...

OpenAI DALL-E 3モデルには「不適切なコンテンツ」を生成する脆弱性があり、マイクロソフトの従業員はそれを報告した後に「口止め命令」を受けた。

2月2日、マイクロソフトのソフトウェアエンジニアリング部門のマネージャーであるシェーン・ジョーンズ...

企業が生産性向上のためにAIを活用しようとする中、最高AI責任者の必要性が高まっている。

Foundry の 2023 年 AI 優先事項調査では、組織内で AI および AIGC テクノ...

ボストン・ダイナミクスの二足歩行ロボット「アトラス」が驚異的な体操ショーを披露、ネットユーザー「恐ろしい」

この記事はAI新メディアQuantum Bit(公開アカウントID:QbitAI)より許可を得て転載...

報酬のランダム化により、マルチエージェントゲームにおける多様な戦略的行動が発見され、研究者は新しいアルゴリズムを提案する

本論文では、研究者らは報酬空間を探索するための新しいアルゴリズム RPG (報酬ランダム化ポリシー勾...

...

...

一目でわかるアルゴリズム「配列と連結リスト」

データ構造はソフトウェア開発の最も基本的な部分であり、プログラミングの内部的な強さを反映しています。...

IT リーダーが避けるべき 6 つの生成 AI の危険性

多くの場合、さまざまな組織がさまざまな方法で生成 AI テクノロジーを適用しますが、それがもたらす悪...