令和7年度 春期 応用情報技術者試験 午後 問3 幅優先探索によるスライドパズルの解法

テクノロジアルゴリズム

この問題は2025(R7)春 応用情報技術者 午後に出題されたものです。出題時点の法令・制度に基づく内容のため、現行の内容と一致しない場合があります。

本ページの問題文・選択肢は、原本の体裁を Web 表示用に正規化しています(改行・記号・数式・図表参照の調整)。設問の趣旨および正解に影響する変更は加えていません。

学習ガイド

スライドパズルの最小手数を求めるプログラムを題材にした、幅優先探索のアルゴリズム問題です。盤面を配列で表現し、キューを使って探索を進め、訪問済み盤面をリストで管理するという実装の定石が一通り登場します。正答率はやや高めでしたが、探索の順序を誤解するとまとめて失点します。この記事では、小さな盤面で探索木を手で展開しながら、8つの空欄それぞれをキュー操作との対応で確認していきます。

この記事で押さえる論点

  • 幅優先探索の探索順序とキューの動きを説明する
  • 盤面の配列表現と状態の重複排除を理解する
  • プログラムの空欄を探索アルゴリズムの構造から埋める

問題本文

問3 スライドパズルを解くプログラムに関する次の記述を読んで,設問に答えよ。

表1のルールで定義されるスライドパズルについて考える。

表1 スライドパズルのルール
図の説明テキスト

表1 スライドパズルのルール

用語 説明
盤面 正方形の面に、1から順に1ずつ大きくなる数字の書かれた駒が配置されている。1か所だけ駒の置かれていない空白のマス(以下、空白マスという)がある。
開始時点の盤面 開始時点では、駒と空白マスはランダムに配置されている。
ゴールの盤面 盤面左上に数字が1の駒があり、右の駒の数字は1ずつ増えていき、その行の右端の駒に書かれた数字より1大きい数字が、次の行の左端の駒の数字となる。盤面右下のマスが空白マスとなる。
駒の移動 駒の上下左右のいずれかに空白マスがある場合、駒を空白マスに移動することができる。駒が移動した場合、駒が置かれていたマスが空白マスになる。駒を空白マスに移動させて盤面を変化させ、ゴールの盤面と同じにすることを目指す。

一辺が3マスのスライドパズルの例を図1に示す。

図1 一辺が3マスのスライドパズルの例
図の説明テキスト

図1 一辺が3マスのスライドパズルの例
左側に「開始時点の盤面」、中央に右向きの白抜き矢印、右側に「ゴールの盤面」が示されている。

開始時点の盤面(3×3のマス目):
左側に「1行目→」「2行目→」「3行目→」というラベルがある。
1行目: 8, 6, 7
2行目: 2, 5, 4
3行目: 3, (空白), 1
3行目の空白マスに対して、下から上向きの矢印で「空白マス」と指示されている。

ゴールの盤面(3×3のマス目):
1行目: 1, 2, 3
2行目: 4, 5, 6
3行目: 7, 8, (空白)

本問では,一辺のマスの個数が任意のスライドパズルにおいて,ゴールの盤面になるまでの駒の移動回数が最小となる移動方法(以下,最小解という)を一つ求めるプログラムを作成する。

〔一辺がNマスのスライドパズルの最小解を幅優先探索を用いて求める方法〕

幅優先探索を行ったときの,スライドパズルの盤面の遷移を,グラフで表現する。開始時点の盤面をルートノード,ある時点の盤面をノード,駒の移動に伴う盤面の遷移をエッジで表現する。また,ゴールの盤面をゴールノードとして定義する。

幅優先探索で最小解を求める方法を次のように考える。ここで,Nは2以上とする。

探索対象のノードに対して,移動できる駒ごとにその駒を移動した後のノードを作成して,探索対象のノードの子ノードとし,探索対象のノードは探索済みとなる。
このとき,子ノードが表す盤面が既に探索したノード(以下,探索済みノードという)と同じであれば,この子ノードは終端ノードとして,以降の探索は行わない。
また,子ノードがゴールノードと同じであれば,最小解が見つかったと判断して探索を終了する。

〔Nが3の場合の例〕

一辺が3マスのスライドパズルの最小解を求める過程の例を図2に示す。

図2 一辺が3マスのスライドパズルの最小解を求める過程の例
図の説明テキスト

一辺が3マスのスライドパズルの最小解を求める過程の例を示す木構造の図。ルートノードから状態が遷移し、1回目(ノード①〜③)、2回目(ノード④〜⑪)、…、n回目(ノード⑫:ゴールノード)へと展開される。各ノードは3x3マスの盤面における数字の配置を示している。凡例として「X:終端ノード」があり、図中のノード①、⑤、⑥、⑩などにはX印が付与されている。また「注記 網掛けの部分は表示していない。」とあり、ノード②やその子ノード(⑦〜⑨)は盤面が網掛けで伏せられている。最終的にn回目の遷移で[1,2,3], [4,5,6], [7,8,空き]のゴールノードに到達する様子が描かれている。

(1) 1回目の駒の移動では,ルートノードを探索対象のノードとする。ここでは,移動できる駒が三つあるので,ルートノードから深さが1となる①~③の子ノードを作成し,ルートノードは探索済みノードとなる。ここで,①~③の子ノードに対して,ゴールノードの判定及び終端ノードの判定を行う。
(2) 次に,作成した子ノードで終端ノード以外のノードをそれぞれ探索対象のノードとし,駒の移動にあわせて子ノードを作成する。図2では,①~③のノードから④~⑪の子ノードを作成し,①~③は探索済みノードとなる。ここで,④~⑪の子ノードに対して,ゴールノードの判定及び終端ノードの判定を行う。図2では,盤面が探索済みノードと一致する⑤,⑥及び⑩は終端ノードとなり,以降の探索は行わない。
(3) 以降,(2)で作成したノードの子ノードの作成と,ゴールノードの判定及び終端ノードの判定を繰り返す。図2の⑫は,n回目の移動でゴールノードに至ったことを示している。なお,全てのリーフノードが終端ノードと判定された場合,ゴールの盤面に至る駒の移動方法がないことを意味しているので,その旨のメッセージを出力して探索を終了する。

次に,配列を用いた盤面の表現方法を図3に示す。ここで,空白マスを表す値は最も大きい駒の数字である8に1を加えた9とする。なお,配列の要素番号は1から始まるものとする。

図3 配列を用いた盤面の表現方法
図の説明テキスト

盤面を配列で表現する方法について解説している。
枠内のテキスト:
盤面を次の例に示すように配列を用いて表現する。
(1) 盤面の行ごとに並ぶ駒の数字を整数型の配列で表す。
左側に3×3マスの盤面の模式図があり、上から1列目・2列目・3列目を示す矢印がある。各行から右に向かって矢印が伸び、それぞれ以下の配列に対応づけられている。
・1行目: [8, 6, 7] -> 1行目を表す配列 {8, 6, 7}
・2行目: [2, 5, 4] -> 2行目を表す配列 {2, 5, 4}
・3行目: [3, (空きマス), 1] -> 3行目を表す配列 {3, 9, 1} (※空きマスは9として扱う説明が直前の本文にある)
(2) (1)で行ごとに定義した配列を整数型配列の配列(以下、盤面配列という)で表す。
{{8, 6, 7}, {2, 5, 4}, {3, 9, 1}}
(3) 1行目の3列目の駒の数字は、盤面配列名をboardとするとboard[1][3]として参照する。

〔一辺がNマスのスライドパズルの最小解を求めるプログラム〕

一辺がNマスのスライドパズルにおいて,開始時点の盤面をランダムに作成し,最小解を求め,開始時点の盤面からの遷移及び駒の移動回数を出力するプログラムを作成する。開始時点からの盤面の遷移を保持する単方向連結リストの要素となるクラスBoardStateの説明を図4に,キューを実現するクラスQueueの説明を図5に,リストを実現するクラスListの説明を図6に,プログラムで使用する主な関数を表2に,最小解を求めるプログラムを図7に示す。

図4 クラス BoardState の説明
図の説明テキスト

メンバ変数の表:

メンバ変数 説明
board 整数型配列の配列 盤面配列。初期状態は未定義。
space 整数型の配列 空白マスの場所を示す行番号と列番号の二つの要素から成る配列。初期状態は未定義。
prev BoardState 駒が1回移動する前の BoardState のインスタンスへの参照。開始時点の盤面からの遷移を出力する際に用いる。初期状態は未定義。

コンストラクタの表:

コンストラクタ 説明
BoardState() インスタンスを初期化する。
BoardState(BoardState: st) インスタンスを初期化し,メンバ変数に次の処理を行う。
(1) メンバ変数 board に引数で渡された BoardState のインスタンスの board の値を格納する。
(2) メンバ変数 space に引数で渡された BoardState のインスタンスの space の値を格納する。
(3) メンバ変数 prev に引数で渡された BoardState のインスタンスへの参照 st を格納する。
図5 クラス Queue の説明
図の説明テキスト

コンストラクタの表:

コンストラクタ 説明
Queue() 可変長のキューを生成する。

メソッドの表:

メソッド 戻り値 説明
add(BoardState: st) なし キューに BoardState のインスタンスへの参照 st を追加する。
isEmpty() 論理型 キューが空の場合は true を返し,空でない場合は false を返す。
poll() BoardState 先頭の BoardState のインスタンスへの参照を取り出して返す。
peek() BoardState 先頭の BoardState のインスタンスへの参照を返す。
図6 クラス List の説明
図の説明テキスト

コンストラクタの表:

コンストラクタ 説明
List() 可変長のリストを生成する。

メソッドの表:

メソッド 戻り値 説明
add(整数型配列の配列: b) なし リストに盤面配列 b を追加する。
isEmpty() 論理型 リストが空の場合は true を返し,空でない場合は false を返す。
peek() 整数型配列の配列 リストの先頭に存在する盤面配列を返す。
表2 プログラムで使用する主な関数
図の説明テキスト
名称 戻り値 説明
createGoal(整数型: board_size) 整数型配列の配列 引数board_sizeの値を一辺のマス数としたスライドパズルのゴールの盤面を表す配列(以下,ゴールの盤面配列という)を作成して返す。
createStart(整数型: board_size) 整数型配列の配列 引数board_sizeの値を一辺のマス数としたスライドパズルの開始時点の盤面配列をランダムに作成し,出力して返す。なお,ゴールの盤面配列と同じものが作成されることはない。
getSpace(整数型配列の配列: b) 整数型の配列 盤面配列bを用いて,空白マスの場所を行番号と列番号から成る配列で返す。
checkGoal(整数型配列の配列: b, 整数型配列の配列: g) 論理型 第一引数の盤面配列bと第二引数のゴールの盤面配列gを用いて両者が一致しているかどうかを判定して結果を返す。一致する場合はtrueを返し,一致しない場合はfalseを返す。
printResult(BoardState: st) なし 引数として与えられたBoardStateのインスタンスへの参照stを用いて,開始時点の盤面からの遷移及び駒の移動回数を出力する。
checkSameBoard(整数型配列の配列: b, List: l) 論理型 引数で渡される探索済みの盤面配列のリストへの参照lを用いて,盤面配列bが探索済みの盤面配列のリストに存在するかどうかを判定して結果を返す。存在する場合はtrueを返し,存在しない場合はfalseを返す。
図7 最小解を求めるプログラム
図の説明テキスト

プログラムの変数宣言部のコード。
1: ○solveNPuzzle(整数型: board_size)
2: BoardState: start_state /* 開始時点の盤面を保持するBoardStateのインスタンスへの参照 /
3: 整数型配列の配列: goal_board /
ゴールの盤面配列 /
4: 整数型配列の配列: direction /
駒の移動に伴う空白マスの移動方向を行番号の増減を1番目の要素, 列番号の増減を2番目の要素で表現する配列 /
5: Queue: explore_queue /
探索対象の盤面を保持するキューのインスタンスへの参照 /
6: List: check_list /
探索済みの盤面配列を保持するリストのインスタンスへの参照 /
7: BoardState: state /
駒が移動する前の盤面を保持するBoardStateのインスタンスへの参照 /
8: BoardState: new_state /
駒が移動した後の盤面を保持するBoardStateのインスタンスへの参照 /
9: 整数型: change_num /
移動する駒の数字 /
10: 整数型: i /
forループ内で使用するカウンタ変数 */

図7 最小解を求めるプログラム(続き)
図の説明テキスト

行番号11から45までの擬似言語で記述されたプログラムのソースコード。状態の探索やキューの操作などを行うループと条件分岐が含まれており、コード内に , , , , , , , の空欄が設けられている。

設問と解答・解説

設問1

図2中の②の盤面を図3に倣って,配列で答えよ。

図2中の②の盤面を図3に倣って,配列で答えよ。

模範解答

{{8, 6, 7}, {2, 9, 4}, {3, 5, 1}}

採点基準(配点 4点)

知識・理解度(内容)(2点)

  • 2: 盤面の各行の要素が正しく抽出され、順番通りに記載されている。
  • 1: 要素の抽出に一部誤りがあるが、全体として盤面の状態を捉えようとしている。
  • 0: 盤面の状態が正しく反映されていない。

論理性(構造)(2点)

  • 2: 指定通り、中括弧を用いた正しい2次元配列の形式で表現されている。
  • 1: 配列の表現形式に軽微な構文上の誤り(括弧の抜けやカンマの欠落など)がある。
  • 0: 配列の形式として体をなしていない。

解説

正解の根拠

スライドパズルの盤面(図2中の②)を、図3に示された2次元配列の形式に則って記述する問題です。
盤面の各行はそれぞれひとつの配列として中括弧 {}\{\} で囲まれ、それらをさらに全体の中括弧で囲む必要があります。
図2中の②の盤面は上から順に以下のようになっています。

  1. 1行目: 8, 6, 7
  2. 2行目: 2, 9, 4
  3. 3行目: 3, 5, 1
    これらを指定の構文に従って記述すると、 {{8,6,7},{2,9,4},{3,5,1}}\{\{8, 6, 7\}, \{2, 9, 4\}, \{3, 5, 1\}\} となります。

高得点のポイント

  • 盤面の状態を各行ごとに正確に読み取っていること
  • 指定された2次元配列の記述規則(中括弧の対応やカンマ区切りの構文)に厳密に従い記述できていること

設問2

図7中の に入れる適切な字句を答えよ。

(1)

に入れる適切な字句を答えよ。

模範解答

explore_queue.isEmpty()

採点基準(配点 2点)

正確性(内容)(2点)

  • 2: 探索キューが空であるかどうかの判定式が完全に正しく記述されている。
  • 1: 対象のデータ構造やメソッド名に軽微なスペルミス等があるが、意図は明確に読み取れる。
  • 0: 誤った字句が記述されている、または無解答。

解説

正解の根拠

この空欄は、幅優先探索アルゴリズムにおけるメインループの継続(または終了)条件を決定する箇所です。
幅優先探索では、探索対象をキューに格納し、キューから要素を取り出しながら処理を進めます。探索が終了するのは、キューの中に探索すべき状態が存在しなくなったとき、すなわちキューが空になったときです。
プログラム中では、探索対象を格納するキューが explore_queue として定義されているため、キューが空であるかを判定するメソッドを用いた explore_queue.isEmpty() が入ります。

高得点のポイント

  • 幅優先探索アルゴリズムの終了条件(キューが空になること)を正しく理解していること
  • 利用する変数のスコープとオブジェクトのメソッド名を正確に記述できること

(2)

に入れる適切な字句を答えよ。

模範解答

poll()

採点基準(配点 2点)

正確性(内容)(2点)

  • 2: キューからの要素取り出しメソッドが完全に正しく記述されている。
  • 1: メソッド名に軽微な誤りがあるが、要素を取り出す意図は読み取れる。
  • 0: 誤った字句が記述されている、または無解答。

解説

正解の根拠

キューからの要素の取り出し操作に関するメソッドを問う箇所です。
幅優先探索の反復処理内では、まずキューの先頭から探索対象の状態を1つ取り出し、それを基に次の状態を展開していきます。
キュー(Queue)のインターフェースにおいて、先頭要素を取得しつつ削除する標準的な操作は poll() または dequeue() などで行われます(言語仕様に依存しますが、本題の文脈に従います)。ここでは poll() が正解となります。

高得点のポイント

  • キュー(Queue)の基本的なデータ構造とFIFO(先入れ先出し)の特性を理解していること
  • 要素を取り出すための適切なメソッドを正確に記述できること

(3)

に入れる適切な字句を答えよ。

模範解答

direction[i][1]

採点基準(配点 2点)

正確性(内容)(2点)

  • 2: 2次元配列による移動量の取得が完全に正しく記述されている。
  • 1: 配列名やインデックス指定に軽微な誤りがあるが、意図は読み取れる。
  • 0: 誤った字句が記述されている、または無解答。

解説

正解の根拠

空白マス(ゼロ)の移動処理において、移動量(変位)を取得するための配列参照箇所です。
移動方向および各方向に対する座標の増減は、2次元配列 direction に定義されています。ループ変数 i を用いて複数の方向を順に調べます。
インデックス 12 がそれぞれX方向やY方向の移動量を保持しているため、文脈に合わせて片方の移動量を取得する direction[i][1] が入ります。

高得点のポイント

  • プログラム内で定義された2次元配列の構造を正しく理解していること
  • ループ変数を用いた適切なインデックス指定(添字の記述)ができること

(4)

に入れる適切な字句を答えよ。

模範解答

board_size

採点基準(配点 2点)

正確性(内容)(2点)

  • 2: 上限チェックのための変数が完全に正しく記述されている。
  • 1: 変数名に軽微な誤りがあるが、盤面サイズを示す意図は読み取れる。
  • 0: 誤った字句が記述されている、または無解答。

解説

正解の根拠

空白マスが移動した先の座標が、盤面の範囲内に収まっているかを確認する境界チェック(バウンダリチェック)の条件式の一部です。
座標は 0 以上かつ盤面サイズ未満である必要があります。盤面サイズは変数 board_size によって管理されています。
したがって、移動後の座標値が超えてはならない上限値を示す board_size が入ります。

高得点のポイント

  • 配列外参照等のエラーを防ぐための境界チェックの重要性を理解していること
  • 条件式において上限判定に用いるべき変数を正確に特定できること

(5)

に入れる適切な字句を答えよ。

模範解答

direction[i][2]

採点基準(配点 2点)

正確性(内容)(2点)

  • 2: もう一方の移動量を取得する配列指定が完全に正しく記述されている。
  • 1: 配列名やインデックス指定に軽微な誤りがあるが、意図は読み取れる。
  • 0: 誤った字句が記述されている、または無解答。

解説

正解の根拠

ウと同様に、空白マスをもう一方の軸(X方向またはY方向)へ移動させる際の移動量を取得する記述です。
配列 direction において、一方の移動量がインデックス 1 にあるのに対し、もう一方はインデックス 2 に格納されています。
したがって、ループ変数 i と合わせて direction[i][2] が入ります。

高得点のポイント

  • 多次元配列を用いたデータ管理と座標表現の仕組みを理解していること
  • ウの記述と対になる適切な添字(インデックス)を正確に選択できること

(6)

に入れる適切な字句を答えよ。

模範解答

checkGoal(new_state.board, goal_board)

採点基準(配点 2点)

正確性(内容)(2点)

  • 2: ゴール判定関数の呼び出しと引数が完全に正しく記述されている。
  • 1: 関数名や引数に軽微な誤りがあるが、目標状態の比較を行う意図は読み取れる。
  • 0: 誤った字句が記述されている、または無解答。

解説

正解の根拠

新しく生成された盤面状態が、最終的な目標状態(ゴール)に到達したかどうかを判定する処理です。
目標到達をチェックするための関数 checkGoal が用意されており、この関数は現在の盤面と目標とする盤面を比較します。
現在検証している新しい盤面は new_state.board、目標とする盤面は goal_board という変数に格納されているため、これらを引数として渡す checkGoal(new_state.board, goal_board) が正解となります。

高得点のポイント

  • ゴール判定に必要なロジックと関数呼び出しの形式を正しく理解していること
  • 引数として渡すべき適切なオブジェクトとそのプロパティを正確に指定できること

(7)

に入れる適切な字句を答えよ。

模範解答

add(new_state)

採点基準(配点 2点)

正確性(内容)(2点)

  • 2: キューへの状態追加処理が完全に正しく記述されている。
  • 1: メソッド名や変数名に軽微な誤りがあるが、意図は読み取れる。
  • 0: 誤った字句が記述されている、または無解答。

解説

正解の根拠

新しく生成された状態が目標状態ではない場合、次以降の探索対象とするためにキューにその状態を追加する処理です。
探索待ちのキューに対する要素追加のメソッドは add() などが使用されます。
追加するべき要素は、新たに生成された状態を保持するオブジェクト new_state であるため、add(new_state) が正解となります。

高得点のポイント

  • キューへの要素追加により探索空間を展開していく幅優先探索の流れを理解していること
  • 探索キューで管理されるべきデータ型(状態オブジェクト全体)を正しく把握していること

(8)

に入れる適切な字句を答えよ。

模範解答

add(new_state.board)

採点基準(配点 2点)

正確性(内容)(2点)

  • 2: 訪問済みリストへの盤面追加処理が完全に正しく記述されている。
  • 1: 対象プロパティなどに軽微な誤りがあるが、盤面状態を記録する意図は読み取れる。
  • 0: 誤った字句が記述されている、または無解答。

解説

正解の根拠

探索の過程において、同じ状態を何度も探索してしまうことを防ぐ(重複探索の防止)ために、訪問済みの状態を記録する処理です。
すでに訪れた盤面状態を管理するリストやセットに対し、現在の盤面を追加します。
状態オブジェクト全体ではなく、盤面の配置そのものを表すプロパティを記録するため、add(new_state.board) のような記述が入ります。

高得点のポイント

  • 探索アルゴリズムにおける重複探索の防止(閉鎖リスト等への追加)の重要性を理解していること
  • 訪問済みとして記録すべき適切なオブジェクトのプロパティを正確に指定できること

設問2アは,正答率がやや低かった。幅優先探索では,探索対象をキューに格納し,そこから探索対象を取り出しながら処理を進めていく。その過程で新たな処理対象が見つかれば,それはキューに追加される。探索が終了するのは,探索対象が存在しない,すなわち,探索対象を格納するキューが空になったときであることを理解してほしい。設問2エは,正答率が低かった。変数directionは空白マスの移動方向,エを含むif文は移動後の空白マスが盤面内に収まっているかをチェックしている。プログラムで使用する変数の仕様を理解した上で,条件式を設定するよう心掛けてほしい。