戦国IT - 情報処理技術者試験の過去問対策サイト
ブログお知らせお問い合わせ料金プラン

応用情報技術者 2014年 秋期 午後03


マージソートに関する次の記述を読んで、設問1~3に答えよ。

 マージソートは、整列(ソート)したいデータ(要素)列を、細かく分割した後に、併合(マージ)を繰り返して全体を整列する方法である。  ここでは、それぞれの要素数が1になるまでデータ列の分割を繰り返し、分割されたデータ列を昇順に並ぶように併合していくアルゴリズムを考える。例として、要素数が8の場合のアルゴリズムの流れを図1に示す。
応用情報技術者試験(平成26年度 秋期 午後 問03 図01)
 再帰呼出しを使って記述したマージソートのアルゴリズムを図2に示す。
応用情報技術者試験(平成26年度 秋期 午後 問03 図02)
 図2のアルゴリズムを連結リストに対して実行するプログラムを考える。ここでは、整列対象のデータとして正の整数を考える。連結リストは、複数のセルによって構成される。セルは、正の整数値を示すメンバvalueと、次のセルへのポインタを示すメンバnextによって構成される。連結リストの最後のセルのnextの値は、NULLである。連結リストのデータ構造を図3に示す。
応用情報技術者試験(平成26年度 秋期 午後 問03 図03)
〔連結リストの分割〕  図2中の(2)の処理を行う関数divideを考える。関数divideは、連結リストの先頭へのポインタ変数listを引数とし、分割後の後半の連結リストの先頭へのポインタを戻り値とする。連結リストの分割前後のイメージを図4に示す。
応用情報技術者試験(平成26年度 秋期 午後 問03 図04)
 連結リストをセルの個数がほぼ同じになるように分割するために、ポインタ変数を二つ用意し、一方が一つ進むごとに、他方を二つずつ進める。後者のポインタが連結リストの終わりに達するまでこの処理を繰り返すと、前者のポインタは連結リストのほぼ中央のセルを指す。この方法を利用した関数divideのプログラムを図5に示す。  以下、連結リストのセルを指すポインタ変数をaとするとき、aが指すセルのメンバvalueをa->valueと表記する。
応用情報技術者試験(平成26年度 秋期 午後 問03 図05)
〔連結リストの併合〕  図2中の(4)の処理を行う関数mergeを考える。関数mergeは、二つの連結リストの先頭へのポインタ変数aとbを引数とし、併合後の連結リストの先頭へのポインタを戻り値とする。併合処理を行う際には、ダミーのセルを用意し(そのセルへのポインタをheadとする)、この後ろに併合後の連結リストを構成する。aとbが指すセルの値を比較しながら、値が小さい順に並ぶよう処理を進める。連結リストの併合の流れを図6(処理は、①、②、③、…と続く)に、関数mergeのプログラムを図7に示す。
応用情報技術者試験(平成26年度 秋期 午後 問03 図06)
応用情報技術者試験(平成26年度 秋期 午後 問03 図07)

設問1〔連結リストの分割〕について、(1)〜(3)に答えよ。

(1)図5中のに入れる適切な字句を答えよ。

模範解答

ア:bがNULLと等しくない イ:b ← b->next ウ:a->next

解説

解答の論理構成

  1. ループ条件
    • 【問題文】には「後者のポインタが連結リストの終わりに達するまでこの処理を繰り返す」とあります。
    • divide では“後者”が b なので、“終わり”とは b が NULL になる瞬間です。
    • よって while 文は b がまだ NULL でない間だけ繰り返します。
    • すなわち = bがNULLと等しくない となります。
  2. ループ内の 2 ステップ移動
    • a は毎回1つ進めるために a ← a->next が置かれています。
    • b を2つ進めるためには、①ループ冒頭で b ← b->next、②その直後の if 文でさらに1回進める処理が必要です。
    • if 文のガードは既に「b が NULL と等しくない」ですから、内部で行う追加1ステップが です。
    • よって = b ← b->next になります。
  3. リストの切断位置
    • ループ終了時点で a は分割点(前半の最後のセル)を指しています。
    • p ← a->next で後半先頭を保存したあと、分割のために a->next を NULL に書き換えます。
    • したがって = a->next です。
以上より
ア:bがNULLと等しくない
イ:b ← b->next
ウ:a->next

誤りやすいポイント

  • b と b->next を混同する
    while ( b->next が NULL ではない ) とすると奇数要素リストで分割位置が一つ後ろにずれます。
  • ループ内で b を2回進め忘れる
    を入れ忘れると a と b の距離が1セルに縮まり、ほぼ中央になりません。
  • 切断前に p を退避しない
    先に a->next ← NULL を実行すると後半先頭が失われ、戻り値が不正になります。

FAQ

Q: ループ条件に b != NULL と b->next != NULL のどちらを使うか迷います。
A: divide の目的は「b が終端に到達した瞬間にループを抜ける」ことです。b が一足先に終端へ到達するため、条件は「b が NULL と等しくない」になります。
Q: 奇数個の要素の場合、前半と後半の要素数はどうなりますか。
A: a は中央のセルを指した状態で分割するので、前半の方が1要素多い結果になります。
Q: a->next ← NULL を行わずに戻り値だけ返してはいけませんか。
A: 併合時に前半と後半を独立に再帰呼び出しするため、リストを物理的に切断しておかないと循環参照のような状態になり、無限ループや重複処理を招きます。

関連キーワード: マージソート、連結リスト、ポインタ、再帰、分割と併合

設問1〔連結リストの分割〕について、(1)〜(3)に答えよ。

(2)図3の連結リストに対して関数divideを実行し、プログラムが図5中のαの部分に達したとき、ポインタ変数aは、図3中のどのセルを指しているか。指しているセルの値(valueの数値)を答えよ。

模範解答

8

解説

解答の論理構成

  1. 初期配置を確認
    • プログラム冒頭でa ← list、b ← a->nextなので、図3の先頭セル"6"を指すaと、その直後のセル"4"を指すbが用意されます。
    • 直後のif ( b が NULL と等しくない ) b ← b->nextにより、bはさらに一つ進み、セル"3"を指します。
  2. ループ条件を把握
    • while 行には「連結リストの終わりまで繰り返す」とあるため、は「b が NULL と等しくない」すなわち b が末尾を過ぎていない ことを意味します。
    • ループ本体では
      a ← a->next b ← b->next if ( b が NULL と等しくない ) b ← b->next
      と記述されており、aは1セル、bは合計2セルずつ前進します。
  3. 実際の前進を追跡
    連結リストの値列は図3より
    "6" → "4" → "3" → "8" → "7" → "2" → "1" → "5" です。
    ループ回数a が指す値b が指す値ループ継続判定
    開始前"6""3"b ≠ NULL
    1 回目終了"4""7"b ≠ NULL
    2 回目終了"3""1"b ≠ NULL
    3 回目終了"8"NULLb = NULL → ループ終了
  4. α到達時の状態
    • while ループを抜けた直後(α位置)でaはセル"8"を指しています。
    • この時点でa->next(セル"7")を切り離すことで、前半リストが"6" "4" "3" "8"、後半リストが"7" "2" "1" "5"に分割されるしくみです。
したがって、設問の答えは 8 となります。

誤りやすいポイント

  • bの二重前進を見落として、aを1セル遅く数えてしまう。
  • while 条件を「b->next が NULL」と誤解し、ループを1回余分または不足させる。
  • 図3の値列を途中で読み違え、"7"と"8"の位置を取り違える。

FAQ

Q: なぜbを2セルずつ動かすのですか?
A: 「後者のポインタが連結リストの終わりに達するまでこの処理を繰り返す」とあるとおり、bを速く進めることで、aがほぼ中央に到達した時点でbは末尾付近になり、均等分割ができます。
Q: 要素数が奇数でも同じ方法で中央を取れますか?
A: はい。偶奇にかかわらず、bが2セル進むたびにaが1セル進むため、aは常に前半と後半の境界付近を指します。奇数の場合は前半が1要素多い状態で分割されます。
Q: α以降でa->next ← NULLとするのはなぜでしょう?
A: p ← a->nextで後半リストの先頭を保存した後、a->next ← NULLで前半と後半を物理的に切り離し、2本の独立した連結リストを生成するためです。

関連キーワード: マージソート、連結リスト、ポインタ操作、二分分割、走査アルゴリズム

設問1〔連結リストの分割〕について、(1)〜(3)に答えよ。

(3)奇数2N+1個のセルから成る連結リストを関数divideで分割すると、前半と後半の連結リストのセルの個数はそれぞれ幾つになるか式で答えよ。

模範解答

前半:N+1 後半:N

解説

解答の論理構成

  1. 連結リストを二つのポインタで走査
    【問題文】では、分割の方法を
    「一方が一つ進むごとに、他方を二つずつ進める」
    と説明しています。一つずつ進むポインタを slow、二つずつ進むポインタを fast と呼びます。
  2. fast が末尾に到達した時点の slow の位置
    fast は slow の 2 倍の速さで進むため、fast が連結リスト全体(要素数 )を走破するとき、slow はその半分だけ進みます。
    • fast の移動回数 …
    • slow の移動回数 …
      よって slow は先頭から 個進んだセル(0 始まりならインデックス )を指します。
  3. divide が切り離す位置
    図5のプログラムで
    plaintext p ← a->next ← NULL
    とあるとおり、slow(変数 a)の直後を NULL で切り離します。つまり slow を含む側が前半、p を先頭とする側が後半になります。
  4. 各部分のセル数
    • 先頭から slow まで … 個(slow を含む)
    • 残り …
      したがって
      前半: 後半:
      が求まります。

誤りやすいポイント

  • 「前半と後半をほぼ同じ個数にする」=完全に同数と誤解し、 ではなく両方 と考えてしまう。
  • slow が中央セルを指すとき「中央セルはどちらに属するか」を考慮せず、前半・後半を の逆に書いて失点する。
  • 図5の切り離し位置を見落とし、「slow の位置」ではなく「slow の一つ前」を NULL にする実装と混同する。

FAQ

Q: なぜ中央セルを前半に入れる実装なのですか?
A: 図5で slow(a)の「次」を NULL にして分割しているため、slow 自身は前半側に残る仕様です。よって奇数個の場合、中央セルは前半に入ります。
Q: 先に fast を 2 ステップ進めている理由は?
A: fast をあらかじめ 1 周回分余計に進めておくことで while ループ内の NULL チェックがシンプルになり、ループ終了時に slow が中央セルを正しく指します。
Q: 偶数個の場合のセル数はどうなりますか?
A: 要素数が 個なら slow が進む回数は 回となり、前半が 、後半も で完全に半分に割れます。

関連キーワード: マージソート、連結リスト、ポインタ走査、再帰呼出し、分割統治

設問2

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

模範解答

エ:aがNULLと等しくない オ:「aがNULLと等しい」   もしくは   「bがNULLと等しくない」 カ:head->next

解説

解答の論理構成

  1. 併合ループの継続条件
    図7には
    while ( 、かつ、bが NULL と等しくない )
    とあります。ここで b については既に “bが NULL と等しくない” が書かれているので、残る には a が空でないことを示す条件を入れれば、両方のリストに要素がある間ループします。
    「aが NULL と等しくない」
  2. 残余リストの判定
    ループを抜けると、どちらかのリストが空になっています。図7の
    if ( ) // 要素が残っている連結リストを連結する
    は“どちらに要素が残っているか”を判定する式です。
    ・a が空なら b に要素が残る
    ・逆に b が空なら a に要素が残る
    よって
    • 「aが NULL と等しい」
    • または同値の 「bが NULL と等しくない」
      のいずれでも正しく動作します。
      「aが NULL と等しい」(同値条件として「bが NULL と等しくない」も可)
  3. 返却値
    ダミーセル head を使っているため、完成した連結リストの実際の先頭は head->next です。図7の
    return
    に入るのは head->next になります。
    head->next

誤りやすいポイント

  • ダミーセルを使う場合、返却時に head をそのまま戻してしまうミス。
  • で “要素が残っている方”を判定するのに aが NULL と等しくない と書いてしまい、条件が逆になるミス。
  • while 条件に a と b の両方を同時に書き、二重否定で混乱してループが早く終わるロジックエラー。

FAQ

Q: ダミーセルを入れる利点は何ですか?
A: 先頭ノードの追加・削除時に特別扱いをせず、一律で“現在ノードの後ろに挿入”という処理に統一できるため、コードが簡潔かつバグが入りにくくなります。
Q: に二つの書き方があるのはなぜ?
A: ループ終了時には a と b のどちらかが必ず NULL です。aが NULL と等しい でも bが NULL と等しくない でも“要素が残っている方が b である”という事実を表現できるため、同値条件として両方認められます。
Q: マージソートは配列より連結リストの方が向いているのですか?
A: 連結リストでは「分割」「併合」がポインタの付け替えだけで済むため、要素の大量コピーが不要になります。対して配列では併合のたびに配列領域へのコピーが発生するため、連結リストの方がメモリ効率が良い場合があります。

関連キーワード: 連結リスト、マージソート、ポインタ操作、ダミーノード、併合アルゴリズム

設問3

32個のセルから成る連結リストに対し、図2のアルゴリズムに相当するプログラムを実行した場合、関数mergeは何回呼び出されるか答えよ。

模範解答

31

解説

解答の論理構成

  1. マージソートは、まずデータを「それぞれの要素数が1になるまでデータ列の分割を繰り返し」た後、再帰的に「要素が昇順に並ぶよう一つのデータ列に併合する」(【問題文】「図2中の(4)」)アルゴリズムです。
  2. 連結リストに対しても同じで、再帰木(分割木)を描くと、葉は “要素数1” のリストに相当します。今回のリスト長は 32 個のセルです。
  3. 葉が 32 個ある完全二分木の内部ノード数は葉数−1 で計算できます。これは木理論の基本性質で、次のように導けます。
    • 葉数を とすると、二分木の辺数は (根から葉まで張られる)。
    • 各内部ノードは必ず一度だけ併合(“図2中の(4)”=merge 呼び出し)を担当する。
    • したがって merge の呼び出し回数は
  4. を代入すると
  5. 以上より、32 個のセルを整列する過程で 関数 merge は 31 回呼び出される と分かります。

誤りやすいポイント

  • 「高さ × データ数」で回数を見積もり、log₂32 = 5 として 5 回 と勘違いする。呼び出しはレベルごとの合計になるため誤りです。
  • "図2中の(4)" が内部で再帰を持つと思い込み、divide・merge をセットで 1 回として数えてしまう。
  • 末尾の NULL が 1 要素に見えるなど、葉の数を 33 と数えてしまう(リスト長はあくまで 32)。

FAQ

Q: merge は葉のリストまで呼び出されるのでは?
A: いいえ。要素数が1のときは「図2中の(1)」で「呼出し元に処理を戻す」ので merge は実行されません。
Q: リスト長が 2 のときは何回 merge が呼ばれますか。
A: 回です。2 要素を分割すると葉が 2、内部ノードが 1 だからです。
Q: 配列実装でも同じ回数ですか。
A: はい。分割木の形が同じならデータ構造に依存せず merge 回数は です。

関連キーワード: マージソート、再帰呼出し、連結リスト、完全二分木、計算量
戦国ITクイズ機能

\ せっかくなら /

応用情報技術者
クイズ形式で学習しませんか?

クイズ画面へ遷移する

すぐに利用可能!

©︎2026 情報処理技術者試験対策アプリ

このサイトについてブログプライバシーポリシー利用規約特商法表記開発者について