6

私は最近、この (編集: 問題 A)興味深い問題に出くわしました。これは、今年初めに Spotify のハッカー チャレンジで発生したもので、列車を出発点に戻すための列車トラックのジャンクションでの切り替えを決定することを含みます。列車は出発したときと同じ方向を向いて到着しなければならず、線路上で逆走することはできません。

私が理解しているように、問題は、特定の頂点から最短のサイクルを見つけるか、そのようなサイクルが存在しないことを検出する必要がある無向 (?) グラフとしてモデル化できます。ただし、興味深い点は、頂点 v の場合、v に隣接する頂点は v までのパスに依存するため、ある意味ではグラフは有向と見なすことができますが、この方向はパスに依存します。

私が最初に考えたのは、各ノードを A、B、C の 3 つの個別の頂点 (A <-> B および A <-> C) としてモデル化し、幅優先検索を使用して元のノードが見つかるまで検索ツリーを構築することでした。頂点ですが、これは上記の警告によって複雑になります。つまり、特定の頂点の隣接関係は、以前に訪れた頂点に依存するということです。これは、BFS ツリーでは、ノードが複数の親を持つことができることを意味します。

この問題を解決するには、明らかに単純な BFS 検索では不十分です。グラフ内のサイクルを検出するアルゴリズムが存在することは知っています。1 つのアプローチとして、すべてのサイクルを検出し、各サイクルについて、パスが有効かどうかを検出することが考えられます。(つまり、方向を反転しません)

この問題を解決するためのアプローチについて、他の誰かが洞察を持っていますか?

更新: コメントで@Karussellが提案したアプローチに従いました。

これがgithubの私のソリューションです。

秘訣は、従来の頂点ベースのグラフではなく、エッジベースのグラフを使用して状況をモデル化することでした。コンテストで提供される入力ファイルは、既にエッジに関して便利に指定されているため、このファイルを使用してエッジベースのグラフを簡単に作成できます。

このプログラムは、Road と Solver という 2 つの重要なクラスを使用します。Road には、j1 と j2 の 2 つの整数フィールドがあります。j1 はソース ジャンクションを表し、j2 はターゲット ジャンクションを表します。各道路は一方通行です。つまり、j1 から j2 にしか移動できません。各 Road には、隣接する Road の LinkedList と親 Road も含まれます。Road クラスには、入力ファイルで使用される文字列と、各ジャンクションの A、B、および C ポイントを表す整数インデックスとの間で変換するための静的メソッドも含まれています。

入力ファイルのエントリごとに、HashMap に 2 つの Road を追加します。2 つのジャンクション間の各方向に 1 つの Road です。これで、ジャンクション間を走るすべての道路のリストができました。A、B、C スイッチを介してジャンクションで道路を接続するだけです。道路が Junction.A で終わる場合、Junction.B と Junction.C で始まる道路を検索し、これらの道路を隣接として追加します。buildGraph() 関数は、ターゲット ジャンクション (j2) が "1A" == インデックス 0 である道路を返します。

この時点で、グラフが作成されます。最短経路を見つけるために、単純に BFS を使用してグラフをトラバースしました。ルートにはマークを付けずに、ルートの隣接関係をキューに入れることから始めます。ターゲット ジャンクションが「1A」(インデックス 0) である道路が見つかった場合、開始点を通る最短のサイクルが見つかりました。各 Road の親プロパティを使用してパスを再構築したら、問題の必要に応じてスイッチを適切に設定するのは簡単なことです。

このアプローチを提案してくれた Karussell に感謝します。短い説明付きの回答フォームにコメントを入れたい場合は、それを受け入れます. @Originにも感謝します。私はあなたの答えの論理に完全に従わなかったことを認めなければなりませんが、それが正しくないと言っているわけではありません. 誰かがあなたのソリューションを使用してこの問題を解決した場合、私はそれを見ることに非常に興味があります.

4

2 に答える 2

1

私のコメントが示唆したように、エッジベースのグラフ、または多かれ少なかれ「強化された」ノードベースのグラフである改善によって、これを解決できると思います。

詳細:

  • あなたの状況は、道路網の右左折制限に似ています。通りごとに 1 つのノードを作成し、許可されたターンに応じてそのノードを接続すると、これらをモデル化できます。
  • したがって、現在の位置の位置だけでなく、方向と可能性のあるさらなる「状況」も保存してください。同じ姿勢でも180度回転して今の自分と違う状態にできるように。

「状態」(指示されている!) をグラフにモデル化する代わりに、考えられる結果をすべてのジャンクションに割り当てることもできます。今度は、アルゴリズムをより賢くする必要があり、ジャンクションごとに以前の状態に応じて何をすべきかを決定する必要があります (方向)。私は、これが「強化された」ノードベースのグラフの主なアイデアであり、メモリ集約型ではないはずだと思います(あなたの場合はそれほど重要ではありません)。

于 2012-11-03T11:28:29.860 に答える
1

考えられるアプローチの 1 つ: まず、すべての接続をモデル化する何らかのグラフを作成します (グラフ G)。次に、サイクルを見つける別のグラフを作成します (グラフ H)。G の各ノード A に対して、ノードをグラフ H に追加します。各 A ノードには、(グラフ G の B および C ノードへの) 2 つの出力エッジもあります。H では、これらのエッジは、G で検出される次の A ノードに移動します。 H. 各エッジの重みは、そのルートを通過したスイッチの数です (開始スイッチを含む)。

これにより、順方向最短パス ツリーを成長させることができるグラフが生成されます。再びスタートに到達すれば、サイクルは完了します。

重要なのは、スイッチが A-> 方向にトラバースされた場合にのみ、スイッチが決定ポイントになるということです。検索が複雑になるだけなので、逆方向をモデル化する必要はありません。

編集:さらに明確化

この問題は、A から A への最短経路を決定することから成ります (再び)。ここでの最短の定義は、渡されたスイッチの数です。これは、ダイクストラ ベースの検索アルゴリズムで使用されます。基本的に、エッジのコストがそのエッジのスイッチの数に等しいグラフ H でダイクストラを実行します。

H グラフには、各スイッチのノードがあります。各ノードには、取ることができる 2 つのパス (B および C 方向) に対応する 2 つの出力エッジがあります。H のエッジは、元のグラフの 2 つの A ノード間のルート全体に対応します。問題の説明の例では、次のようになります。

スイッチ 1 に対応するノード:

  • ノード 2 への 1 つの発信リンク、重み 2 は、スイッチ 1 を離れるときに C 方向を取ることに対応します。A1->C1->C3->A3->A2 から進む場合、スイッチ 1 とスイッチ 3 を通過するため、重みは 2 です。
  • ノード 3 への 1 つの発信リンク、重み 2、B 方向の取得に対応

スイッチ 2 に対応するノード:

  • ノード 6 への 1 つの発信リンク、重み 2、B 方向の取得に対応
  • C 方向は行き止まりのため、2 つ目のリンクはありません

スイッチ 3 に対応するノード:

  • ノード 6 への 1 つの発信リンク、重み 2、C 方向の取得に対応
  • ノード 9 への 1 つの発信リンク、重み 3、B 方向を取り、スイッチ 3、7、および 8 を通過することに対応

など、すべてのスイッチについて。これにより、それぞれが最大 2 つの有向エッジを持つ 10 個のノードを持つグラフが生成されます。

これで、ダイクストラ ツリーの構築を開始できます。ノード 1 から開始し、B と C の 2 つの方向が考えられます。これらを優先キューに入れます。2 つのスイッチを通過した後にスイッチ 2 の A 入口に到達し、2 つのスイッチを通過した後にスイッチ 3 の A 入口に到達できるため、キューには [node 2,weight 2] と [node 3, weight 2] が含まれます。次に、キューから最も重みの低いエントリを取得して検索を続けます。

  • [ノード 2, 重み 2]: B 方向のみを取るため、[ノード 6, 重み 4] をキューに入れます
  • [ノード 3、重み 2]: 2 つの方向があるため、[ノード 6、重み 4] と [ノード 9、重み 5] をキューに追加します。
  • [ノード 6、重み 4]: 2 方向が可能、[ノード 4、重み 5] と [ノード 8、重み 8] をキューに追加]
  • [ノード 9、重み 5]: C 方向のみ、[ノード 10、重み 6] を追加
  • [ノード 4、重み 5]: C 方向に [ノード 5、重み 7] を追加し、B 方向に [ノード 1、重み 9] を追加]
  • [ノード 10、重み 6]:C 方向に [ノード 1、重み 8] を追加し、B 方向に [ノード 1、重み 10] を追加します。
  • [ノード 5、重み 7]:[ノード 1、重み 11] と [ノード 8、重み 10] を追加します。
  • [ノード 8、重み 8]: [ノード 7、重み 9] を追加します。
  • [ノード 1、重み 8]: 戻る方法を見つけたので停止できます

(間違いの可能性があります、私はこれを手作業でやっています)

その後、アルゴリズムは 1 サイクルの最終的な長さ 8 で停止します。たどるパスを決定するには、ノードを解決してパスをアンパックするときに、ノードの親ポインターを維持するだけです。

H の各ノードは元のノード (G の) を正しい方向にトラバースすることに対応するため、ダイクストラを使用できます。次に、H の各ノードをダイクストラ方式で解決できるため、アルゴリズムの複雑さはダイクストラの複雑さに制限されます (スイッチ数の上限 100k を処理できます)。

于 2012-11-02T22:38:35.283 に答える