応用情報技術者 2021年 秋期 午後 問03
一筆書きに関する次の記述を読んで、設問1~4に答えよ。
グラフは、有限個の点の集合と、その中の2点を結ぶ辺の集合から成る数理モデルである。グラフの点と点の間をつなぐ辺の列のことを経路という。本問では、任意の2点間で、辺をたどることで互いに行き来することができる経路が存在する(以下、強連結という)有向グラフを扱う。強連結な有向グラフの例を図1に示す。辺は始点と終点の組で定義する。各辺には1から始まる番号が付けられている。
〔一筆書き〕
本問では、グラフの全ての辺を1回だけ通り、出発点から出て出発点に戻る閉じた経路をもつグラフを、一筆書きができるグラフとする。
〔一筆書きの経路の求め方〕
一筆書きの経路を求めるためには、出発点から辺の向きに従って辺を順番にたどり、出発点に戻る経路を見つける探索を行う。たどった経路(以下、探索済の経路という)について、グラフ全体で通過していない辺(以下、未探索の辺という)がない場合は、この経路が一筆書きの経路となる。未探索の辺が残っている場合は、探索済の経路を、未探索の辺が接続する点まで遡り、その点を出発点として、同じ点に戻る経路を見つけて、遡る前までの経路に連結することを繰り返す。
各点を始点とする辺を接続辺という。グラフの各点に対して接続辺の集合が決まり、辺の番号が一番小さい接続辺を最初の接続辺という。同じ始点をもつ接続辺の集合で、辺の番号を小さいものから順番に並べたときに、辺の番号が次に大きい接続辺を次の接続辺という。
図1のグラフの各点の接続辺の集合を表1に示す。図1において、点bの最初の接続続辺は辺2である。辺2の次の接続辺は辺5となる。辺5の次の接続辺はない。
一筆書きの経路の探索において、一つの点に複数の接続辺がある場合には、最初の接続辺から順にたどることにする。
図1のグラフで点aを出発点とした一筆書きの経路の求め方を図2に示す。
経路を構成する辺とその順番が、これ以上変わらない場合、確定済の経路という。

図2を参考にした一筆書きの経路を求める手順を次に示す。
〔一筆書きの経路を求める手順〕
点aから探索する場合は、点aの最初の接続辺である辺1から始め、辺1の終点bの最初の接続辺である辺2をたどり、同様に辺3、辺4をたどる。辺4の終点aからたどれる未探索の辺は存在しないので、これ以上探索が進められない(図2[1])。
しかし、未探索の辺5、辺6、辺7、辺8が残っているので、未探索の辺が接続する点まで遡る。
終点aから辺4を遡ると、辺4の始点dで未探索の辺7が接続している。遡った経路は途中で未探索の辺が存在しないので、これ以上、辺の順番が変わらず、辺4は、一筆書きの経路の一部として確定済の経路となる(図2[2])。
点dから同様に辺7→辺8→辺5→辺6と探索できるので、辺3までの経路と連結した新しい探索済の経路ができる(図2[3])。
辺6の終点dからは、辺6→辺5→辺8→辺7→辺3→辺2→辺1と出発点の点aまで遡り、これ以上、未探索の辺がないことが分かるので、全ての辺が確定済の経路になる(図2[4])。
一筆書きの経路は、次の(1)〜(4)の手順で求められる。
(1) 一筆書きの経路の出発点を決める。
(2) 出発点から、未探索の辺が存在する限り、その辺をたどり、たどった経路を探索済の経路に追加する。
(3) 探索済の経路を未探索の辺が接続する点又は一筆書きの経路の出発点まで遡る。遡った経路は、探索済の経路から確定済の経路にする。未探索の辺が接続する点がある場合は、それを新たな出発点として、(2)に戻って新たな経路を見つける。
(4) 全ての辺が確定済の経路になった時点で探索が完了して、その確定済の経路が一筆書きの経路になる。
〔一筆書きの経路を求めるプログラム〕
一筆書きの経路を求める関数directedEのプログラムを作成した。
実装に当たって、各点を点n(nは1~N)と記す。例えば、図1のグラフでは、点aは点1、点bは点2と記す。
グラフの探索のために、あらかじめ、グラフの点に対する最初の接続辺の配列edgefirst及び接続辺に対する次の接続辺の配列edgenextを用意しておく。edgenextにおいて、次の接続辺がない場合は、要素に0を格納する。
図1のグラフの場合の配列edgefirst、edgenextを図3に示す。

edgefirstによって点2の最初の接続辺が辺2であることが分かり、点2から最初にたどる接続辺は辺2となる。edgenextによって、辺2の次の接続辺が辺5であることが分かるので、点2から次にたどる接続辺は辺5となる。辺5の次の接続辺はないので、点2からたどる接続辺はこれ以上ないことが分かる。
プログラム中で使用する定数と配列を表2に、作成した関数 directedE のプログラムを図4に示す。
全ての配列の添字は1から始まる。


設問1:
図4中のア〜エに入れる適切な字句を答えよ。
模範解答
a:0
b:edgenext[temp]
c:top - 1
d:start[temp]
解説
解答の論理構成
-
if (currentx が ア でない) の判定
- 表2に「currentnには、点nを始点とする未探索の辺がない場合は0を格納する。」とあります。
- よって「未探索の辺があるか」を判定する値は 0 です。
⇒ ア = 0
-
currentx ← イ の更新
- 未探索だった辺 temp を取り出した直後は、その次の接続辺を current にセットする必要があります。
- 図1の説明には「edgenextによって、辺2の次の接続辺が辺5であることが分かる」とあります。
- したがって currentx には edgenext[temp] を代入します。
⇒ イ = edgenext[temp]
-
backtrack 部分 top ← ウ
- 探索済スタック searched は top で末尾位置(次に書き込む場所)を示します。
- 一つ戻るには「1 減算」します。
⇒ ウ = top - 1
-
x ← エ の更新
- 遡った辺 temp の始点へ戻るので、表2の startm を参照します。
- 具体例として図2[2]では「辺4を遡ると、辺4の始点d…」という処理が行われています。
⇒ エ = start[temp]
結果
- ア 0
- イ edgenext[temp]
- ウ top - 1
- エ start[temp]
誤りやすいポイント
- 「currentx==0 は“接続辺が無い”」という定義を見落とし、NULL や負数を入れてしまう。
- top の意味を「現在の格納済み位置」ではなく「最後に格納した位置」と誤解し、top ← top - 1 を忘れる。
- 遡り時に終点 end[temp] を入れてしまい、無限ループに陥る。
- edgenext と edgefirst を取り違えて、次の接続辺の更新が壊れる。
FAQ
Q: last を M で初期化しているのはなぜですか?
A: 表2で「M はグラフの辺の個数」と明示されており、path 配列を後ろから埋めるために最大インデックス M から開始します。
A: 表2で「M はグラフの辺の個数」と明示されており、path 配列を後ろから埋めるために最大インデックス M から開始します。
Q: searched と path の2段階管理をする利点は?
A: searched で一時的に経路を積み、遡りが確定した部分だけを path に移すことで、「確定済み」と「まだ変動する可能性のある」辺を明確に分離できます。
A: searched で一時的に経路を積み、遡りが確定した部分だけを path に移すことで、「確定済み」と「まだ変動する可能性のある」辺を明確に分離できます。
Q: 強連結でないグラフに適用するとどうなりますか?
A: 出発点から到達できない辺が残るため current[...] が 0 になっても last が 1 以上のままとなり、探索が完了しません。
A: 出発点から到達できない辺が残るため current[...] が 0 になっても last が 1 以上のままとなり、探索が完了しません。
関連キーワード: オイラー路、スタック操作、有向グラフ探索、深さ優先探索、配列シミュレーション
設問2:
図1のグラフで関数directedEを動作させたとき、while文中のif文は、何回実行されるか、数値で答えよ。
模範解答
16
解説
解答の論理構成
-
ループ条件の確認
- プログラムでは last ← M と初期化され、while (①last が 1 以上) で繰り返します。
- M は【表2】で「グラフの辺の個数」と定義されています。図1のグラフでは「辺1」〜「辺8」が列挙されているので M = 8 です。
-
last が減るタイミング
- else 句に last ← last − 1 があり、if 句側には last を変更する文がありません。
- よって last を減らすのは else が実行されたときだけです。
- ループ終了条件は last < 1 になるまでなので、else はちょうど M 回、すなわち 8 回 実行されます。
-
ループ回数の算出
- else が 8 回実行されるあいだ、if も含めて毎回判定が行われるため、総ループ回数は
8(if 側で探索に進む回数) + 8(else 側で遡る回数) = 16 回です。 - したがって if 文は 16 回評価 され、「何回実行されるか」という設問の答えは 16 になります。
- else が 8 回実行されるあいだ、if も含めて毎回判定が行われるため、総ループ回数は
誤りやすいポイント
- top を増減させている行に目を取られ、last だけを減らす場所を見落とす。
- if 側で複数の辺を一気に処理すると思い込み、探索回数と if 評価回数を混同する。
- while を抜けた後にもう一度 if が評価されると誤解し、+1 してしまう。
FAQ
Q: currentx が 0 になる場面は何回ありますか?
A: 辺をすべて使い切った点でだけ発生します。そのたびに else が呼ばれ、合計で 8 回です。
A: 辺をすべて使い切った点でだけ発生します。そのたびに else が呼ばれ、合計で 8 回です。
Q: 図1以外のグラフでも if 回数は常に 2M ですか?
A: はい。本プログラムは辺を「進む」「戻る」の 2 段階で必ず 1 辺につき 2 ループ消費するので、グラフが一筆書き可能なら 2M 回になります。
A: はい。本プログラムは辺を「進む」「戻る」の 2 段階で必ず 1 辺につき 2 ループ消費するので、グラフが一筆書き可能なら 2M 回になります。
Q: while を last > 0 に書き換えても結果に影響はありますか?
A: ありません。last が 0 になった時点でループを抜ける仕様は同じなので、if の実行回数は変わりません。
A: ありません。last が 0 になった時点でループを抜ける仕様は同じなので、if の実行回数は変わりません。
関連キーワード: オイラー路、スタック操作、深さ優先探索、有向グラフ、再帰的バックトラック
設問3:
一筆書きができない強連結な有向グラフで関数directedEを動作させたとき、探索はどのようになるかを、解答群の中から選び、記号で答えよ。
解答群
ア:探索が完了するが、配列pathに格納された経路は一筆書きの経路にならない。
イ:探索が完了せずに終了して、配列pathに格納された経路は一筆書きの経路にならない。
ウ:探索が無限ループに陥り、探索が終了しない。
模範解答
ア
解説
解答の論理構成
- ループの終了条件を確認
プログラムでは「while (①last が 1 以上)」と明記されています。さらに、初期化時に「last ← M」とし、else 節に入るたびに「last ← last − 1」で必ず 1 つ減らします。したがって 辺を確定済にするたびに last は 1 減り、last が 0 になれば必ず while ループは終了します。 - last が 0 になるまでに何回減るか
if 節では探索済スタックに push するだけで last は減りません。else 節で pop したときだけ減ります。push された総数はちょうどグラフの辺の個数 M、pop される回数も M なので、last は必ず 0 になります。 - グラフが一筆書き不可でも終了できる理由
アルゴリズムは【問題文】の「(2) 出発点から、未探索の辺が存在する限り…」「(3) 探索済の経路を未探索の辺が接続する点…」を忠実に模倣していますが、“閉路になる” 条件はコード中に組み込まれていません。入次数=出次数という Euler 条件を満たさない有向グラフでも、 ・未探索の辺があれば必ず if 節で push
・未探索の辺が尽きれば else 節で pop
を繰り返すだけなのでループ自体は正常終了します。 - path に格納される経路の性質
一筆書きが可能になる必要十分条件は各点で「入次数=出次数」です。しかし設問では「一筆書きができない強連結な有向グラフ」と明言されています。よって 完成した path はすべての辺を含むものの、出発点に戻らない(途中で不整合が起こる)ので一筆書きの経路にはなりません。 - 解答選択
以上より「探索が完了」かつ「path は一筆書きにならない」状況なので、解答群の
ア:探索が完了するが、配列pathに格納された経路は一筆書きの経路にならない。
が該当します。
誤りやすいポイント
- last の役割を「確定済辺の個数」ではなく「格納位置」と取り違えると、無限ループと勘違いしやすいです。
- Euler 条件を満たさないと探索アルゴリズム自体が停止しないと思い込みがちですが、directedE は「辺数が有限」である限り必ず last を 0 にできます。
- top ← top - 1 の直後に searched[top] を参照するので、「スタックが空になった瞬間に underflow する」と誤解しやすいですが、push 回数=pop 回数なので underflow にはなりません。
FAQ
Q: 一筆書き不可のとき path にはどんな並びが入るのですか?
A: すべての辺が 1 回ずつ格納されますが、始点と終点が一致しない単なる列です。
A: すべての辺が 1 回ずつ格納されますが、始点と終点が一致しない単なる列です。
Q:探索が終わったあとに一筆書きかどうか判定する方法は?
A: 各点で「入次数=出次数」を事前に確認するか、path を走査して開始点と終了点が一致しているかをチェックします。
A: 各点で「入次数=出次数」を事前に確認するか、path を走査して開始点と終了点が一致しているかをチェックします。
Q: x ← start[temp] で戻る理由は?
A: else 節では経路を遡っているので、確定済とした辺 temp の始点に戻り、そこからさらに遡るためです。
A: else 節では経路を遡っているので、確定済とした辺 temp の始点に戻り、そこからさらに遡るためです。
関連キーワード: Euler回路、Hierholzer法、入次数、出次数、スタック操作
設問4:
図4のプログラムは、配列searchedを配列pathに置き換えることで、使用する領域を減らすことができる。このとき、無駄な繰返しが発生しないように、下線①の繰返し条件を、変数topとlastを用いて変更せよ。
模範解答
topがlast以下
解説
解答の論理構成
- ループ開始時の変数の役割を整理します。問題文には
- 「top ← 1」「last ← M」
とあり、top は配列searched(探索済の経路)の「次に格納する位置」、last は配列path(確定済の経路)の「次に格納する位置」を示しています。
- 「top ← 1」「last ← M」
- 探索が進むにつれて
- 探索済の辺を追加すると top ← top + 1
- 遡って確定すると last ← last − 1
が実行され、top はインクリメント、last はデクリメントされます。
- すべての辺を確定し終える瞬間は「探索済に入れる位置 top が、確定済に入れる位置 last を追い越した」瞬間です。これ以上 while 文を回しても何も処理できません。
- したがって、下線①の条件
「while (last が 1 以上)」
を、処理が残っている状態──すなわち「top が last 以下」のあいだだけ繰り返す条件に置き換えれば、無駄なループは起きません。 - よって模範解答は
topがlast以下
誤りやすいポイント
- ①を「top <= M」など配列長と比較してしまう。探索の進行度を示すのは last であって M ではありません。
- top と last が「インデックス」ではなく「要素数」だと誤解する。実際には「次に格納する位置」です。
- 「探索済が空になったら終了」と考えて top == 1 を判定に用いるミス。確定済へ移すたびに探索済の末尾要素は削除されるため、top が 1 でも探索が終わっているとは限りません。
FAQ
Q: top と last が同じになった時点でループを抜けても、最後の要素は確定済に移せますか?
A: はい。top が last 以下でループに入れば、内部の else 節で必ず 1 つ確定済に移されるため、問題なく処理が完了します。
A: はい。top が last 以下でループに入れば、内部の else 節で必ず 1 つ確定済に移されるため、問題なく処理が完了します。
Q: グラフに辺が 0 本の場合でもこの条件で大丈夫ですか?
A: M が 0 なら初期値で top = 1、last = 0 になるので、top ≤ last が偽となりループに入らず正常終了します。
A: M が 0 なら初期値で top = 1、last = 0 になるので、top ≤ last が偽となりループに入らず正常終了します。
Q: 再帰ではなくスタック(searched)を使う理由は?
A: 有向グラフの一筆書き経路探索は深さ優先探索の一種ですが、配列をスタック的に扱うことで再帰呼び出しによるスタックオーバーフローを避け、実装を簡潔にできます。
A: 有向グラフの一筆書き経路探索は深さ優先探索の一種ですが、配列をスタック的に扱うことで再帰呼び出しによるスタックオーバーフローを避け、実装を簡潔にできます。
関連キーワード: グラフ理論、オイラー路、スタック、ループ制御、インデックス操作


