グラフアルゴリズムシリーズ: 計算グラフにおける最短経路

グラフアルゴリズムシリーズ: 計算グラフにおける最短経路

[[398324]]

この記事はWeChatの公開アカウント「Beta Learns JAVA」から転載したもので、著者はSilently9527です。この記事を転載する場合は、Beta Learning JAVA パブリック アカウントにお問い合わせください。

序文

前の 2 つの記事では、深さ優先探索を使用して、グラフ内の頂点 v から頂点 w までのパスを見つけることができました。ただし、深さ優先探索は頂点の入力に大きく関係しており、見つかったパスは必ずしも最短ではありません。通常、map 関数など、グラフ内の最短パスを見つける必要があることがよくあります。ここでは幅優先探索アルゴリズムを使用する必要があります

幅優先探索

以前に定義されたパス検索APIを引き続き使用します

  1. パブリッククラスPaths{
  2. パス(グラフ グラフ、 int s);
  3.      
  4. boolean hasPathTo( int v); // s->v からのパスがあるかどうかを判定する
  5.      
  6. Iterable< Integer > pathTo( int v); //パスが存在する場合はパスを返す
  7. }

幅優先探索では、最短経路を見つけるために、再帰を使用してそれを達成するのではなく、開始点の順序ですべての頂点をトラバースする必要があります。アルゴリズムの考え方は次のとおりです。

  • マークされているが、隣接リストがまだ走査されていない頂点を格納するためにキューを使用します。
  • キューから次の頂点vを取り出してマークする
  • vに隣接するマークされていない頂点をすべてキューに追加します。

このアルゴリズムでは、パスを保存するために、エッジ配列 edgeTo[] を使用し、親リンク ツリーを使用して、ルート ノードから接続されているすべての頂点までの最短パスを表す必要があります。

  1. パブリッククラスBreadthFirstPaths {
  2. プライベートブール値 marked[];
  3. プライベートint []エッジTo;
  4. プライベートint s;
  5. プライベート Queue< Integer > queue = new LinkedListQueue<>();
  6.  
  7. パブリックBreadthFirstPaths(グラフグラフ、 int s) {
  8. this.s = s;
  9. this.marked = 新しいブール値[graph.V()];
  10. this.edgeTo = 新しいint [graph.V()];
  11.  
  12. bfs(グラフ, s);
  13. }
  14.  
  15. プライベートvoid bfs(グラフグラフ、 int s) {
  16. this.marked[s] = true ;
  17. this.queue.enqueue(s);
  18. while (!this.queue.isEmpty()) {
  19. 整数v = this.queue.dequeue();
  20. ( int w : graph.adj(v))の場合{
  21. if (!this.marked[w]) {
  22. this.marked[w] = true ;
  23. this.edgeTo[w] = v;
  24. this.queue.enqueue(w);
  25. }
  26. }
  27. }
  28.  
  29.  
  30. }
  31.  
  32. パブリックブール値hasPathTo( int v) {
  33. this.marked[v]を返します
  34. }
  35.  
  36. パブリックイテラブル<整数> pathTo( int v) {
  37. (!hasPathTo(v))の場合{
  38. 新しい IllegalStateException をスローします ( "s は v に到達できません" );
  39. }
  40. Stack<整数> stack = new LinkedListStack<>();
  41. スタックをプッシュします。
  42. (edgeTo[v] != s) の間 {
  43. スタックをプッシュします(edgeTo[v]);
  44. v = エッジ[v];
  45. }
  46. スタックをプッシュします。
  47. スタックを返します
  48. }
  49. }

次の図を列としてとらえ、幅優先探索の軌跡を見てみましょう。

ユニットテストコード:

  1. @テスト
  2. パブリックボイドテスト(){
  3. グラフ graph = new Graph(8);
  4. グラフにエッジを追加します。(0, 1);
  5. グラフにエッジを追加します(0, 2);
  6. グラフにエッジを追加します(0, 5);
  7. グラフにエッジを追加します。(1, 3);
  8. グラフにエッジを追加します。
  9. グラフにエッジを追加します(4, 3);
  10. グラフにエッジを追加します(5, 3);
  11. グラフにエッジを追加します(6, 7);
  12.  
  13. BreadthFirstPaths パス = new BreadthFirstPaths(graph, 0);
  14. システム.out.println (paths.hasPathTo(5));
  15. System.out.println (paths.hasPathTo(2)) ;
  16. システム.out.println (paths.hasPathTo(6));
  17.  
  18. paths.pathTo(5).forEach(System.out:: print );
  19. System.out.println( ) ;
  20. paths.pathTo(4).forEach(System.out:: print );
  21. System.out.println( ) ;
  22. paths.pathTo(2).forEach(System.out:: print );
  23. System.out.println( ) ;
  24. paths.pathTo(3).forEach(System.out:: print );
  25.  
  26. }

実行結果は、私たちが分析した走行軌跡と一致しています。

シンボル図

最近の記事で学んだグラフ アルゴリズムはすべて、数値を頂点として使用しています。これは、数値を使用してこれらのアルゴリズムを実装するのが非常に簡単で便利だからです。ただし、実際のシナリオでは、地図上の場所や映画と俳優の関係など、数値ではなく文字が頂点として使用されることがよくあります。

実際のシナリオに対応するには、数字と文字列のマッピングを作成する必要があります。このとき、前回の記事で実装したマップ(マップはバイナリツリー、赤黒木、ハッシュテーブルなどを通じて実装されます。興味のある学生は調べることができます)を考え、Mapを使用して文字列と数字のマッピング関係を維持します。

  1. パブリックインターフェースSymbolGraph {
  2. boolean contains (String key ); //頂点が存在するかどうかを判断します
  3.  
  4. 整数  index (String key ); //名前で対応するデジタル頂点を返す
  5.  
  6. String name ( int v); //デジタル頂点を通じて対応する文字名を返す
  7.  
  8. グラフ graph();
  9. }

実装のアイデア:

  • マップを使用して文字列と数値のマッピングを保存します。キーは文字列、値は数値です。
  • 配列を使用して、数値から文字列へのマッピングを逆にします。配列の添え字は数値の頂点に対応し、値は文字列名に対応します。
  • 構築されたグラフの頂点と辺が、a、b、c、d\nb、a、h、l、p\ng、s、z などの文字列で表されるとします。\n で区切られた各セグメントの最初の文字列は頂点 v を表し、次の文字列は頂点 v に接続された隣接頂点を表します。

実際のプロセスは、特定の状況に応じて決定されます。必ずしもこの種類の文字列である必要はありません。データベースまたはネットワーク要求から取得できます。

コードは次のように実装されます。

  1. パブリッククラスSymbolGraph {
  2. プライベート Map<String, Integer > map = new RedBlack23RedBlackTreeMap<>();
  3. プライベートString[]キー;
  4. プライベートグラフグラフ;
  5.  
  6. パブリックシンボルグラフ(文字列テキスト) {
  7. Arrays.stream(text.split( "\n" )).forEach(行 -> {
  8. 文字列[]を分割 = line.split( "," );
  9. ( int i = 0; i < split.length; i++) {
  10. map.put([i], i);
  11. }
  12. });
  13.  
  14. this.keys = 新しい文字列[ map.size ()];
  15. map.keys().forEach(キー-> {
  16. this.keys[this.map.get( key )] =キー;
  17. });
  18.  
  19. this.graph = 新しいグラフ(this.keys.length);
  20. Arrays.stream(text.split( "\n" )).forEach(行 -> {
  21. 文字列[]を分割 = line.split( "," );
  22. 分割されたマップ0に分割します。
  23. ( int i = 1; i < split.length; i++) {
  24. this.graph.addEdge(v, this.map.get(split[i]));
  25. }
  26. });
  27.          
  28. }
  29.  
  30. パブリックブール値には(文字列キー)が含まれます
  31. map.contains (キー)を返します
  32. }
  33.  
  34. 公共 整数 インデックス(文字列キー) {
  35. map.get(キー)を返します
  36. }
  37.  
  38. パブリック文字列( int  索引) {
  39. this.keys[インデックス]を返します
  40. }
  41.  
  42. パブリックグラフグラフ() {
  43. this.graphを返します
  44. }
  45.  
  46. 公共 静的void main(String[] args) {
  47. システム.out .println(Arrays.toString( "323\n2323" .split( "\n" )));
  48. }
  49. }

この記事のすべてのソースコードは、github リポジトリに保存されています: https://github.com/silently9527/JavaCore

<<:  AIに関する4つの最も一般的な誤解

>>:  50億のブルーオーシャンが呼び寄せる、電力検査ロボットが最前線に

ブログ    

推薦する

滴滴出行の米国研究責任者:インテリジェント運転は間違いなく未来を変えるだろうが、そのプロセスは単純ではない

6月20日、滴滴出行研究院副院長兼アメリカ研究院長のゴン・フェンミン博士が、TechCrunch I...

...

具現化された知能の新時代! VLAは、UIナビゲーションとロボット操作を備えた最強の基本モデルMagmaを歓迎します

既存の大規模言語モデル、画像生成モデルなどは、少数のモーダルデータに対してのみ動作し、人間のように物...

...

自動車AI市場は2027年までに70億ドルに達する

世界の自動車人工知能市場規模は、2022年の23億米ドルから2027年には70億米ドルに成長すると予...

MIT、指の爪ほどの大きさのドローンを作れるマイクロチップを設計

MITの研究者らが、指の爪ほどの小さなドローン用コンピューターチップを設計6月21日、Venture...

完全なルーティングアルゴリズムの設計目標の分析

ルーティング アルゴリズムには通常、次の 1 つ以上の設計目標があります。最適化最適化とは、メトリッ...

このAI商用リストをお見逃しなく: 生産上の問題はアプリケーションによって解決されるかもしれません

[[219776]]リアム・ヘーネル編纂者:趙怡雲、江宝尚、銭天培人工知能があらゆる分野に浸透してい...

セキュリティ分野におけるドローン技術応用の現状

[[422011]] 2013年、バハマで仕事をしていたとき、私は現在ドローンとして知られているもの...

...

市場情報調査 | モノのインターネット市場における人工知能

現在、機械学習とディープラーニング技術は、IoT 向け人工知能の世界市場で 5.7% の CAGR ...

原理から応用まで: ロジスティック回帰アルゴリズムの簡単な説明

ロジスティック回帰は、バイナリ分類タスクで最も一般的に使用される機械学習アルゴリズムの 1 つです。...

...

自然言語処理シーケンスモデル - CRF 条件付きランダムフィールド

シーケンスモデルにおけるHMM(隠れマルコフモデル)を習得した後は、別のシーケンスモデルであるCRF...