データ構造とアルゴリズムシリーズ - 深さ優先と幅優先

データ構造とアルゴリズムシリーズ - 深さ優先と幅優先

序文

データ構造とアルゴリズムシリーズ(完了部分):

  1. 時間計算量と空間計算量の分析
  2. 配列の基本的な実装と特徴
  3. リンクリストとジャンプリストの基本的な実装と特徴
  4. スタック、キュー、優先キュー、両端キューの実装と特性
  5. ハッシュテーブル、マップ、セットの実装と特性
  6. 木、二分木、二分探索木の実装と特徴
  7. ヒープとバイナリヒープの実装と特徴
  8. グラフの実装と機能
  9. 再帰の実装、特徴、重要なポイント
  10. 分割統治とバックトラッキングの実装と特徴
  11. 深さ優先探索と幅優先探索の実装と特徴
  12. 貪欲アルゴリズムの実装と特徴
  13. 二分探索の実装と特徴
  14. 動的計画法の実装とポイント
  15. タイヤツリーの基本的な実装と特徴
  16. union-findの基本的な実装と特徴
  17. 剪定の実装と特徴
  18. 双方向BFSの実装と特徴
  19. ヒューリスティック探索の実装と特徴
  20. AVL木と赤黒木の実装と特徴
  21. ビット演算の基礎と実践ポイント
  22. ブルームフィルタの実装と応用
  23. LRUキャッシュの実装と応用
  24. 初級ソートと上級ソートの実装と特徴
  25. 文字列演算

PS: 公式アカウントやGitHubではほとんど完成しており、後日Toutiaoにリンクが追加されます(外部リンクは許可されていません)

この記事では、深さ優先探索と幅優先探索について説明します。

検索とトラバーサルについて

検索に関しては、ほとんどの場合、いわゆる「総当たり検索」、つまり比較的単純で素朴な検索を扱います。つまり、検索するときには、いわゆるインテリジェントな状況は考慮されません。多くの場合、すべてのノードを 1 回走査して、必要な結果を見つけるという 1 つのことが行われます。

このようなデータ構造に基づいて、データ構造自体に何らかの特徴がない場合、つまり、それはごく普通のツリーまたはごく普通のグラフです。次に、すべてのノードをトラバースする必要があります。同時に、各ポイントが 1 回だけ訪問され、最終的に結果が見つかるようにします。

そこで、まず検索全体を簡略化し、次にツリーの状況に合わせて縮小してみましょう。

必要な値を見つけたい場合、このツリーで何をすればよいでしょうか?そうすると、ルートから始めて、まず左のサブツリーを検索し、次に次のポイントに 1 つずつ進み、終了したら右のサブツリーに進み、目的のポイントが見つかるまで続ける必要があることは間違いありません。これが私たちが使用する方法です。

データ構造の定義に戻ると、左サブツリーと右サブツリーだけがあります。

このようなトラバーサルや検索を実装したい場合、次の点を確実にする必要があります。

  • 各ノードは1回ずつ訪問する必要がある
  • 各ノードは一度だけ訪問される
  • ノードアクセスの順序に制限はありません
  • 深さ優先探索
  • 幅優先探索

一度だけ訪問するということは、検索中に無駄な訪問をあまり行わないことを意味します。そうしないと、アクセス効率が非常に低下します。

もちろん、他の検索も可能であり、他の検索は深さ優先や幅優先ではなく、優先度優先になります。もちろん、途中から他のものを優先するなど、任意に定義することもできますが、その定義は実際のシナリオに基づいている必要があります。したがって、一般的には深さ優先、幅優先、優先度優先と考えることができます。優先順位による検索は、実際には多くのビジネス シナリオに適しています。このアルゴリズムは一般にヒューリスティック検索と呼ばれ、ディープラーニングの分野でよく使用されます。このような優先順位は、例えば、さまざまな推奨アルゴリズムや高度な検索アルゴリズムでよく使用され、ユーザーが最も関心のあるコンテンツを検索できるようにしたり、毎日DouyinやKuaishouを開いたときに最も関心のあるコンテンツを推奨したりするのが、実はその理由です。

深さ優先探索 (DFS)

再帰的な記述

再帰書き込み方式は、再帰の終了条件から開始し、現在のレイヤーを処理してから次に進みます。

  • 次に、現在のレイヤーを処理することは、ノード node を訪問し、このノード node を訪問済みノードに追加するのと同じです。
  • 終了条件は、このノードが以前に訪問されたことがあるかどうかは関係ないということです。
  • 次に、下に行くと、その子ノードに移動します。バイナリ ツリーの場合は、左の子と右の子です。グラフの場合は、隣接するノードです。マルチ ブランチ ツリーの場合は、子です。次に、すべての子を 1 回トラバースします。

1. バイナリツリーテンプレート

Javaバージョン

  1. //C/C++
  2. //再帰的な書き込み:
  3. map< int , int > を訪問しました。
  4.  
  5. void dfs(ノード*ルート) {
  6. //ターミネータ
  7. if (!root)戻り値;
  8.  
  9. if (訪問回数( ルート->val) ) {
  10. // すでに訪問済み
  11. 戻る;
  12. }
  13.  
  14. 訪問[root->val] = 1;
  15.  
  16. // ここで現在のノードを処理します。
  17. // ...
  18. ( int i = 0 ; i < root->children.size ( ); ++i) {
  19. dfs(ルート->子[i]);
  20. }
  21. 戻る;
  22. }

Python バージョン

  1. #パイソン
  2. 訪問 =設定()
  3.  
  4. def dfs(ノード、訪問済み):
  5. ノード訪問済みの場合: # ターミネータ
  6. # すでに訪問済み
  7. 戻る  
  8.  
  9. 訪問しました。追加(ノード)
  10.  
  11. # ここで現在のノードを処理します。
  12. ...
  13. node.children()内のnext_nodeの場合:
  14. next_node 訪問した場所:
  15. dfs(次のノード、訪問済み)

C/C++ バージョン

  1. //C/C++
  2. //再帰的な書き込み:
  3. map< int , int > を訪問しました。
  4.  
  5. void dfs(ノード*ルート) {
  6. //ターミネータ
  7. if (!root)戻り値;
  8.  
  9. if (訪問回数( ルート->val) ) {
  10. // すでに訪問済み
  11. 戻る;
  12. }
  13.  
  14. 訪問[root->val] = 1;
  15.  
  16. // ここで現在のノードを処理します。
  17. // ...
  18. ( int i = 0 ; i < root->children.size ( ); ++i) {
  19. dfs(ルート->子[i]);
  20. }
  21. 戻る;
  22. }

JavaScript バージョン

  1. 訪問 =設定()
  2. def dfs(ノード、訪問済み):
  3. ノード訪問済みの場合: # ターミネータ
  4. # すでに訪問済み
  5. 戻る  
  6. 訪問しました。追加(ノード)
  7. # ここで現在のノードを処理します。
  8. ...
  9. node.children()内のnext_nodeの場合:
  10. next_node 訪問した場所:
  11. dfs(次のノード、訪問済み)

2. マルチブランチツリーテンプレート

  1. 訪問 =設定()
  2. def dfs(ノード、訪問済み):
  3. ノード訪問済みの場合: # ターミネータ
  4. # すでに訪問済み
  5. 戻る  
  6. 訪問しました。追加(ノード)
  7. # ここで現在のノードを処理します。
  8. ...
  9. node.children()内のnext_nodeの場合:
  10. next_node 訪問した場所:
  11. dfs(次のノード、訪問済み)

非再帰的な記述

Python バージョン

  1. #パイソン
  2. def DFS(自己、ツリー):
  3.  
  4. tree.rootNone の場合:
  5. 戻る[]
  6.  
  7. 訪問済み、スタック = []、[tree.root]
  8.  
  9. スタック中:
  10. ノード = stack.pop()
  11. 訪問しました。追加(ノード)
  12.  
  13. プロセス(ノード)
  14. ノード = generate_related_nodes(ノード)
  15. スタック.push(ノード)
  16.  
  17. # その他の処理作業  
  18. ...

C/C++ バージョン

  1. //C/C++
  2. //非再帰的な書き込み:
  3. void dfs(ノード*ルート) {
  4. map< int , int > を訪問しました。
  5. if(!root)戻り値;
  6.  
  7. スタック<Node*> stackNode;
  8. スタックノードをプッシュします(ルート);
  9.  
  10. スタックノードが空である間(!スタックノードが空である間){
  11. ノード* ノード = stackNode.top ();
  12. スタックノードをポップします。
  13. if (visited.count (node->val) )継続;
  14. 訪問[node->val] = 1;
  15.  
  16.  
  17. ( int i = node->children.size ( ) - 1; i >= 0; --i) {  
  18. ノードをスタックします。
  19. }
  20. }
  21.  
  22. 戻る;
  23. }

トラバーサル順序

深さ優先探索または深さ優先トラバーサルを見ると、トラバーサルの順序全体が常にルート ノード 1 から始まることは間違いありません。次にどのブランチに進んでも、実際には同じです。簡単にするために、左端から開始します。したがって、深さ優先の場合は、最後まで進みます。

多分岐ツリーテンプレートを参考にして、頭の中で図を描いたり、そのような構造である再帰状態ツリーを再帰的に描いたりすることができます。

  • 例えば、ルートを通り過ぎると、ルートはvisitedに最初に入れられ、ルートが訪問済みであることを示します。訪問された後、root.childernからnext_nodeを探します。そのnext_nodesはすべて訪問されていないので、最初に一番左のノードを訪問します。ここで、一番左のノードが最初に取り出された場合は、ルート以外のノードが訪問されていないため、visitedにないと判断されることに注意してください。そうでない場合は、直接dfsを呼び出し、next_nodeは一番左のノードを入れ、次にvisitedを一緒に入れます。
  • 再帰呼び出しの特徴は、ループの実行が完了するのを待たずに、直接次のレイヤーに進むことです。つまり、現在の夢の場合、ここにループが書かれていますが、最初のループにあるときに、新しい夢のレイヤーにドリルダウンを開始します。

グラフ走査順序

幅優先探索 (BFS)

幅優先トラバーサルでは、再帰やスタックは使用されなくなり、いわゆるキューが使用されます。これは、水滴が位置 1 に落ち、その水の波紋が層ごとに広がっていく様子を想像してみてください。

両者の比較

BFS コード テンプレート

  1. //Java
  2. パブリッククラス TreeNode {
  3. 整数値;
  4. TreeNode;
  5. TreeNode;
  6.  
  7. ツリーノード( int x) {
  8. val = x;
  9. }
  10. }
  11.  
  12. パブリックList<List< Integer >> levelOrder(TreeNode ルート) {
  13. List<List< Integer >> allResults = new ArrayList<>();
  14. ルートがnull場合
  15. すべての結果を返します
  16. }
  17. Queue<TreeNode> ノード = new LinkedList<>();
  18. ノードを追加します(ルート);
  19. ノードが空の場合
  20. 整数 サイズ= nodes.size () ;
  21. List< Integer > 結果 = new ArrayList<>();
  22. ( int i = 0; i <サイズ; i++) {
  23. TreeNode ノード = nodes.poll();
  24. 結果を追加します(node.val);
  25. ノード.left != null )の場合 {
  26. ノードを追加します(ノードの左)。
  27. }
  28. node.right != null )の場合 {
  29. ノードを追加します(ノードの)。
  30. }
  31. }
  32. allResults.add (結果);
  33. }
  34. すべての結果を返します
  35. }

  1. パイソン
  2. def BFS(グラフ、開始、終了):
  3. 訪問 =設定()
  4. キュー = []
  5. キューに追加([開始])
  6. キュー中:
  7. ノード = キュー.pop()
  8. 訪問しました。追加(ノード)
  9. プロセス(ノード)
  10. ノード = generate_related_nodes(ノード)
  11. キュー.push(ノード)
  12. # その他の処理作業  
  13. ...

  1. // C/C++
  2. void bfs(ノード*ルート) {
  3. map< int , int > を訪問しました。
  4. if(!root)戻り値;
  5.  
  6. キュー<Node*> queueNode;
  7. キューノードをプッシュします(ルート);
  8.  
  9. キューノードが空の場合(){
  10. ノード* ノード = queueNode.top ();
  11. キューノードをポップします。
  12. if (visited.count (node->val) )継続;
  13. 訪問[node->val] = 1;
  14.  
  15. ( int i = 0 ; i < node->children.size ( ); ++i) {
  16. queueNode.push(node->children[i]);
  17. }
  18. }
  19.  
  20. 戻る;
  21. }

  1. //JavaScript
  2. const bfs = (ルート) => {
  3. 結果 = []、キュー = [ルート]
  4. (キューの長さ > 0){
  5. レベル= []、n = キューの長さ
  6. ( i = 0; i < n; i++ とします) {
  7. ノードをキュー.pop() にします。
  8. レベル.push(node.val)
  9. if ( node.left ) queue.unshift( node.left )
  10. if ( node.right ) キュー.unshift( node.right )
  11. }
  12. result.push(レベル)
  13. }
  14. 結果を返す
  15. };

<<:  人工知能、自動化、新興技術のトレンドが4.6兆ドルの通貨市場に混乱をもたらしている

>>:  「インテリジェント接続」を理解するにはこの記事で十分です!

ブログ    
ブログ    

推薦する

速報です!李菲菲の一番弟子カルパシーが辞任、テスラの自動運転は危機に瀕しているのか?

たった今、テスラはまた別の技術専門家を失いました!テスラAIのシニアディレクターであり、自動運転ビジ...

物流の新たな勢いを刺激するGewutaiは、Anjiのインテリジェントマシンビジョンのスマート化を支援します

[[417396]]上海にある新エネルギー車を製造する全自動立体倉庫では、受注から製品出荷までの時間...

...

...

200語あれば本一冊分は読める。GPT-3はすでに小説の要約を書くことができる

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

海外AI界が騒然! Googleの黒人女性AI倫理研究者が「退職」し騒動を引き起こす

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

JavaScript ChatGPT プラグインの構築、学習しましたか?

チャット プラグイン システムは、ChatGPT の機能を拡張し、独自のビジネス データを組み込み、...

...

人工知能研究は行き詰まりに陥っているかもしれない

[51CTO.com クイック翻訳]フィリップ・K・ディックの1968年の小説『アンドロイドは電気羊...

いくつかのシンプルな負荷分散アルゴリズム

負荷分散とは負荷分散(英語名は Load Balance)とは、複数のサーバーを対称的に構成したサー...

2019 年の機械学習フレームワークの戦い: Tensorflow との競争は熾烈、進化する PyTorch はどこで勝利するのか?

[[278853]]ビッグデータダイジェスト制作出典: thegradient翻訳者: 張大毓如、...

7兆のブルーオーシャンが呼んでいる、ケータリングロボットの商業利用を加速させるには?

「機械が人に代わる」という無人化とインテリジェント化の潮流は、伝統的な飲食業界のあらゆる分野に広が...

AIの未来はブロックチェーンの未来とつながっているのでしょうか?

近代以降、ほぼすべての産業革命はさまざまな程度の自動化によって推進されてきました。これまでの産業革命...

...

...