応用情報技術者 2022年 秋期 午後 問03
迷路の探索処理に関する次の記述を読んで、設問に答えよ。
始点と終点を任意の場所に設定するn×mの2次元のマスの並びから成る迷路の解を求める問題を考える。本問の迷路では次の条件で解を見つける。
・迷路内には障害物のマスがあり、n×mのマスを囲む外壁のマスがある。障害物と外壁のマスを通ることはできない。
・任意のマスから、そのマスに隣接し、通ることのできるマスに移動できる。迷路の解とは、この移動の繰返しで始点から終点にたどり着くまでのマスの並びである。ただし、迷路の解では同じマスを2回以上通ることはできない。
・始点と終点は異なるマスに設定されている。
5×5の迷路の例を示す。解が一つの迷路の例を図1に、解が複数(四つ)ある迷路の例を図2に示す。
〔迷路の解を見つける探索〕
迷路の解を全て見つける探索の方法を次のように考える。
迷路と外壁の各マスの位置をx座標とy座標で表し、各マスについてそのマスに関する情報(以下、マス情報という)を考える。与えられた迷路に対して、障害物と外壁のマス情報にはNGフラグを、それ以外のマス情報にはOKフラグをそれぞれ設定する。マス情報全体を迷路図情報という。
探索する際の“移動”には、“進む”と“戻る”の二つの動作がある。“進む”は、現在いるマスから① y座標を1増やす、② x座標を1増やす、③ y座標を1減らす、④ x座標を1減らす、のいずれかの方向に動くことである。マスに“進む”と同時にそのマスのマス情報に足跡フラグを入れる。足跡フラグが入ったマスには“進む”ことはできない。“戻る”は、今いるマスから“進んで”きた一つ前のマスに動くことである。マスに“移動”したとき、移動先のマスを“訪問”したという。
探索は、始点のマスのマス情報に足跡フラグを入れ、始点のマスを“訪問”したマスとして、始点のマスから開始する。現在いるマスから次のマスに“進む”試みを①~④の順に行い、もし試みた方向のマスに“進む”ことができないならば、次の方向に“進む”ことを試みる。4方向いずれにも“進む”ことができないときには、現在いるマスのマス情報をOKフラグに戻し、一つ前のマスに“戻る”。これを終点に到達するまで繰り返す。終点に到達したとき、始点から終点まで“進む”ことでたどってきたマスの並びが迷路の解の一つとなる。
迷路の解を見つけた後も、他の解を見つけるために、終点から一つ前のマスに“戻り”、迷路の探索を続け、全ての探索を行ったら終了する。迷路を探索している間、それまでの経過をスタックに格納しておく。終点にたどり着いた時点でスタックの内容を順番にたどると、それが解の一つになる。
図1の迷路では、始点から始めて、(1,1)→(1,2)→(1,3)→(1,4)→(1,5)→(2,5)→(1,5)→(1,4)のように“移動”する。ここまででマスの“移動”は7回起きていて、このときスタックには経過を示す4個の座標が格納されている。さらに探索を続けて、始めから13回目の“移動”が終了した時点では、スタックにはア個の座標が格納されている。〔迷路の解を全て求めて表示するプログラム]
迷路の解を全て求めて表示するプログラムを考える。プログラム中で使用する主な変数、定数及び配列を表1に示す。配列の添字は全て0から始まり、要素の初期値は全て日とする。迷路を探索してマスを“移動”する関数visitのプログラムを図るに、メインプログラムを図4に示す。メインプログラム中の変数及び配列は大域変数とする。




〔解が複数ある迷路〕
図2は解が複数ある迷路の例で、一つ目の解が見つかった後に、他の解を見つけるために、迷路の探索を続ける。一つ目の解が見つかった後で、最初に実行される関数visitの引数の値はカである。この引数の座標を基点として二つ目の解が見つかるまでに、マスの“移動”はキ回起き、その間に座標が(4,2)のマスはク回“訪問”される。
設問1:〔迷路の解を見つける探索〕について答えよ。
(1)図1の例で終点に到達したときに、この探索で“訪問”されなかったマスの総数を、障害物と外壁のマスを除き答えよ。
模範解答
a:3
解説
解答の論理構成
-
迷路の内側は 5×5=25 マスです。
ただし図1には障害物として「(4,1)、(2,2)、(3,3)、(2,4)、(3,5)、(4,5)」の6マスが示されています。
よって “通過可能” なマスは
25 − 6 = 19 マス です。 -
深さ優先探索は【問題文】の順番
「① y座標を1増やす → ② x座標を1増やす → ③ y座標を1減らす → ④ x座標を1減らす」
で枝を伸ばし、行き止まりになれば一つ前へ“戻る”のを繰り返します。 -
図1について、実際にこの順で辿ると
(1,1) → (1,2) → (1,3) → (1,4) → (1,5) → (2,5) で行き止まり。
そこで (1,5) → (1,4) と戻り、 (1,3) → (2,3) で再び行き止まり。
さらに (1,1) まで戻って右側へ伸ばし
(2,1) → (3,1) → (3,2) → (4,2) → (4,3) → (4,4) → (5,4) → (5,5)
となり、ここで終点に到達します。 -
この時点で“足跡フラグ”が付いた(=訪問済み)マスは
15 個〔上に挙げた座標列の重複を除いた集合〕です。 -
よって “訪問されなかった” 通過可能マスは
19 − 15 = 3 個 となります。
誤りやすいポイント
- 障害物・外壁を引いた後の可動マス総数を数え忘れる。
- 深さ優先探索では「終点に着いた瞬間」はまだ他の枝を探索していない点を見落とし、訪問マスを過大評価してしまう。
- 途中で同じマスに戻った回数を「訪問回数」に重ねて数えてしまう。問題では “同じマスを2回以上通ることはできない” と明示されている。
FAQ
Q: 終点到達後に他の経路を探す際は訪問マス数が増えますか?
A: はい。終点に到達した後で再帰から戻る過程で未探索方向を調べるため、新たなマスを訪問する可能性があります。ただし今回の設問は「終点に到達した時点」で区切っています。
A: はい。終点に到達した後で再帰から戻る過程で未探索方向を調べるため、新たなマスを訪問する可能性があります。ただし今回の設問は「終点に到達した時点」で区切っています。
Q: “足跡フラグ”を OK に戻すのはどのタイミングですか?
A: visit 関数末尾の「エ ← OK」で、そのノード(マス)からすべての探索が終わった時に元に戻します。したがって再帰中に重複訪問は起きません。
A: visit 関数末尾の「エ ← OK」で、そのノード(マス)からすべての探索が終わった時に元に戻します。したがって再帰中に重複訪問は起きません。
Q: BFS(幅優先探索)でも同じ答えになりますか?
A: 結果として訪問総数は異なりますが、障害物と外壁を除く可動マス総数は変わらないため、「訪問されなかったマスの数」は BFS でも 3 個になります。
A: 結果として訪問総数は異なりますが、障害物と外壁を除く可動マス総数は変わらないため、「訪問されなかったマスの数」は BFS でも 3 個になります。
関連キーワード: 深さ優先探索、再帰呼出し、スタック、迷路探索
設問1:〔迷路の解を見つける探索〕について答えよ。
(2)本文中のアに入れる適切な数値を答えよ。
模範解答
ア:2
解説
解答の論理構成
-
問題文の前提
- 「(1,1)→(1,2)→(1,3)→(1,4)→(1,5)→(2,5)→(1,5)→(1,4)」の 7 回の“移動”後に「スタックには経過を示す4個の座標が格納されている」とある。
- スタックに入るのは visit が呼ばれた直後であり、子ノードの探索終了後に stack_top ← ウ で 1 段階戻る(= ポップ動作)ことが分かる。
-
7 回目の“移動”終了時点のスタック
- 残っている座標は (1,1)、(1,2)、(1,3)、(1,4) の4個。
-
8〜13 回目の“移動”を追う
8. (1,4) → (1,3) へ“戻る” → (1,4) をポップ(残り3個)
9. (1,3) → (2,3) へ“進む” → (2,3) をプッシュ(4個)
10. (2,3) → (1,3) へ“戻る” → (2,3) をポップ(3個)
11. (1,3) → (1,2) へ“戻る” → (1,3) をポップ(2個)
12. (1,2) → (1,1) へ“戻る” → (1,2) をポップ(1個)
13. (1,1) → (2,1) へ“進む” → (2,1) をプッシュ(2個) -
結論
13 回目の“移動”後にスタックに残る座標は (1,1) と (2,1) の 2 個。
よって ア は 2 となる。
誤りやすいポイント
- “移動”回数と visit 呼び出し回数を混同し、プッシュ/ポップのタイミングを取り違える。
- “戻る”動作でも“移動”回数が増えることを見落とし、8〜13 回目の流れを正しく数えられない。
- (1,2) から右方向 (2,2) へ進めると誤解(図1では障害物)。その結果スタック残数を多く見積もるミス。
FAQ
Q: “戻る”ときにも visit は呼ばれるのですか?
A: いいえ。“戻る”は呼び出しから戻る動作そのものです。visit は“進む”ときだけ新たに呼ばれます。
A: いいえ。“戻る”は呼び出しから戻る動作そのものです。visit は“進む”ときだけ新たに呼ばれます。
Q: スタックに最初から (1,1) が入っているのはなぜ?
A: 「始点のマスのマス情報に足跡フラグを入れ、…始点のマスから開始する」とある通り、visit(start_x, start_y) を最初に呼ぶので (1,1) がプッシュされます。
A: 「始点のマスのマス情報に足跡フラグを入れ、…始点のマスから開始する」とある通り、visit(start_x, start_y) を最初に呼ぶので (1,1) がプッシュされます。
Q: 13 回目終了時に (1,1) をポップしないのですか?
A: (1,1) はまだ探索中(右方向 (2,1) に進んだばかり)なので保持されます。全探索終了後にのみスタックが空になります。
A: (1,1) はまだ探索中(右方向 (2,1) に進んだばかり)なので保持されます。全探索終了後にのみスタックが空になります。
関連キーワード: 深さ優先探索、バックトラッキング、スタック、再帰呼び出し、探索アルゴリズム
設問2:
図3中のイ〜エに入れる適切な字句を答えよ。
模範解答
イ:paths[sol_num]k
ウ:stack_top - 1
エ:mazexy
解説
解答の論理構成
-
ゴールに到達したときの処理(イ)
【問題文】には「終点にたどり着いた時点でスタックの内容を順番にたどると、それが解の一つになる。」とあります。また図3ではfor (k を 0 から stack_top まで 1 ずつ増やす) イ ← stack_visitkとして、スタックに積まれた座標を“解を格納する配列”に写し取っています。解の番号は変数「sol_num」で管理しているので、行先は paths[sol_num]k しかありません。よって
イ:paths[sol_num]k -
再帰呼び出し後のスタックポインタ復元(ウ)
“進む”前に stack_top ← stack_top + 1 とインクリメントしているため、探索を終えて呼び出し元へ戻る直前で 1 つ減らして元の位置に戻す必要があります。したがって
ウ:stack_top - 1 -
足跡フラグの解除(エ)
アルゴリズムの説明では「4方向いずれにも“進む”ことができないときには、現在いるマスのマス情報をOKフラグに戻し、一つ前のマスに“戻る”。」と述べられています。図3最後の行エ ← OKで、この“OK フラグに戻す”を実装しています。変更すべき対象は現在座標のマス情報、すなわち mazexy です。よって
エ:mazexy
誤りやすいポイント
- イ で pathsk[sol_num] と添字を逆にしてしまう。第一添字が「解番号」、第二添字が「経路上の順番」と定義されています。
- ウ を stack_top のみで書き換え忘れ、再帰から戻るたびにポインタがずれ、スタックオーバーフローを招く。
- エ を VISITED に戻すと誤解し、いつまでも足跡が残ったままになり他の解が探索できなくなる。
FAQ
Q: stack_top をデクリメントせずに pop しない実装でも動く場合があるのでは?
A: 小さな迷路では偶然動くように見えることがありますが、再帰が深くなるとスタック領域に旧データが残り誤った経路を複写してしまいます。必ず stack_top - 1 で元に戻してください。
A: 小さな迷路では偶然動くように見えることがありますが、再帰が深くなるとスタック領域に旧データが残り誤った経路を複写してしまいます。必ず stack_top - 1 で元に戻してください。
Q: mazexy ← OK ではなく VISITED を解除する別フラグを持つ設計はどうですか?
A: もちろん可能ですが、本問の仕様では「OK フラグに戻す」と明示されているため、フラグを二値で管理する前提に合わせるのが正解です。
A: もちろん可能ですが、本問の仕様では「OK フラグに戻す」と明示されているため、フラグを二値で管理する前提に合わせるのが正解です。
Q: paths の確保サイズはどう決めれば良いですか?
A: 最悪ケースで「解数 × 最長経路長」を満たすようにします。制限が求められる場合は、動的配列やリストでの実装が一般的です。
A: 最悪ケースで「解数 × 最長経路長」を満たすようにします。制限が求められる場合は、動的配列やリストでの実装が一般的です。
関連キーワード: 深さ優先探索、スタック、バックトラック、再帰
設問3:
図4中のオに入れる適切な字句を答えよ。
模範解答
オ:sol_num
解説
解答の論理構成
- 変数の役割を確認
- 表1に「“sol_num” は『見つけた解の総数』」と明記されています。
- メイン処理の流れを追跡
- 図4では stack_top ← 0 に続き sol_num ← 0 で初期化し、 その後 visit(start_x, start_y) を呼び出しています。
- visit 関数での更新を確認
- 図3の visit 内の終点判定部で sol_num ← sol_num + 1 が行われ、 迷路の解を発見するたびに “sol_num” が増加します。
- if 条件の意図を読み取る
-
図4の該当行はif( オ が 0 と等しい) “迷路の解は見つからなかった” と印字するここで 0 かどうかを判定するのは「解が 1 つも見つからない場合」を検出するためです。
-
- 結論
- 解の総数を保持する “sol_num” こそが 0/非 0 を確認すべき変数なので、 オ に入る適切な字句は “sol_num” です。
誤りやすいポイント
- stack_top と混同する
- スタックが空かどうかを判定する箇所と誤認しやすいですが、 ここで求められているのは「解の個数」です。
- visit が 1 回以上呼ばれた=解があると早合点する
- visit は探索自体を行うため、呼び出し回数と解の有無は直結しません。
- “paths[][]” のサイズで判断しようとする
- 実際に格納されたかどうかは “sol_num” によって制御されており、 配列サイズはあらかじめ十分に確保されている前提です。
FAQ
Q: “sol_num” を 0 で初期化する必要はありますか?
A: はい。図4の最上部に sol_num ← 0 があり、探索前に必ず 0 にリセットしています。探索途中での加算は visit 関数の中で行われます。
A: はい。図4の最上部に sol_num ← 0 があり、探索前に必ず 0 にリセットしています。探索途中での加算は visit 関数の中で行われます。
Q: stack_top を使って解の個数を判定できないのですか?
A: できません。stack_top はあくまで現在の探索経路長(スタックの高さ)を示します。経路が見つからなくても探索は行われ、スタックは増減します。
A: できません。stack_top はあくまで現在の探索経路長(スタックの高さ)を示します。経路が見つからなくても探索は行われ、スタックは増減します。
Q: 途中で探索を打ち切った場合、“sol_num” は正確ですか?
A: いいえ。visit が全経路を走査し終える前にプログラムを終了すると、残っている潜在的な経路がカウントされないため不完全になります。
A: いいえ。visit が全経路を走査し終える前にプログラムを終了すると、残っている潜在的な経路がカウントされないため不完全になります。
関連キーワード: バックトラッキング、深さ優先探索、スタック、経路探索
設問4:〔解が複数ある迷路〕について答えよ。
(1)本文中のカに入れる適切な引数を答えよ。
模範解答
カ:5, 3
解説
解答の論理構成
-
終点へ到達した後の動き
【問題文】には「迷路の解を見つけた後も、他の解を見つけるために、終点から一つ前のマスに“戻り”、迷路の探索を続け、全ての探索を行ったら終了する。」とあります。したがって最初の解が見つかった直後、呼び出し元のマス(終点の一つ手前)へ制御が戻ります。 -
最初の解が通る経路を確認
方向優先順位は① y 座標を 1 増やす(北)→② x 座標を 1 増やす(東)→③ y 座標を 1 減らす(南)→④ x 座標を 1 減らす(西)です(【問題文】「“進む”は、現在いるマスから① … ④ …」)。
図2(解が複数ある迷路)でこの優先順位どおりに深さ優先探索を行うと、最初に到達する終点までの経路は
(1,1)→(1,2)→(1,3)→(2,3)→(3,3)→(3,4)→(4,4)→(5,4)→(5,5)
となります。よって終点「(5,5)」の一つ前は「(5,4)」です。 -
“戻った”後の次の分岐点
関数 visit の擬似コード(図3)の該当部分を引用します。if (maze<span class="choice-box">x</span>[y+1] が OK と等しい) visit(x, y+1) endif if (maze[x+1]<span class="choice-box">y</span> が OK と等しい) visit(x+1, y) endif if (maze<span class="choice-box">x</span>[y-1] が OK と等しい) visit(x, y-1) endif if (maze[x-1]<span class="choice-box">y</span> が OK と等しい) visit(x-1, y) endif(5,4) では
・北 (5,5):すでに訪問済み(VISITED)
・東 (6,4):外壁で NG
・南 (5,3):OK
・西 (4,4):VISITED
となるため、最初に満たされる条件は南方向で visit(5,3) が呼び出されます。 -
よって、最初に実行される visit の引数は「5, 3」と確定します。
誤りやすいポイント
- 最初の解を「北へ行けるだけ進んで右端で下る」と早合点し、(1,5)→(2,5)…を正解経路と考えてしまう。図2では (3,5)、(4,5) が障害物で行き止まりです。
- 方向優先順位を東→南→西→北などと勘違いし、呼び出される座標を誤る。
- 終点から“戻る”際に足跡フラグが OK に戻る点を見落とし、(5,3) も VISITED のままだと思い込む。
FAQ
Q: 終点に着いた後に足跡フラグを戻すのはどこで行われますか?
A: 図3の stack_top ← ウ の後で mazexy ← OK(エ)が実行されます。この処理により、探索再開時に同じマスへ再訪できるようになります。
A: 図3の stack_top ← ウ の後で mazexy ← OK(エ)が実行されます。この処理により、探索再開時に同じマスへ再訪できるようになります。
Q: 深さ優先探索だから必ず最短経路になりますか?
A: いいえ。深さ優先探索は経路を網羅的に列挙しますが、経路長を考慮しないため最初に見つかった解が最短とは限りません。
A: いいえ。深さ優先探索は経路を網羅的に列挙しますが、経路長を考慮しないため最初に見つかった解が最短とは限りません。
Q: 方向優先順位を変更すると解の列挙順は変わりますか?
A: 変わります。優先順位は分岐ごとの再帰呼び出し順を決めるため、探索木のたどり方が変わり、解が見つかる順序も異なります。
A: 変わります。優先順位は分岐ごとの再帰呼び出し順を決めるため、探索木のたどり方が変わり、解が見つかる順序も異なります。
関連キーワード: 深さ優先探索、バックトラック、再帰呼出し、スタック構造、経路探索
設問4:〔解が複数ある迷路〕について答えよ。
(2)本文中のキ、クに入れる適切な数値を答えよ。
模範解答
キ:22
ク:3
解説
解答の論理構成
- 深さ優先探索では、常に
「① y座標を1増やす→② x座標を1増やす→③ y座標を1減らす→④ x座標を1減らす」の順に“進む(visit を再帰呼出し)”ことが【問題文】に示されています。 - 一つ目の解が見つかった時点でスタックに残っている経路は
(1,1)→(1,2)→(1,3)→(2,3)→(3,3)→(3,4)→(4,4)→(5,4)→(5,5)
です(外れ枝であった(1,4)・(1,5)・(2,5) などは既に戻り済み)。 - 終点「(5,5)」から“戻る”と visit(5,4) に復帰し、
③方向の探索で最初に呼び出される引数が「(5,3)」です(ここが設問中のカ)。
ここを“基点”にして二つ目の解が見つかるまでの“移動”を以下に整理します。
(進=“進む”、戻=“戻る”。移動のたびに移動先を“訪問”します。)
- 表より、基点「(5,3)」以降に発生した“移動”は全部で「22」回。
- その間に座標「(4,2)」が“訪問”された回数は「3」回。
- よって
キ:22
ク:3
となります。
誤りやすいポイント
- “戻る”も【問題文】の「マスに“移動”したとき、移動先のマスを“訪問”した」という定義により 移動回数・訪問回数の両方に含まれる ことを忘れがちです。
- スタックから要素が抜けた時点でそのマスのフラグが「OK」に戻るため、同じマスを再度“進む”ことが可能になります。これを見落とすと訪問回数を 1 回少なく数えてしまいます。
- 方向選択の順序「①→②→③→④」を厳守しないと、経路の分岐点がずれ、移動総数も訪問回数も合いません。
FAQ
Q: “移動”のカウントに、探索開始時の始点への配置は含まれますか?
A: 含まれません。“移動”とは【問題文】で定義された“進む”または“戻る”を実際に行った回数です。
A: 含まれません。“移動”とは【問題文】で定義された“進む”または“戻る”を実際に行った回数です。
Q: 一度“戻る”したマスを再度“進む”ことはできますか?
A: はい。戻った直後にそのマスのフラグが「OK」に戻るので、再度“進む”ことが可能です。
A: はい。戻った直後にそのマスのフラグが「OK」に戻るので、再度“進む”ことが可能です。
Q: 同じマスを“進む”と“戻る”で連続して訪れた場合、訪問回数は 2 回ですか?
A: その通りです。“移動”したたびに訪問として数えるので、連続して訪れても 2 回になります。
A: その通りです。“移動”したたびに訪問として数えるので、連続して訪れても 2 回になります。
関連キーワード: 深さ優先探索、再帰呼出し、バックトラック、スタック、探索アルゴリズム


