応用情報技術者 2014年 春期 午後 問03
循環小数の循環節を検出するアルゴリズムに関する次の記述を読んで、設問1〜4に答えよ。
与えられた自然数 n について、1/n の小数表現を考える。1/n は、割り切れない場合、必ず循環小数となる。本問において、循環小数は、図1に示すように、繰り返し現れる数の並びである循環節の先頭から末尾までを括弧でくくる表記法で表す。
1 を n で割る割り算で現れる余りは、1 から n−1 までの値に限られる。循環小数となる場合は、割り算を小数第1 位から行っていくと、どこかでそれまでに現れた余りと同じ値の余りが現れる。以降の割り算で得られる小数は、同じ数の並びが繰り返されることとなり、その数の並びが循環節となる。循環節の桁数は最大 n−1 である。

1/56 の計算で商を1 桁ずつ求めていくと、余りの遷移は図2のようになる。ここでは、前のマスの中の値を10 倍して56 で割った余りが次のマスの中の値となる。余りとして48 が再度現れることから、1/56 では小数第4 位から第9 位までが循環節となることが分かる。

〔循環節を検出するアルゴリズム〕
単純に余りを配列に記録して、既出の余りであるかどうか比較することによって循環節を検出する方法では、割り算の各桁で現れる余りの種類の最大である n−1 種類の余りを記録する必要がある。その配列の大きさは n に比例するととらえ、処理できる n の値は使用可能な記憶域の大きさによって制約される。そこで記憶域の制約を受けないで、“ウサギとカメ” に例えられるフロイドの循環検出法のアルゴリズムを用いる。この検出法は、循環するデータの先頭と末尾の位置を効率良く検出できることが証明されている。
図2の余りを格納したマスから次のマスへ割り算を 1 桁進めることを、“ウサギとカメ” の歩みに例えて 1 歩進むという。
このアルゴリズムでは、まず、図3に示すように、カメは〈1〉、〈2〉、…と一度に 1 歩ずつ、ウサギは《1》、《2》、…と一度に 2 歩ずつ進む。両者は同時に出発し、進んだマス(〈1〉と《1》、〈2〉と《2》、…)の余りを比較しながら、余りが一致するところ(〈6〉と《6》)まで進む。
このように、循環小数となる全ての n について、ウサギは循環部分の何巡目かで周回遅れのカメに必ず追い付き、両者の余りは一致する。

両者の余りが一致したら、図4に示すように、カメは引き続き、ウサギは最初に戻って今度は両者とも 1 歩ずつ進む。最初に両者の余りが一致したところ〈〈9〉と〈9〉〉の次の割り算の商が、循環節の先頭である。

循環節の先頭を検出した後、図5に示すように、ウサギだけが再びカメと余りが一致するところ(〈9〉と〈15〉)まで1歩ずつ進むことによって、循環節の末尾を検出する。

このアルゴリズムを使って循環節の先頭と末尾の位置を求める関数 junkan を図6に示す。図6で使用する変数と関数は、表1のとおりである。


与えられた n に基づき、0 記法で表した場合、関数 junkan のプログラムが必要とする記憶域の大きさは オ とあり、計算量は カ となる。
設問1:
図6中のア~エに入れる適切な字句を答えよ。
模範解答
ア:mがpと等しい
イ:p ← 1
ウ:mがpと等しくない
エ:t ← s
解説
解答の論理構成
-
第1ループの終了条件(ア)
- 問題文は「両者は同時に出発し、進んだマス…の余りを比較しながら、余りが一致するところ…まで進む。」と述べています。
- したがってウサギ(p)とカメ(m)の余りが一致した時点で break すべきです。
- よって ア は「mがpと等しい」。
-
p を元に戻す操作(イ)
- 同じ段落で「ウサギは最初に戻って」とあるため、ウサギの余り p を開始時の余り 1 にリセットします。
- よって イ は「p ← 1」。
-
循環節先頭を探すループ条件(ウ)
- 復帰後は「今度は両者とも 1 歩ずつ進む。最初に両者の余りが一致したところ…が、循環節の先頭」と説明されています。
- 一致するまで繰り返すのでループ条件は「mがpと等しくない」。
- よって ウ は「mがpと等しくない」。
-
循環節末尾検出の初期化(エ)
- 末尾は「ウサギだけが再びカメと余りが一致するところ…まで 1 歩ずつ進む」ことで求めます。
- このとき t(末尾位置)は循環節の先頭位置 s から数え始める必要があります。
- よって エ は「t ← s」。
誤りやすいポイント
- 第1ループと第2ループの比較対象を混同し、ア と ウ を逆に記入してしまう。
- 「ウサギは最初に戻る」を「余りを 0 にする」と誤解し、イ に「p ← 0」としてしまう。
- 末尾検出で t を 0 から数え始め、エ を書き漏らして循環節長がずれる。
FAQ
Q: ループが終了しない場合はありますか?
A: ありません。問題文で「ウサギは循環部分の何巡目かで周回遅れのカメに必ず追い付き」と保証されています。
A: ありません。問題文で「ウサギは循環部分の何巡目かで周回遅れのカメに必ず追い付き」と保証されています。
Q: p を最初に戻すのはなぜ 1 なのですか?
A: m・p は「1 を n で割る」計算の余りを保持します。開始時の余りは 1 なので、「最初に戻る」は p ← 1 になります。
A: m・p は「1 を n で割る」計算の余りを保持します。開始時の余りは 1 なので、「最初に戻る」は p ← 1 になります。
Q: t ← s を省略するとどうなりますか?
A: t が 0 のまま末尾探索を始めるため、循環節の長さ(末尾位置)が誤って小さい値になります。
A: t が 0 のまま末尾探索を始めるため、循環節の長さ(末尾位置)が誤って小さい値になります。
関連キーワード: フロイドの循環検出法, 循環小数, 余り遷移, 2 ポインタ手法, 計算量解析
設問2:
n=88のとき、図6中のαとβはそれぞれ何回実行されるか答えよ。
模範解答
α:4
β:3
解説
解答の論理構成
-
外側の無限ループ
関数 junkan の先頭では
m ← amari(m * 10, n) ← α // カメが1歩進む
p ← amari(amari(p * 10, n) * 10, n) // ウサギが2歩進む
とあり、1回のループで α が 1 回実行 されます。ループは
if(ア) break
― すなわち “余りが一致したら” 抜けるまで続きます。 -
n=88 での余り遷移(初期値は m=1, p=1)
-
循環節先頭探索フェーズ
if(pが0と等しくない) に入ると、イ でカメを出発点へ戻す処理
m ← 1 が行われ、
while(ウ) (m≠p の間)の中で
m ← amari(m * 10, n) ← β
が 1 回ずつ実行されます。 -
n=88 での先頭探索
初期状態:m=1, p=56, s=1 -
以上より
α:4
β:3
誤りやすいポイント
- ウサギの更新は「二重に amari を呼ぶ」ため、1ループで2桁進むことを忘れやすい。
- ループ回数ではなく「α/β が呼ばれた回数」を問われている。break の直前にも α が走っている点に注意。
- 先頭探索フェーズでは m を 1 に戻してから比較を始める仕様を見落として、β の回数を 4 と誤答するケースが多い。
FAQ
Q: p が 0 になったらどうなりますか?
A: if(pが0と等しくない) が偽となり、循環節が存在しない(有限小数)のため先頭・末尾探索をスキップして関数を終了します。
A: if(pが0と等しくない) が偽となり、循環節が存在しない(有限小数)のため先頭・末尾探索をスキップして関数を終了します。
Q: 余りが一致するまでの最大ループ回数は?
A: 【問題文】に「循環節の桁数は最大 n−1 である」とあるので、α は最大で n−1 回実行されます。
A: 【問題文】に「循環節の桁数は最大 n−1 である」とあるので、α は最大で n−1 回実行されます。
Q: n を素数にした方が循環節は長いのですか?
A: 10 と互いに素な素数 n(例:7, 17 など)は長い循環節を持ちやすいですが、長さは n−1 以下であり必ずしも最大になるとは限りません。
A: 10 と互いに素な素数 n(例:7, 17 など)は長い循環節を持ちやすいですが、長さは n−1 以下であり必ずしも最大になるとは限りません。
関連キーワード: フロイド循環検出法, 余り計算, 循環小数, モジュラ計算
設問3:
1/nが割り切れるとき、関数junkanの戻り値はどのようになるか。15字以内で述べよ。
模範解答
s, tともに0である。
解説
解答の論理構成
- 【問題文】には「1 を n で割る割り算で現れる余りは、1 から n−1 までの値に限られる。」とあり、余りが「0」になるときは整数で割り切れる、すなわち循環節が存在しません。
- 関数 junkan の冒頭で m ← 1, p ← 1, s ← 0, t ← 0 と初期化されます。
- 本体の while ループは
p ← amari(amari(p * 10, n) * 10, n)
によりウサギの余りを更新し、[ア] で余りの一致を判定します。割り切れる場合、いずれ p が「0」になります。 - ループを抜けた直後に
if(pが0と等しくない)
が置かれており、余りが「0」のときは以降の イ 以後の処理(循環節の先頭・末尾探索)をスキップします。 - よって初期値のまま残る s = 0, t = 0 がそのまま戻り値となります。
- 以上より、1/n が割り切れる場合の戻り値は「s, t ともに 0 である」と結論づけられます。
誤りやすいポイント
- 余りがすでに一致した時点で循環と決めつけ、p = 0 になるケースを見落とす。
- 「割り切れたら循環節の長さ 0」という理解で t = s と答えてしまう。関数仕様では両方 0 に固定される。
- 初期値が 1/1 の余り計算と混同し、s = 1 と思い込む。実際には初期化で s ← 0 が行われ、その後更新されない。
FAQ
Q: 余りが 0 になると while ループはどうやって抜けるのですか?
A: [ア] の判定条件には m = p が使われるため、p が 0 になった瞬間に同じ回で m も 0 となり、一致して break します。
A: [ア] の判定条件には m = p が使われるため、p が 0 になった瞬間に同じ回で m も 0 となり、一致して break します。
Q: s が 0 のときは循環節がないと判断してよいのですか?
A: はい。関数仕様上、p = 0 ―すなわち循環節が存在しない場合― は s = 0, t = 0 が返される設計です。
A: はい。関数仕様上、p = 0 ―すなわち循環節が存在しない場合― は s = 0, t = 0 が返される設計です。
Q: 循環節が長い場合でもメモリーを食わないのはなぜ?
A: フロイドの循環検出法は配列を使わず、「ウサギ」と「カメ」の 2 つの余りのみを保持するため、必要記憶域は定数オーダーになります。
A: フロイドの循環検出法は配列を使わず、「ウサギ」と「カメ」の 2 つの余りのみを保持するため、必要記憶域は定数オーダーになります。
関連キーワード: フロイドの循環検出法, 余り計算, 循環小数, 計算量, ブレーク条件
設問4:
本文中のオ、カに入れる適切な字句を答えよ。
模範解答
オ:O(1)
カ:O(n)
解説
解答の論理構成
-
記憶域量の考察
- プログラム図6には変数として「n」「m」「p」「s」「t」の5つしか登場しません。
- 【問題文】では「与えられた n に基づき、0 記法で表した場合、関数 junkan のプログラムが必要とする記憶域の大きさは オ」とあります。
- 配列やリストなどサイズが n に比例する構造を一切使わず、固定個数のスカラー値だけで処理しているため、必要なメモリは入力サイズ n に依存せず一定です。
- よって オ には計算機科学で「定数時間(空間)」を表す O(1) が入ります。
-
計算量の考察
- 周期検出部(ウサギとカメで余りが一致するまで)
while ループは余りの総種類数である高々 n−1 回で必ず終了します(同じ余りが出現すると循環が確定するため)。 - 循環節の先頭検出部、末尾検出部も、それぞれ循環節長+非循環部長を1回ずつなぞるだけなので、総和は高々 n−1 歩です。
- したがって全体で実行する余り計算は入力値に比例し、「計算量は カ となる」という記述には O(n) が当てはまります。
- 周期検出部(ウサギとカメで余りが一致するまで)
-
まとめ
- 記憶域:固定個数の変数のみ → O(1)
- 計算量:余りの種類数(最大 n−1)だけ繰返し → O(n)
誤りやすいポイント
- 「amari(a,b) を2回呼ぶから2倍で O(2n)」と細かく数えてしまう
➔ 演算回数に定数倍を付けてもビッグオー表記では同じ O(n) です。 - 余りを毎回記録していると勘違いし、記憶域を O(n) としてしまう
➔ 本アルゴリズムはフロイド法なので記録は不要です。 - 循環節長だけを計算量として採り「最大でも n−1 だから O(n−1)」と書く
➔ 引き算も定数扱い、正式には O(n) とします。
FAQ
Q: 余りを比較するだけで本当にメモリは増えませんか?
A: はい。2つのポインタ(m,p)を更新して比較するだけなので、循環検出に過去の軌跡を保存する必要がありません。
A: はい。2つのポインタ(m,p)を更新して比較するだけなので、循環検出に過去の軌跡を保存する必要がありません。
Q: n が素数でも複合数でも計算量は変わらないのですか?
A: 変わりません。余りの種類数は常に最大で n−1 なので、最悪計算量はどちらも O(n) です。
A: 変わりません。余りの種類数は常に最大で n−1 なので、最悪計算量はどちらも O(n) です。
Q: もし配列表現で余りを保存したらどうなりますか?
A: その場合は余りを最大 n−1 個格納するため記憶域が O(n)、計算量も配列検索方法によっては悪化します。フロイド法はこの欠点を解消します。
A: その場合は余りを最大 n−1 個格納するため記憶域が O(n)、計算量も配列検索方法によっては悪化します。フロイド法はこの欠点を解消します。
関連キーワード: ビッグオー表記, フロイド循環検出法, 定数空間, 余り計算, ループ解析


