応用情報技術者 2024年 春期 午後 問03
グラフのノード間の最短経路を求めるアルゴリズムに関する次の記述を読んで、設問に答えよ。
グラフ内の二つのノード間の最短経路を求めるアルゴリズムにダイクストラ法がある。このアルゴリズムは、車載ナビゲーションシステムなどに採用されている。
〔経路算定のモデル化〕
グラフは、有限個のノードの集合と、その中の二つのノードを結ぶエッジの集合とから成る数理モデルである。ダイクストラ法による最短経路の探索問題を考えるに当たり、本問では、エッジをどちらの方向にも行き来することができ、任意の二つのノード間に経路が存在するグラフを扱う。ここで、グラフを次のように定義する。
・ノードの個数を N とし、N は 2 以上とする。ノードの番号 (以下、ノード番号という) は、始点のノード番号を 1 とし、1 から始まる連続した整数とする。ノードには、ノード番号に対応させて、V1, V2, V3, …、VN とラベルを付ける。
・二つのノードが他のノードを経由せずにエッジでつながっているとき、それらのノードは隣接するという。隣接するノード間のエッジには、ノード間の距離として正の数値を付ける。
・始点のノード (以下、始点という) と別のノードを終点のノード (以下、終点という) として定める。始点からあるノードまでの経路の中から、経路に含まれるエッジに付けられた距離の和が最小の距離を最短距離といい、始点から終点までの最短距離となる経路を最短経路という。
図1にノードが五つのグラフの例を示す。図1の例では、始点を V1 のノードとし、終点を V5 のノードとした場合の最短経路は、V1, V2, V3, V5 のノードを順にたどる経路である。

〔始点から終点までの最短距離を求める手順〕
ダイクストラ法による始点から終点までの最短距離の算出は次のように行う。
最初に、各ノードについて、始点からそのノードまでの距離(以下、始点ノード距離という)を作業用に導入して十分に大きい定数としておく。ただし、始点の始点ノード距離は0とする。この時点では、どのノードの最短距離も確定していない。
次に、終点の最短距離が確定するまで、①〜③を繰り返す。ここで、始点との距離を算出する基準となるノードを更新起点ノードという。
① 最短距離が確定していないノードの中で、始点ノード距離が最小のノードを更新起点ノードとして選び、そのときの始点ノード距離の値で、当該更新起点ノードの最短距離を確定する。更新起点ノードを選ぶ際に、始点ノード距離が最小となるノードが複数ある場合は、その中の任意のノードを更新起点ノードとして選ぶ。
② 更新起点ノードが終点であれば、終了する。
③ ①で選択した更新起点ノードに隣接しており、かつ、最短距離が確定していない全てのノードについて、更新起点ノードを経由した場合の始点ノード距離を計算する。ここで計算した始点ノード距離が、そのノードの現在までの始点ノード距離よりも小さい場合には、そのノードの現在までの始点ノード距離を更新する。
〔図1の例における最短距離を求める手順と始点ノード距離〕
図1の例において、始点V1から終点V5までの経路に対して、上の①〜③を繰り返し適用する。そのとき、更新起点ノードを選ぶたびに、更新起点ノードの始点ノード距離、更新起点ノードと隣接するノードの始点ノード距離、及び最短距離が確定していないノードの始点ノード距離を計算した内容を表1に示す。

〔最短距離の算出プログラム〕
始点から終点までの最短距離を求める関数 distance のプログラムを図2に示す。
配列の要素番号は 1 から始まるものとする。また、行頭の数字は行番号を表す。

〔最短経路の出力〕
関数 distance を変更して、求めた最短距離となる最短経路を出力できるようにする。具体的には、まず、ノード番号1〜nを格納する配列 viaNode を使用するために、図3の変数宣言を図2の行10の直後に、図4のプログラムを図2の行21の直後に、それぞれ挿入する。さらに、各ノードの始点ノード距離を更新するたびに、直前に経由したノード番号を viaNode に格納する①代入文を一つ、図2のプログラムの行オの直後に挿入する。
このプログラムの変更によって、終点のノード番号を起点として カ などとすることで、最短経路のノード番号を逆順に出力する。


〔計算量の考察〕
関数 distance では、次の キ を選ぶために始点ノード距離を計算する回数は最大でも N 回である。また、キ を選ぶ回数は、一度選ばれると当該ノードの最短距離は確定するので、最大でも N 回である。よって、最悪の場合の計算量は、O(ク) である。
設問1:
表1中の ア に入れる適切な字句を答えよ。
模範解答
ア:17
解説
解答の論理構成
- 表1「3回目」終了時点での状況を確認します。更新起点ノードが “V4<13>” に確定し、その結果、最短距離が未確定のノードは “V3<14>, V5<19>” です。
- 4回目では “① 最短距離が確定していないノードの中で、始点ノード距離が最小のノード” として “V3<14>” が選択され、更新起点ノードになります。
- 手順③に従い、更新起点ノード “V3” と隣接している未確定ノード “V5” について距離を再計算します。隣接距離は【図01】で示された “V3―V5:3” です。
- 再計算式
- 手順③の条件「ここで計算した始点ノード距離が、そのノードの現在までの始点ノード距離よりも小さい場合」に合致します。現在の “V5<19>” より小さいので更新し、表1「4回目」の “ア” には “17” が入ります。
したがって、解答は “17” となります。
誤りやすいポイント
- 既に確定したノード(done=1)を再計算対象に含めてしまい、更新不要な距離を再度上書きしてしまう。
- “V3―V5:3” ではなく “V4―V5:6” と取り違え、 と計算して誤答する。隣接関係の把握ミスに注意が必要です。
- 表1の数値 “V5<19>” を見落とし、「現距離と同値なら更新しない」というルールと混同してしまう。ダイクストラ法は「より小さい」かを判定します。
FAQ
Q: 既に最短距離が確定したノードを再び更新起点に選ぶことはありますか?
A: ありません。“最短距離が確定していないノードの中で” と明記されており、確定済み(done=1)のノードは対象外です。
A: ありません。“最短距離が確定していないノードの中で” と明記されており、確定済み(done=1)のノードは対象外です。
Q: “ア” の値は経路が複数あった場合も一意に決まりますか?
A: はい。距離が等しい経路が複数あっても、距離そのものは同じになるため “ア” は一意です。
A: はい。距離が等しい経路が複数あっても、距離そのものは同じになるため “ア” は一意です。
Q: “INF” はどのくらい大きく設定すべきですか?
A: アルゴリズム上は「どんな実際の経路距離よりも大きい」値であれば良く、多くの実装では int 型最大値付近(例:2,147,483,647)を用います。
A: アルゴリズム上は「どんな実際の経路距離よりも大きい」値であれば良く、多くの実装では int 型最大値付近(例:2,147,483,647)を用います。
関連キーワード: ダイクストラ法, 最短経路, 無向グラフ, 重み付きエッジ
設問2:
図2中の イ 〜 エ に入れる適切な字句を答えよ。
模範解答
イ:dist[k]がminDistより小さい
ウ:dist[k]
エ:dist[curNode] + edge[curNode, k]
解説
解答の論理構成
-
更新起点ノードの選択条件
【問題文】には「① 最短距離が確定していないノードの中で、始点ノード距離が最小のノードを更新起点ノードとして選び」とあります。
図2の行15
if (done[k]が0 かつ イ)
は「未確定かつ最小距離」を判定する箇所なので、
イ = dist[k]がminDistより小さい
となります。 -
最小距離値の保持
更新起点ノードとして採用したノードの距離を変数 minDist に記録するのが行16
minDist ← ウ
です。直前で イ を満たしたノードの距離そのものを代入するため、
ウ = dist[k]
となります。 -
隣接ノードへの距離更新式
行25~26はステップ③「更新起点ノードを経由した場合の始点ノード距離を計算」に対応します。
図2の行25
if (エ が dist[k] より小さい かつ done[k]が0)
は「curNode を経由した暫定距離が現在値より小さいか」を判定する部分で、式は
dist[curNode] + edge[curNode, k]
です。よって
エ = dist[curNode] + edge[curNode, k]
となります。
以上より、解答は
・イ:dist[k]がminDistより小さい
・ウ:dist[k]
・エ:dist[curNode] + edge[curNode, k]
・イ:dist[k]がminDistより小さい
・ウ:dist[k]
・エ:dist[curNode] + edge[curNode, k]
誤りやすいポイント
- 「最小」を“≦”で比較するか“<”で比較するか迷う
INF 初期化のため重複選択を防ぎたければ“<”が安全です。 - 行25の式に edge[k, curNode] と書くミス
無向グラフでも配列の添字順を揃えないと距離更新が正しく行えません。 - done[curNode] ← 1 の位置を勘違い
更新より先に確定フラグを立てないと、同じノードを再度候補に入れるバグが起きます。
FAQ
Q: minDist を毎回 INF で初期化する理由は何ですか?
A: 各イテレーションで「まだ確定していないノードの中での最小値」を再計算するためです。前回の最小値を残しておくと、より大きい値が残って誤選択する恐れがあります。
A: 各イテレーションで「まだ確定していないノードの中での最小値」を再計算するためです。前回の最小値を残しておくと、より大きい値が残って誤選択する恐れがあります。
Q: 配列 done を使わずに dist[k]==INF で判定してはいけませんか?
A: dist は暫定距離を随時更新するため、最短距離が確定済みかどうかと値が無限大かどうかは一致しません。確定フラグを別に持つ方が安全です。
A: dist は暫定距離を随時更新するため、最短距離が確定済みかどうかと値が無限大かどうかは一致しません。確定フラグを別に持つ方が安全です。
Q: 重みが負数でも同じアルゴリズムで動きますか?
A: ダイクストラ法は「各エッジの重みが正」であることが前提です。負辺が含まれると正しい最短経路を保証できません。
A: ダイクストラ法は「各エッジの重みが正」であることが前提です。負辺が含まれると正しい最短経路を保証できません。
関連キーワード: ダイクストラ法, 始点ノード距離, 最小値探索, 隣接ノード更新, 無向グラフ
設問3:〔最短経路の出力〕について答えよ。
(1)本文中の下線①と オ について、挿入すべき代入文と オ に入れる行の番号を答えよ。行の番号については、最も小さい番号を答えること。ただし、図2中の現在の行の番号は図3及び図4の挿入によって変化しないものとする。
模範解答
代入文:viaNode[k]←curNode
オ:25
解説
解答の論理構成
-
代入文の内容
【問題文】では
「各ノードの始点ノード距離を更新するたびに、直前に経由したノード番号を viaNode に格納する」
と指示されています。
距離を更新する箇所は図2の
26: dist[k] ← エ
です。ここで採用した経路の“直前に経由したノード”は、現在の更新起点ノード curNode なのでviaNode[k] ← curNodeが唯一の適切な代入文となります。 -
挿入位置(行番号)の決定
代入は
「始点ノード距離を更新するたびに」
行わなければなりません。条件判定は行25、実際の距離更新は行26です。
・行25直後に挿入すれば、条件が真(すなわち距離が短縮される)である場合のみ実行されます。
・行26直後でも機能しますが、問題文は「最も小さい番号」を求めています。
よって最小の適合法は行25直後です。 -
結論
代入文:viaNode[k] ← curNode
オ に入る行番号:25
誤りやすいポイント
- 行26直後に置くと正しく動くため、「25 と 26 のどちらでも良い」と勘違いしがちですが、設問は「最も小さい番号」を要求しています。
- viaNode[curNode] と誤って書くケースが多いですが、更新対象は隣接ノード k です。
- 代入を if 文の外(行27以降)に置くと、距離が短縮されない場合でも上書きされ、経路復元が壊れます。
FAQ
Q: 行24の for ループの前に viaNode を初期化する必要はありますか?
A: 初期値は 0 でよいと【問題文】に明記されているので追加初期化は不要です。
A: 初期値は 0 でよいと【問題文】に明記されているので追加初期化は不要です。
Q: 距離更新と同時に viaNode を記録するメリットは?
A: 更新が行われた瞬間に“最短経路の直前ノード”が確定するため、後で逆順にたどるだけで経路を復元できます。追加の探索は不要です。
A: 更新が行われた瞬間に“最短経路の直前ノード”が確定するため、後で逆順にたどるだけで経路を復元できます。追加の探索は不要です。
Q: プログラム全体の計算量に viaNode の代入が影響しますか?
A: 代入は距離更新と同じ回数だけ行われ、既に O(ク) と評価済みの範囲に含まれるため増大しません。
A: 代入は距離更新と同じ回数だけ行われ、既に O(ク) と評価済みの範囲に含まれるため増大しません。
関連キーワード: ダイクストラ法, 最短経路, グラフ探索, 経路復元, 配列操作
設問3:〔最短経路の出力〕について答えよ。
(2)本文中の カ に入れる適切な字句を解答群の中から選び、記号で答えよ。
解答群
ア:viaNode に格納してあるノード番号を
イ:viaNode の要素番号を大きい方から
ウ:viaNode の要素番号を小さい方から
模範解答
カ:ア
解説
解答の論理構成
-
viaNode に保持する情報の確認
【問題文】では「直前に経由したノード番号を viaNode に格納する」と明記されています。すなわち、viaNode の各要素には「その要素番号のノードに到達する直前のノード番号」が入っています。 -
逆順出力ループの動き
図4のコードはj ← GOAL GOAL を出力 while (j が 1 より大きい) viaNode[j] を出力 j ← viaNode[j] endwhileとなっており、終点 GOAL から始めて viaNode[j] を取り出し、そのノード番号を次の探索対象 j に設定しています。これは「viaNode に格納してあるノード番号をたどる」ことで最短経路を後ろから前へ順次たどる処理です。 -
空所 [カ] に入る語句
【問題文】は「終点のノード番号を起点として カ などとすることで、最短経路のノード番号を逆順に出力する。」と記述しています。
・経路を逆順にたどる鍵は viaNode の内容(前ノード番号)である
・viaNode の“要素番号”の大小はループ制御に直接用いられていない
以上より「viaNode に格納してあるノード番号を」が文脈に最も合致します。 -
結論
よって [カ] には解答群「ア:viaNode に格納してあるノード番号を」が入ります。
誤りやすいポイント
- viaNode が「要素番号」なのか「格納値」なのかを取り違える
➔ 要素番号はノード番号と一致しますが、逆順トレースに用いるのは格納値(前ノード)です。 - “大きい方から/小さい方から”という選択肢に引きずられて、配列添字の順序を意識し過ぎる
➔ 添字順でなく、格納値をリンクリストのようにたどることが本質です。 - GOAL が常に最大番号とは限らない点を見落とす
➔ GOAL が 1 になるケースも考えられるため、添字の大小に依存しない実装が正解です。
FAQ
Q: viaNode は初期値 0 で良いのですか?
A: 経路確定前のノードは前駆ノードが存在しないため 0 で問題ありません。0 が出力されることはありません。
A: 経路確定前のノードは前駆ノードが存在しないため 0 で問題ありません。0 が出力されることはありません。
Q: なぜ while 条件が「j が 1 より大きい」なのですか?
A: 始点ノード番号が 1 と定義されているため、逆順トレースはノード番号 1 で終了します。
A: 始点ノード番号が 1 と定義されているため、逆順トレースはノード番号 1 で終了します。
Q: ゴールからスタートへ逆順に出力するメリットは?
A: 配列やスタック追加処理が不要で、前駆ノード情報だけで簡潔に経路を復元できる点です。
A: 配列やスタック追加処理が不要で、前駆ノード情報だけで簡潔に経路を復元できる点です。
関連キーワード: ダイクストラ法, 最短経路, 逆順トラバーサル, 前駆ノード, グラフ探索
設問4:〔計算量の考察〕について答えよ。
(1)本文中の キ に入れる適切な字句を、本文中の字句を用いて10字以内で答えよ。
模範解答
キ:更新起点ノード
解説
解答の論理構成
-
問題文は計算量を論じる際に、特定の処理を「キ を選ぶ」と表現しています。
-
ダイクストラ法の手順説明では、次の記述があります。「① 最短距離が確定していないノードの中で、始点ノード距離が最小のノードを 更新起点ノード として選び、…」
この“選び”という動作が、計算量評価で数える「キ を選ぶ」処理に相当します。 -
プログラム(図2)でも同じ操作が実装されています。行14〜18でif (done[k]が0 かつ イ)
minDist ← ウ
curNode ← kとして、curNodeがまさに「更新起点ノード」に決定されます。 -
したがって、計算量を左右する “選ぶ対象” は「更新起点ノード」であり、キ には「更新起点ノード」が適切です。
誤りやすいポイント
- “最短距離が確定するノード”と“更新起点ノード”を混同しやすいですが、後者は確定前に毎回探索で選ばれるノードの呼称です。
- プログラム上のcurNodeをただ「現在のノード」と読んでしまい、正式名称「更新起点ノード」と結び付けないまま解答してしまうケースがあります。
- 計算量の説明に引きずられ、「ノード」や「頂点数 N」など一般語で答えてしまうミスが散見されます。設問は“本文中の字句”を要求しています。
FAQ
Q: 更新起点ノードは毎回必ず新しく選び直されるのですか?
A: はい。最短距離が確定していないノードの中から始点ノード距離が最小のものを毎回選び直します。確定したノードはdone配列で除外されるため、同じノードが再び更新起点になることはありません。
A: はい。最短距離が確定していないノードの中から始点ノード距離が最小のものを毎回選び直します。確定したノードはdone配列で除外されるため、同じノードが再び更新起点になることはありません。
Q: 「curNode」と「更新起点ノード」は完全に同義ですか?
A: 問題文の用語「更新起点ノード」をプログラム上で表現した変数がcurNodeです。従って両者は同義と考えて差し支えありません。
A: 問題文の用語「更新起点ノード」をプログラム上で表現した変数がcurNodeです。従って両者は同義と考えて差し支えありません。
Q: 計算量 O(N²) を導く鍵はどの処理ですか?
A: for文で全ノードを調べて更新起点ノードを選ぶ部分と、選んだ後に隣接ノードを更新する部分の組合せが 回繰り返されるため となります。
A: for文で全ノードを調べて更新起点ノードを選ぶ部分と、選んだ後に隣接ノードを更新する部分の組合せが 回繰り返されるため となります。
関連キーワード: ダイクストラ法, 最短経路探索, 計算量解析, グラフアルゴリズム, ノード距離
設問4:〔計算量の考察〕について答えよ。
(2)本文中の ク に入れる適切な字句を答えよ。
模範解答
ク:
解説
解答の論理構成
-
問題文は次のように述べています。
・「次の キ を選ぶために始点ノード距離を計算する回数は最大でも N 回」
・「キ を選ぶ回数は、一度選ばれると当該ノードの最短距離は確定するので、最大でも N 回」
・「よって、最悪の場合の計算量は、O(ク) である。」 -
関数 distance のプログラムを確認すると、更新起点ノードを決める処理が
for (kを1からNまで1ずつ増やす)(行14)
により N 個のノード全てを走査しています。これが「始点ノード距離を計算する回数」に相当します。 -
while ループは、行12の while (true) から始まり、行29で閉じています。更新起点ノードが確定するたび、終点に到達するまで繰り返されます。ノードは一度確定すると再度選択されないので、ループ回数は高々 N 回です。
-
したがって、
・外側の while ループ:最大 N 回
・内側でのノード全走査:各回 N 回
であり、掛け合わせると 回の操作となります。 -
以上より、ク に入るのは
となります。
誤りやすいポイント
- 更新起点ノード確定後の「隣接ノードの距離更新」だけを見て計算量を O(N) と誤認するケースがあります。実際には毎回 for (kを1からNまで1ずつ増やす) による全ノード走査が行われるため二重計数が必要です。
- ダイクストラ法=ヒープ利用と早合点し、計算量を と書いてしまう例があります。本問の実装は単純配列走査でヒープを用いていません。
- 「最悪の場合」を「平均的な場合」と混同し、漠然と O(N) としてしまう失点が見受けられます。
FAQ
Q: ヒープ(優先度付きキュー)を使えば計算量は変わりますか?
A: はい。最小距離ノードの選択をヒープで行えば、選択操作は 、エッジ緩和は となり、全体で に改善できます。
A: はい。最小距離ノードの選択をヒープで行えば、選択操作は 、エッジ緩和は となり、全体で に改善できます。
Q: ここでいう「始点ノード距離を計算する回数」とは何を指しますか?
A: 行24〜28の距離更新用 for ループではなく、行14〜18で「更新起点ノード」を決めるために各ノードの dist[k] を評価する回数を指します。
A: 行24〜28の距離更新用 for ループではなく、行14〜18で「更新起点ノード」を決めるために各ノードの dist[k] を評価する回数を指します。
Q: 配列 done が 1 になったノードを再度走査しても良いですか?
A: 行15の if (done[k]が0 …) により既に確定したノードは無視されるため、再度選択されることはありません。
A: 行15の if (done[k]が0 …) により既に確定したノードは無視されるため、再度選択されることはありません。
関連キーワード: 計算量, ダイクストラ法, Big-O記法, 2重ループ, 配列走査


