アルゴリズム: 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 番目は識別子です。識別子によって、生成されたブランチのスタイルが決まります。さまざまなスタイルをカスタマイズできます。

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

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

ブログ    
ブログ    
ブログ    
ブログ    

推薦する

研究者は特別な画像を使って人工知能を「毒する」

DALL-E、Midjourney、Stable Diffusion などの AI 生成アート ツ...

AIファースト戦略に移行する5つの方法

ガートナーによると、AI は 2022 年までに世界中で 2.9 兆ドルのビジネス価値と 62 億時...

クロスカメラトラッキングと「スマート」な眼認識技術戦略の研究と実装

ラボガイド現在、公共の場や個人の応用場面に設置されている監視カメラの総数は1億7500万台を超えてい...

ドローン基地局は被災地の通信復旧にどのように役立つのでしょうか?

災害時において、通信は途切れることのできない生命線です。 [[412620]] 7月21日、河南省の...

CVPR 2017 論文の解釈: フィーチャーピラミッドネットワーク FPN

論文: 物体検出のための特徴ピラミッドネットワーク論文アドレス: https://arxiv.org...

公安部:「AI顔変え」事件79件を摘発、容疑者515人を逮捕

IT Homeは公安部の公式サイトから、公安部が8月10日に記者会見を開き、公安機関が国民の個人情報...

ガートナー:ディープフェイクと生成AIがゼロトラストの世界へ

この記事はLeiphone.comから転載したものです。転載する場合は、Leiphone.com公式...

世界トップジャーナルPNASに掲載されました!科学者たちは理論上のコンピューターに基づく意識モデル「意識のあるチューリングマシン」を提案した。

5月下旬、トップの国際学術誌である米国科学アカデミー紀要(PNAS)は、昨年10月に査読が受理され...

...

世界最大の多言語音声データセットがオープンソースになりました! 23言語で40万時間以上

[[416170]]この記事はAI新メディアQuantum Bit(公開アカウントID:QbitAI...

デジタルヒューマンのための大規模モデル

ビッグモデルはソフトウェア業界全体を変えるでしょう。その代表的な製品の一つがデジタルヒューマンです。...

スタートアップに適した AI ビジネス モデルを選択するにはどうすればよいでしょうか?

[[406810]] [51CTO.com クイック翻訳]人工知能技術は企業が行うビジネスにとって...

李碩:AIは産業知能の波を促進する

2020年12月29日、2020年産業インターネットイノベーション大会(第4回)が盛大に開幕しました...

...

...