- 配列・リスト・スタック・キュー・木・ハッシュ表の仕組みと、操作にかかるコストの違い
- 整列と探索の代表的アルゴリズムを、途中状態の図と計算量の式の両方から見分ける
- 逆ポーランド記法・再帰・分割統治・動的計画法という「解き方の型」
- 流れ図と IPA 共通の擬似言語を読み下し、机上でトレースする作法
- プログラムの 4 性質(再帰的・再使用可能・再入可能・再配置可能)と言語・データ記述言語の分類
科目 A は直近 10 年 19 回で 66 問、1 回平均 3.5 問。3 問確保が目標です。科目 B は直近 19 回すべて「問 3」がプログラミングで、本章がその土台になります(解き方の実戦は第 24 章)。
配列とリスト ― 格納の仕方でコストが変わる
データ構造を選ぶ基準は「どの操作を速くしたいか」の一点です。配列は添字の計算だけで任意の要素へ一発で届く代わりに、途中に要素を割り込ませると後続を全部ずらす必要があります。連結リストはポインタを張り替えるだけで割り込めますが、n 番目の要素に届くには先頭から順にたどるしかありません。この裏返しの関係が、科目 A では「配列実現とポインタ実現の長所短所を入れ替えた誤答肢」として繰り返し出題されています。
配列 ― 添字から位置を計算する
配列は同じ型の要素をメモリ上の連続領域に並べたものです。要素 1 個の大きさを L、先頭アドレスを a とすると、添字 i(0 始まり)の要素は a + L × i にあります。たどる必要がないので、要素数がいくら増えてもアクセス時間は変わりません。宣言時に大きさが決まる静的配列と、実行時に確保・拡張できる動的配列があります。
多次元配列も、実体は 1 次元の連続領域です。2 次元配列を「行を単位に前から並べる」置き方を行優先(row-major)といい、科目 B では盤面やグラフを 1 次元配列に載せる添字式(div と mod)が定番の論点になります。直近 10 年の科目 B 問 3 では、この mod/div による添字計算が 3 回、より広く「配列によるデータ表現と添字操作」が 19 回中 17 回で問われました。
計算の型: 2 次元を 1 次元配列に写す添字式
列数 C の 2 次元配列 A[i, j](i, j とも 1 始まり)を 1 次元配列 B に行優先で写すと、B[(i − 1) × C + j] が対応する。逆向きは、p = 要素番号 − 1 として 行 = p div C + 1、列 = p mod C + 1。
- 例題: 9 × 9 の盤面を 1 次元配列(要素番号 1〜81)に行優先で載せた。要素番号 40 は何行何列か
- 0 始まりに直す: p = 40 − 1 = 39
- 行 = 39 div 9 + 1 = 4 + 1 = 5
- 列 = 39 mod 9 + 1 = 3 + 1 = 4
- 答え: 5 行 4 列。検算は (5 − 1) × 9 + 4 = 40 で元に戻ること
リスト ― ポインタを張り替える
線形リスト(リンク付リスト)は、値と「次の節点を指すポインタ」を持つ節点をつないだ構造です。次だけを持つものが単方向リスト、前後両方を持つものが双方向リスト、末尾のポインタが先頭に戻るものが環状リストです。挿入・削除の本体はポインタの書換えだけで、要素数にも位置にも依存しない一定コストで終わります(図 3-1)。
ただし「挿入自体は一定コスト」であって、挿入位置を見つけるまでのコストは別勘定です。単方向リストで末尾の節点を削除するには、末尾の 1 つ前までポインタをたどる必要があり、これがリスト操作の中でたどる回数が最も多くなります。双方向リストなら前の節点をすぐ得られるので、途中の節点を削除するときに書き換えるポインタは前後 2 本だけです。
| 操作・性質 | 配列で実現 | ポインタ(連結リスト)で実現 |
|---|---|---|
| i 番目へのアクセス | 添字計算 1 回、O(1) | 先頭から i 回たどる、O(n) |
| 途中への挿入・削除 | 後続を全部ずらす、O(n) | ポインタ 1〜2 本の書換え、O(1) |
| 末尾の削除 | 添字で直接、O(1) | 単方向は末尾の 1 つ前までたどる(最多) |
| 記憶領域 | 連続領域を先に確保。未使用領域が生じる | 必要な分だけ確保。ポインタの分だけ余計に要る |
| 要素数の増減 | 上限を超えると再確保が要る | 上限なし(メモリの許す限り) |
スタックとキュー ― LIFO / FIFO と逆ポーランド記法
スタックは最後に入れたものを最初に取り出す LIFO(Last In First Out)、キューは最初に入れたものを最初に取り出す FIFO(First In First Out)です。スタックへの格納がプッシュ、取出しがポップ、キューへの格納がエンキュー、取出しがデキューです。どちらも「取り出せるのは端の 1 つだけ」という制約がある代わりに、操作が O(1) で済みます。
| 構造 | 取出しの規則 | 試験で問われる用途 |
|---|---|---|
| スタック(LIFO) | 最後に入れたものから | 関数呼出しの戻り番地・局所変数、再帰、深さ優先探索、逆ポーランド式の評価、括弧の対応検査 |
| キュー(FIFO) | 最初に入れたものから | 幅優先探索、印刷ジョブや通信バッファの待ち行列、生産者・消費者の受渡し |
同じ木を探索しても、次に見る節点をスタックで管理すれば深さ優先探索、キューで管理すれば幅優先探索になります。データ構造を差し替えるだけで探索順序が変わるという関係は、科目 B でも 4 回問われている頻出の対比です。根 A の子が B・C、B の子が D・E、C の子が F・G という木(後出の図 3-4 と同じ形)で、取り出した節点の子を入れながら 1 手ずつ進めると次のようになります。スタックは先に取り出したい方を後に積むので子を右 → 左の順にプッシュ、キューは子を左 → 右の順にエンキューします。
| 手順 | スタックで管理(深さ優先): 取り出す節点/残りの中身 | キューで管理(幅優先): 取り出す節点/残りの中身 |
|---|---|---|
| 1 | A / C・B(次に出るのは上の B) | A / B・C(次に出るのは先頭の B) |
| 2 | B / C・E・D(次は D) | B / C・D・E |
| 3 | D / C・E | C / D・E・F・G |
| 4 | E / C | D / E・F・G |
| 5 | C / G・F(次は F) | E / F・G |
| 6 | F / G | F / G |
| 7 | G / 空になったので終了 | G / 空になったので終了 |
| 訪問順 | A → B → D → E → C → F → G(先行順) | A → B → C → D → E → F → G(レベル順) |
どちらも終了条件は「スタック(キュー)が空になったとき」で、科目 B ではこの 1 行がそのまま穴埋めになります。あわせて、一度訪れた節点を再び入れない訪問済み管理の配列を用意するのが、グラフを探索するときの定型です。
スタックで作れる出力順列
A、B、C をこの順に 1 回ずつプッシュするとして、プッシュとポップは任意の順で交ぜてよいとき、取り出せる順列は何通りでしょうか。3 個の並べ方は 3! = 6 通りですが、スタックでは実現できない並びが 1 つあります(図 3-2)。
逆ポーランド記法(後置記法)
演算子を 2 つの被演算子の後ろに書く記法を逆ポーランド記法(後置記法)といいます。括弧が不要になり、スタックだけで機械的に評価できるのが利点です。中置式からの変換は、式を木にして後行順(左部分木 → 右部分木 → 根)でたどるのが確実です。
中置: 3 + 4 × 2 式の木(× が + より優先なので × が下に来る)
+
/ \
3 ×
/ \
4 2
後行順(左 → 右 → 根): 3 → 4 → 2 → × → +
後置式: 3 4 2 × +
評価は、記号を左から 1 つずつ読み、数値ならプッシュ、演算子ならスタックの上位 2 つをポップして計算し、結果をプッシュするだけです(図 3-3)。
木構造 ― 表現・巡回・2 分探索木の操作
木は、根から枝をたどって葉に至る階層構造です。ある節点から見て上が親、下が子で、子を高々 2 個しか持たない木を 2 分木、3 個以上を許すものを多分木といいます。子の並び順に意味がある木が順序木です。根の深さを 0 とすると深さ k の節点は最大 2k 個です。完全 2 分木は、最下段を除くすべての深さが埋まっていて、最下段は左から詰めて並んでいる木をいいます。とくにすべての深さが埋まっているとき(葉の深さがすべて等しいとき)は、節点数 n と高さ h の間に n = 2h+1 − 1 が成り立ちます(n = 7 なら h = 2)。木の高さが log2 n 程度に収まることが、探索を速くする根拠です。
配列表現と 3 つの巡回順序
完全 2 分木はポインタを使わずに配列だけで表せます。根を要素番号 1 とすると、節点 i の左の子は 2i、右の子は 2i + 1、親は i div 2 です。この配列を添字の小さい順に見ていく処理は幅優先探索(レベル順)に一致します(図 3-4)。一方、再帰でたどる順序には、根をいつ訪れるかで先行順・中間順・後行順の 3 種類があります。
2 分探索木とその操作
2 分探索木は「左部分木のすべての値 < 節点の値 < 右部分木のすべての値」を保つ木です。探索は根から比較して片側へ降りるだけなので、木が平衡していれば O(log n) で済みます。中間順でたどると値が昇順に並ぶのも、この定義から出る性質です。
削除は 3 通りに場合分けします。葉ならそのまま外し、子が 1 個なら子を繰り上げ、子が 2 個なら「右部分木の最小値」(中間順で次に来る値=後継)を削除位置へ昇格させます(図 3-5 左)。左部分木の最大値を昇格させても条件は保たれますが、試験では後継を使う形で問われています。
挿入を繰り返すと木が一方向に伸びて O(n) に劣化するため、左右部分木の高さの差を 1 以下に保つのが AVL 木です。差が 2 になった時点で回転して平衡を戻します(図 3-5 右)。
B 木・ヒープ
B 木は 1 つの節点に複数のキーと複数の子を持たせた多分木で、木の高さを低く抑えられるためディスクアクセス回数が減り、データベースのインデックスに使われます(第 9 章)。ヒープは「親の値 ≧ 子の値」(最大ヒープ)を全節点で保つ完全 2 分木で、根が常に最大値になります。配列表現と相性がよく、優先度付きキューやヒープソートの土台です。
根を取り出したあとにヒープの条件を回復させる操作がヒープの再構成で、手順は 3 つです。
- 根(最大値)を取り出す
- 最後尾の節点を根の位置へ移す
- 根から下へ、子のうち大きい方と比べ、親の方が小さければ交換する。交換が起きなくなるまで繰り返す
比較は木の高さのぶんしか起きないので 1 回の再構成は O(log n)、これを要素数ぶん繰り返すヒープソートは全体で O(n log n) になります。
| 木の種類 | 保つ条件 | 主な用途・性質 |
|---|---|---|
| 2 分探索木 | 左 < 節点 < 右 | 探索・挿入・削除が平均 O(log n)。偏ると O(n) |
| AVL 木(バランス木) | 左右部分木の高さの差 ≦ 1 | 回転で平衡を維持し、最悪でも O(log n) |
| B 木 | 全葉が同じ深さ、節点あたり複数キー | 高さが低くディスク I/O が少ない。DB のインデックス |
| ヒープ | 親 ≧ 子(または親 ≦ 子) | 根が最大(最小)。優先度付きキュー、ヒープソート |
探索アルゴリズム ― 比較回数を式で押さえる
探索は直近 10 年で 10 問と、この中分類で最も点になる論点です。問われるのは手順の暗記ではなく比較回数の式と前提条件なので、下の 4 つを式ごと覚えます。
| 探索法 | 前提 | 比較回数(n 件) |
|---|---|---|
| 線形探索 | なし(未整列でよい) | 平均 (n + 1) ÷ 2、最悪 n。O(n) |
| ブロック分割探索 | ブロックの代表値が順に並ぶ | 平均 m ÷ 2 + n ÷ (2m)(m = ブロックの大きさ) |
| 2 分探索 | キーで整列済み | 最大 ⌈log2(n + 1)⌉。O(log n) |
| ハッシュ表探索 | ハッシュ関数と衝突対策 | 衝突が少なければデータ数によらずほぼ一定。O(1) |
線形探索は先頭から順に比べるだけです。目的のデータが必ず表にあるなら、1 回目で見つかる確率も n 回目で見つかる確率も等しいので、平均比較回数は (n + 1) ÷ 2 になります。配列の末尾に探索キーと同じ値をあらかじめ置く番兵を使うと、「末尾に達したか」の判定が不要になり、ループ 1 回あたりの比較が 2 回から 1 回に減ります。
2 分探索は整列済みの配列の中央と比べ、大小に応じて探索範囲を半分に捨てます。1 回の比較で候補が半分になるので、n 件なら最大 ⌈log2(n + 1)⌉ 回です。n = 1,000 なら log2 1001 ≒ 9.97 なので 10 回で必ず決着します。整列済みでなければ使えない点が、誤答肢を作るときの定番の落とし穴です。
ブロック分割探索の比較回数
ブロック分割探索は、n 件を m 件ずつのブロックに分け、まずブロックの代表値を線形探索して目的のブロックを決め、次にそのブロック内を線形探索します。2 段階どちらも線形探索なので、平均は「それぞれの平均の和」になります(図 3-6)。
計算の型: ブロック分割探索の平均比較回数
平均比較回数 = m ÷ 2 + n ÷ (2m)(n: 全件数、m: 1 ブロックの件数)
- 例題: n = 10,000 件を m = 100 件ずつに分けたときの平均比較回数は
- ブロック内の平均: m ÷ 2 = 100 ÷ 2 = 50 回
- ブロック数は n ÷ m = 100 個。その線形探索の平均は 100 ÷ 2 = 50 回(= n ÷ (2m))
- 答え: 50 + 50 = 100 回。線形探索の平均 5,000 回に比べて 50 分の 1
- ちなみに m = √n のとき合計が最小になり、その値は √n(この例では 100)
ハッシュ表探索と衝突
ハッシュ表探索は、キー x をハッシュ関数 h(x) に通して得た値を格納位置にします。位置を「計算で決める」ので、比較しながら絞り込む必要がなく、データ数が増えても探索時間はほとんど変わりません。試験では剰余法 h(x) = x mod n が使われ、異なるキーが同じ位置に割り当てられる衝突(シノニムの発生)の条件が繰り返し問われます(図 3-7)。
衝突が起きたときの対策(シノニム対策)は 2 系統です。チェイン法(オープンハッシュ法)は同じ位置に来たキーを連結リストでつなぎます。オープンアドレス法(クローズドハッシュ法)は別の空き位置を再計算して入れる方式で、線形探査法などがあります。どちらもデータが詰まってくる(占有率が高くなる)ほど探索が遅くなるので、実務では表を余裕をもって確保します。
整列アルゴリズム ― 途中状態で見分ける
整列は科目 A で 10 年 7 問。うちバブルソートが 4 問で、単一テーマとしてはこの中分類で最多です。近年は「説明文からアルゴリズム名を選ぶ」型に加えて、流れ図や整列途中の状態から名前を当てる読解型が増えました。手順を丸暗記するより、1 パスで何が確定するかと何と何を比べるかの 2 点で見分けるのが確実です(図 3-8)。
残る 4 つは次のとおりです。シェルソートは、離れた要素どうしの挿入ソートを間隔を縮めながら繰り返し、大きなずれを先に解消します。クイックソートは基準値(ピボット)より小さい群と大きい群に分けて、それぞれを再帰的に整列する分割統治法です。マージソートは列を半分ずつに分割し、整列済みの 2 列を併合して戻します。ヒープソートはヒープを構成して根(最大値)を取り出し、残りをヒープに再構成する処理を繰り返します。
| 整列法 | 1 パスで確定するもの・特徴 | 平均 | 最悪 | 安定 |
|---|---|---|---|---|
| バブル | 隣接 2 個を比較・交換。末尾から確定 | O(n2) | O(n2) | 安定 |
| 選択 | 未整列部分の最小値を選び先頭と交換 | O(n2) | O(n2) | 不安定 |
| 挿入 | 整列済み部分の正しい位置に差し込む | O(n2) | O(n2) | 安定 |
| シェル | 間隔を縮めながら挿入ソートを繰り返す | 実装依存(O(n1.3) 程度) | O(n2) | 不安定 |
| クイック | ピボットで 2 分割して再帰(分割統治) | O(n log n) | O(n2) | 不安定 |
| マージ | 分割して整列し併合(分割統治) | O(n log n) | O(n log n) | 安定 |
| ヒープ | ヒープの根を取り出して再構成 | O(n log n) | O(n log n) | 不安定 |
安定な整列とは、キーが等しい要素どうしの元の並び順が整列後も保たれるものをいいます。バブル・挿入・マージは安定、選択・シェル・クイック・ヒープは不安定です。「支店コードで並べ替えたあと売上額で並べ替える」ように多段のキーで並べ替えるときに安定性が効きます。
再帰・分割統治・動的計画法とグラフ
再帰とは、手続の中で自分自身を呼び出すことです。正しい再帰の定義には必ず ① それ以上呼び出さずに値を返す停止条件と ② 引数が停止条件へ近づく呼出しの 2 つが要ります。階乗の定義なら「n = 0 のとき 1、それ以外は n × fact(n − 1)」で、この形をそのまま選ばせる問題が 10 年で 2 回出ています。
○整数型: fact(整数型: n)
if (n が 0 と等しい) /* 停止条件 */
return 1
else
return n × fact(n - 1) /* 引数が 1 ずつ 0 へ近づく */
endif
再帰呼出しはスタック(LIFO)で実現されます。呼び出すたびに戻り番地・引数・局所変数の組(スタックフレーム)が積まれ、停止条件に達すると積んだ逆順に戻りながら計算が確定します(図 3-9)。科目 B の講評でも「呼出し元へ戻った後の引数を再現できない」誤答が名指しされており、戻りの追跡こそが得点差になります。
再帰で書ける典型としてハノイの塔があります。n 枚の円盤を移す最小手数は 2n − 1 回で、「n − 1 枚を退避 → 最大の 1 枚を移す → n − 1 枚を戻す」という 2 回の再帰から導けます。フィボナッチ数列を素朴な二重再帰で書くと同じ部分問題を何度も計算して指数時間になるため、後述の動的計画法で表に記録します。
分割統治法と動的計画法
分割統治法は、問題を小さな部分問題に分割し、それぞれを独立に解いてから結果を結合する設計法です。クイックソート・マージソート・2 分探索がこの型です。一方動的計画法は、部分問題が重なり合うときにいったん解いた部分問題の答えを表に記録して再利用します。「独立に解く」のが分割統治、「解を使い回す」のが動的計画法という対比で覚えます。
手順の型: 動的計画法の 5 ステップ
- 分割: 元の問題を「小さい添字の同種の問題」に言い換える
- 表の定義: 表の各要素が何を表すかを 1 文で決める(例: L[i, j] = 先頭 i 文字と先頭 j 文字の最長共通部分列の長さ)
- 更新式: L[i, j] を、既に求めた L[i − 1, j − 1] や L[i − 1, j] などから決める漸化式を書く
- 埋める順序: 更新式が参照する要素が先に埋まる順(多くは左上から右下へ)に走査する
- 読み出し: 答えが入っている位置(多くは右下)を指定する
グラフのアルゴリズムと計算量
グラフは節点(ノード)と辺(エッジ)の集合です。表現は 2 通りで、隣接行列は節点数 n の n × n 表に辺の有無を持つため辺が多いグラフ(密グラフ)向き、隣接リストは各節点が「つながっている節点の並び」を持つため辺が少ないグラフ(疎グラフ)向きです。探索は木と同じで、スタックを使えば深さ優先探索、キューを使えば幅優先探索になります。重み付きグラフの最短経路は、確定済みの節点から近い順に確定していくダイクストラ法が代表で、負の重みがある場合はベルマンフォード法を使います。文字列照合の高速化には KMP 法(クヌース・モリス・プラット法)や BM 法(ボイヤ・ムーア法)があります。
| 記法・用語 | 意味 | 代表例 |
|---|---|---|
| O(1) | データ量によらず一定 | 配列の添字アクセス、ハッシュ表探索 |
| O(log n) | データが倍でも手数は 1 増えるだけ | 2 分探索、平衡木の探索 |
| O(n) | データ量に比例 | 線形探索、リストの走査 |
| O(n log n) | 分割統治や木の高さ × 全要素 | クイック・マージ・ヒープソート |
| O(n2) | 二重ループ | バブル・選択・挿入ソート、隣接行列の全走査 |
O 記法は「入力を大きくしたときの増え方」だけを表し、定数倍や下位の項は無視します。同じ問題でも最良・平均・最悪で計算量が変わることがあり(クイックソートは平均 O(n log n)・最悪 O(n2))、試験ではどのケースを聞かれているかを必ず確認します。近似アルゴリズム(厳密解の代わりに十分よい解を短時間で得る)、モンテカルロ法などの確率アルゴリズム、遺伝的アルゴリズム、ハフマン法・ランレングス法などのデータ圧縮アルゴリズム、Z バッファ法・スキャンライン法・レイトレーシング法などの 3 次元図形処理アルゴリズムも、シラバスには用語として並びます。
このほかシラバスには、バッチ処理で使うファイル処理のアルゴリズム(キーの切れ目で小計を出すコントロールブレーク処理、桁揃えやゼロ抑制で出力形式を整える編集処理)、自然言語処理のアルゴリズム(形態素解析・構文解析・意味解析・文脈解析・係り受け解析・n-gram・文章間類似度)、OS の記憶域管理アルゴリズム(空きメモリ管理のためのデータ構造と、メモリの割当て・解放。best-fit などの詳細は第 6 章)が用語例として並びます。いずれも科目 A では 10 年間ほぼ出ていないので、名前と 1 行の説明を結び付けられれば十分です。
流れ図と擬似言語 ― トレースの作法
アルゴリズムの表現方法には流れ図(フローチャート)、擬似言語、決定表(デシジョンテーブル)があります。科目 A では流れ図の読解が 10 年で 5 問、科目 B は 19 回すべてが擬似言語です。どちらも「絵を眺める」のではなく変数の値を紙に書き出して 1 周たどるのが正解への最短経路です。
| 流れ図の記号 | 意味 | 読むときの注意 |
|---|---|---|
| 端子 | 処理の開始・終了 | 入口と出口が 1 つずつあるか確認する |
| 処理 | 代入・計算などの 1 ステップ | 代入の向き(左辺が変わる)を取り違えない |
| 定義済み処理 | 別に定義された手続の呼出し | 中身は別の流れ図。戻ってくる位置を押さえる |
| 判断 | 条件による分岐 | Yes / No のどちらの枝かを線ごとに確認する |
| ループ端 | 繰返しの始端と終端 | 始端に条件があれば前判定、終端にあれば後判定 |
| データ・線 | 入出力データ、処理の流れ | 線の向き(矢印)が処理順序を決める |
構造化プログラミングでは、どんな処理も順次(連接)・選択・繰返しの 3 つの制御構造だけで書けます(構造化定理)。ここに手続呼出しが加わると、制御の流れはいったん呼出し先へ移り、戻ってきて続きを実行します。呼出し側が渡す値がパラメータ(実引数)、受け取る側が仮引数です。なお、実行中のプログラム 1 つ分を OS が管理する単位がプロセスで、1 つのプロセッサを細かく切り替えて複数の処理が同時に進んでいるように見せる方式を擬似並列制御といいます(詳細は第 6 章)。
繰返しには判定の位置で 2 種類あり、条件を先に見る前判定(while 型)は 1 回も実行されないことがあり、後で見る後判定(do-while 型)は必ず 1 回は実行されます(図 3-10)。令和 6 年春には、2 つの流れ図が同じ結果になるようにループの終了条件を選ばせる等価変換が出題されました。判定の位置と条件の境界で実行回数が変わる点が論点です。
擬似言語の記法
科目 B の擬似言語は IPA が公表している共通の記法です。答案も擬似言語の断片で書くため、記法そのものの正確さが得点になります。最低限、次の要素は読み書きできるようにします。
○整数型: maxOf(整数型の配列: data) /* ○ は手続・関数の宣言、: の後ろが戻り値型 */
整数型: i, max /* 変数宣言 */
max ← data[1] /* ← が代入。配列の要素番号は 1 から */
for (i を 2 から dataの要素数 まで 1 ずつ増やす)
if (data[i] > max)
max ← data[i]
elseif (data[i] = max) /* 条件の追加は elseif */
/* 何もしない */
endif
endfor
while (max < 0) /* 前判定の繰返し */
max ← 0
endwhile
return max
| 記法 | 意味 | つまずきどころ |
|---|---|---|
| ← | 代入(右辺の値を左辺へ) | 空欄に代入を書くときは ← ごと書く。等号 = で書くと減点 |
| 配列[i] / 配列[i, j] | 要素の参照。要素番号は 1 始まり | 2 次元は [i, j] と [i][j] の両方の書き方が出る。設問の他の行に合わせる |
| 変数.メンバ | 構造体・クラスのメンバ参照 | 宣言部のコメントにメンバの意味が書いてある |
| div / mod | 整数除算の商 / 剰余 | 1 次元配列に 2 次元構造を載せるときの定番 |
| for (i を A から B まで 1 ずつ増やす) | 繰返し。B を含む | while の「未満」と混同しない。両端を含むか必ず確認 |
| /* … */ | 注釈 | 穴埋めの答えの語は大半がここに書いてある |
手順の型: 机上トレースの作法
- 設問だけ先に読み、空欄の個数と、それがどの図・どの行にあるかを把握する
- 「実データ ↔ 配列」の対応表を余白に書く(例: viaNode[k] = k の直前のノード)
- 変数を横に並べた表を書き、1 行実行するごとに値を 1 行足す(上書きしない)
- 境界だけは具体値で検算する(要素数 0・1 のとき、i が最初と最後のとき)
- 空欄は前後 2〜3 行から「この行が何を確定させるか」を決めてから埋める
たとえば data = {3, 9, 4} に対して上の maxOf をトレースすると、次の表になります。値を上書きせずに 1 行ずつ足していくのが、戻りや分岐を追い切るコツです。
| 実行行 | i | max | 判定 |
|---|---|---|---|
| max ← data[1] | ― | 3 | ― |
| i = 2 の回 | 2 | 9 | data[2] = 9 > 3 なので更新 |
| i = 3 の回 | 3 | 9 | data[3] = 4 > 9 は偽なので据置き |
| while (max < 0) | ― | 9 | max = 9 なので条件は偽。前判定なので 0 回実行 |
| return max | ― | 9 | for は i = 3 で終了(B を含む) |
条件の組合せと動作の対応を網羅的に整理するときは決定表を使います。上半分に条件、下半分に動作を書き、列ごとに「Y/N の組合せ」と「実行する動作」を対応づけます。条件が n 個なら列は最大 2n 通りになるため、抜けと重複の検査に向いています。
プログラミングとプログラム言語
この節は「知っていれば 1 問取れる」用語が並ぶ領域です。深追いせず、対比の形で覚えます。まずプログラミング作法とコーディング標準は、字下げ(インデンテーション)・ネストの深さの上限・命名規則・使用禁止命令などをチームで揃える取決めで、目的は機能適合性・性能効率性・使用性・保守性の向上です。守らないと、動くけれど読めない・直せないプログラムが残ります。
プログラムの 4 性質
直近 10 年で 3 回出題されました。うち 2 回(平成 29 年春・令和 3 年秋)は同じ 4 つの記述から正しいものを選ばせ、正解肢だけが入れ替わる形式、令和 4 年春は再入可能の特徴だけを問う形式です。いずれも 4 つの定義を相互に入れ替えた誤答肢が作られるので、下の表を丸ごと覚えます。
| 性質 | 定義 | 実現の条件 |
|---|---|---|
| 再帰的(リカーシブ) | 実行中に自分自身を呼び出せる | 呼出しごとの局所データをスタックに積む |
| 再使用可能(リユーザブル) | 一度実行した後、再ロードせずにもう一度実行できる | 実行のたびに変数を初期化する |
| 再入可能(リエントラント) | 複数のタスクから同時に呼ばれても、それぞれ正しい結果を返す | 手続部と、タスクごとのデータ部を分離する |
| 再配置可能(リロケータブル) | 主記憶上のどのアドレスに置いても実行できる | アドレスを相対参照で書く |
プログラム構造とオブジェクト指向
大きなプログラムは、全体の流れを制御するメインルーチンと、そこから呼び出されるサブルーチンに分けて組み立てます。このモジュール分割では、モジュール間の結びつきを弱く(結合度を低く)、内部のまとまりを強く(強度を高く)して独立性を高めるのが原則です(詳細は第 13 章)。オーバーロードは同じ名前の手続を引数の型や個数で区別して複数定義すること、オーバーライドは継承したメソッドをサブクラス側で定義し直すことです。名前が似ているので、「多重定義がオーバーロード、上書きがオーバーライド」と対で覚えます。共通処理を実行時に結合して共有する仕組みが DLL(Dynamic Link Library)です。
オブジェクト指向の柱は 3 つです。カプセル化はデータと操作を 1 つにまとめ内部を隠すこと、継承は既存クラスの性質を引き継いで新しいクラスを作ること、多態(ポリモーフィズム)は同じメッセージに対してオブジェクトごとに異なる振る舞いをすることです。クラスから生成された実体がインスタンスです。データ型には整数型・実数型・論理型・文字型のほか、複数の型をまとめた構造型、値の表現方法を隠して操作だけを公開する抽象データ型があります。
手続への値の渡し方は 2 通りです。値呼出し(call by value)は実引数の値を複写して渡すため、手続の中で仮引数を書き換えても呼出し元には影響しません。参照呼出し(call by reference)はアドレスを渡すため、書換えが呼出し元にも反映されます。実行時の記憶域は、静的変数(プログラム実行中ずっと存在)、自動変数(ブロックに入ると確保・出ると解放)、ヒープ(動的に確保し、不要になった領域はガベージコレクションが回収)に分かれ、変数が有効な範囲をスコープといいます。翻訳後のプログラム本体(目的プログラムテキスト)は実行中に書き換えないので複数のタスクで共有でき、これが再入可能を成り立たせる前提になります。
言語の種類と文法の表記法
プログラム言語は、プロセッサが直接解釈する機械語から、その命令に記号名を与えたアセンブラ言語、人間が読み書きしやすい高水準言語へと発展してきました。高水準言語は次の 5 つに分類されます。
| 分類 | 特徴 | 代表例 |
|---|---|---|
| 手続型言語 | 処理手順を順に記述する | Fortran、COBOL、Pascal、BASIC、C、R(統計解析向き) |
| オブジェクト指向言語 | データと操作をクラスにまとめる | Java、C++、Julia、Go、Scala(関数型の性質も併せ持つ) |
| スクリプト言語 | コンパイルせず逐次解釈で手軽に実行 | ECMAScript(JavaScript)、Perl、PHP、Python、Ruby |
| 関数型言語 | 関数の適用で計算を表し、副作用を抑える | Haskell、Lisp 系 |
| 論理型言語 | 事実と規則を宣言し、推論で答えを導く | Prolog |
ソースコードをまとめて機械語に翻訳してから実行するのがコンパイラ方式、1 文ずつ解釈しながら実行するのがインタプリタ方式です。実行中に頻繁に通る部分だけを機械語に変換して速度を稼ぐ JIT(Just In Time)コンパイル方式もあります。JIS X 3016(ISO/IEC 23271)で標準化された共通言語基盤(CLI)は、複数の言語から共通の中間形式・実行環境を使えるようにする枠組みです。
変数・式・文・手続といったプログラムの構成単位をどう組み立てるかという構文規則は、記号で厳密に書き表す構文記法で定義します。その代表が BNF(Backus Naur Form:バッカス記法)というメタ言語で、繰返しや省略の記法を加えたものが EBNF(Extended Backus Naur Form:拡張バッカス記法)、BNF で定義できる文法の範囲が文脈自由文法です。試験では「この規則から生成できる文字列はどれか」という読解型が出ます。
<数字> ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
<数> ::= <数字> | <数><数字>
読み方: 「::=」は「〜と定義する」、「|」は「または」。
<数> は再帰的に定義されているので 1 桁以上の数字列すべてを表す。
したがって 123 は生成できるが、1.5 や A1 は生成できない。
マークアップ言語・データ記述言語
| 言語 | 位置づけ | 関連用語 |
|---|---|---|
| HTML | Web ページの構造を記述 | 開始タグ・終了タグ、DTD(Document Type Definition:文書型定義)、SGML(Standard Generalized Markup Language) |
| XML | タグを独自に定義できるデータ交換用 | DOM(Document Object Model)、SAX(Simple API for XML)、XML Schema、SOAP(XML でメッセージを交換するプロトコル)、SVG(Scalable Vector Graphics)、符号化宣言 |
| XHTML | HTML を XML の規則で再定義 | XHTML Basic、XHTML Modularization |
| CSS / XSL | 構造と表示形式を分離するスタイルシート | CSS(Cascading Style Sheets:段階スタイルシート)、XSL(Extensible Stylesheet Language:拡張可能なスタイルシート言語) |
| JSON | 「名前と値の組」「値の順序付きリスト」でオブジェクトを表す | JavaScript 由来。Web API の標準的な交換形式 |
| YAML | 字下げで階層を表す設定記述向けの形式 | 設定ファイル、CI 定義 |
| UML | オブジェクト指向設計の図的表記法。クラスは属性と操作を持ち、関連の端に付ける役割名がロール名 | クラス図、オブジェクト図、コンポーネント図、パッケージ図、配置図、コンポジット構造図、アクティビティ図、ステートマシン図、シーケンス図、コミュニケーション図、相互作用概要図、タイミング図、ユースケース図(詳細は第 13 章) |
| SDL / ADL / DDL | UML 以外の記述言語 | SDL(Specification and Description Language)、ADL(Architecture Description Language:アーキテクチャ記述言語)、DDL(Data Definition Language:データ定義言語。第 9 章) |
Web の役割分担は、HTML が構造、CSS が見た目、JavaScript が振る舞いです。ブラウザ側で動くのがクライアントサイド、Web サーバ(代表的な実装が Apache HTTP Server)側で動くのがサーバサイドプログラミング(JSP=Jakarta Server Pages など)で、画面遷移せずに非同期通信で部分更新する仕組みが Ajax(Asynchronous JavaScript and XML)、1 枚の HTML で画面を切り替える構成が SPA(Single Page Application)です。ブラウザ側で操作性の高い画面を実現する仕組みの総称がリッチクライアントで、SPA の構築に使う部品群がフロントエンドフレームワークです。ブラウザ上で機械語に近い形式を高速実行する WebAssembly もシラバスの用語例に入っています。文字列のパターンを記述する正規表現は、入力チェックやログ検索で使う定番の道具です。
科目 B「問 3 プログラミング」への橋渡し
科目 B は問 1 の情報セキュリティだけが必須で、残りは問 2〜11 の 10 問から 4 問を選んで解きます。そのうちプログラミングは直近 19 回すべてで「問 3」に固定されているため、受験前に「解くか捨てるか」を決めておける数少ない分野です。19 問中 18 問が純粋なアルゴリズム題材(業務題材は平成 31 年春の券売機のみ)なので、本文から常識で推測する戦法は効きません。当たれば満点、添字を 1 つ取り違えれば全滅という分散の大きい分野です。
| 題材のジャンル | 10 年(19 回)での出題数 | 例 |
|---|---|---|
| 盤面・パズル系の探索とバックトラック | 7 問(37%) | 魔方陣、ライフゲーム、ナイトの巡歴、迷路、スライドパズル |
| グラフ・木 | 4 問 | 一筆書き、AVL 木の回転、ダイクストラ法 |
| 数値・整数・信号処理 | 3 問 | 多倍長整数、エラトステネスの篩、誤差拡散法 |
| データ分析・機械学習・業務モデル | 3 問 | パーセプトロン、k-means 法、券売機の状態遷移 |
| 動的計画法 | 2 問 | ナップザック問題、最長共通部分列 |
探索(深さ優先・幅優先・バックトラック)を中心とする最初の 2 ジャンルで 11 問=58% を占めます。選ぶと決めたなら、まず探索の型を仕上げるのが数字に見合った投資です。設問の内訳も偏っていて、設問数は平均 3.6 問(枝番まで展開すると平均 4.5 小設問)、そのうち穴埋めが約 58%、計算(実行回数・トレース値・計算量の式・修正行番号)が約 20% で、この 2 つで 8 割に達します。字数記述は 19 回中 5 回しか出ておらず、いずれも 10〜30 字で、40 字以上は 10 年間で 1 問もありません。作文力ではなく擬似言語を正確に書き下す力とトレース精度で決まる分野です。
時間配分は 150 分で 5 問なので 1 問あたり 30 分。下見 2 分 → 説明を読んで対応表 6 分 → トレース 6 分 → 空欄補充 12 分 → 検算 4 分が目安で、着手 10 分で「実データ ↔ 配列」の対応表が書けなければ撤退して別の問題に移ります。実際の過去問題材での解き方と検算の作法は第 24 章で扱います。
ひっかけポイント
理解度チェック(科目 A 形式)
Q1. 整列アルゴリズムのうち、隣り合う 2 つの要素を比較し、大小の順が逆であれば交換する操作を配列の端から端まで繰り返すものはどれか。
- クイックソート
- 挿入ソート
- バブルソート
- 選択ソート
正解: ウ バブルソートは隣接する 2 要素だけを比べて交換し、1 パスごとに末尾へ最大値を確定させます。ア は基準値で 2 分割して再帰する分割統治法、イ は前方の整列済み部分に正しい位置を探して差し込む方法、エ は未整列部分の最小値を選んで先頭と交換する方法です。
Q2. 中置表記の式 (P + Q) ÷ R を逆ポーランド記法(後置表記)で表したものはどれか。
- PQR+÷
- PQ+R÷
- ÷+PQR
- PQ÷R+
正解: イ 式の木を作り、後行順(左 → 右 → 根)でたどると P、Q、+、R、÷ となります。ア は + の被演算子が P と Q になっておらず括弧の効果が消えています。ウ は演算子を先に書く前置(ポーランド)記法です。エ は ÷ と + の適用順序が入れ替わっており、P ÷ Q + R を表します。
Q3. ハッシュ関数 h(x) = x mod n を使ってキー x を格納するとき、異なる 2 つのキー a、b が同じ位置に割り当てられる条件はどれか。
- a − b が n の倍数である
- a + b が n の倍数である
- a と b がともに n の倍数である
- a × b が n の倍数である
正解: ア a mod n = b mod n は、a と b を n で割った余りが等しいこと、すなわち差 a − b が n で割り切れることと同値です。イ・エ は余りの一致とは無関係です。ウ は「n = 13、a = 20、b = 33」のように、どちらも n の倍数でないのに衝突する例があるため誤りです。
Q4. A、B、C をこの順に 1 回ずつスタックにプッシュする。プッシュとポップは任意の順序で行えるとき、ポップによって得られる 3 文字の並びは何通りか。
- 3
- 4
- 5
- 6
正解: ウ ABC・ACB・BAC・BCA・CBA の 5 通りです。エ の 6 通りは 3! をそのまま答えた誤りで、CAB は C を出した時点で A が B の下にあるため作れません。ア・イ は実現できる並びを数え落としています。
Q5. 再入可能(リエントラント)なプログラムの説明として、適切なものはどれか。
- 主記憶上のどのアドレスに配置しても、そのまま実行できる。
- 実行の途中で自分自身を呼び出すことができる。
- 一度実行を終えた後、再ロードしなくてももう一度実行できる。
- 複数のタスクから同時に呼び出されても、それぞれに正しい結果を返す。
正解: エ 再入可能は、手続部とタスクごとのデータ部を分離することで、実行が時間的に重なっても互いに干渉しない性質です。ア は再配置可能、イ は再帰的、ウ は再使用可能の説明で、この 4 つを入れ替えた選択肢が定番の誤答です。
Q6. ポインタで実現した単方向の線形リストの特徴として、適切なものはどれか。
- 任意の要素へ、添字の計算だけで一定時間にアクセスできる。
- 要素の挿入に伴うポインタの書換え回数は、要素数や挿入位置によらず一定である。
- あらかじめ連続領域を確保するため、使われない領域が生じる。
- 末尾の要素の削除は、ポインタを 1 回たどるだけで完了する。
正解: イ 挿入は「新節点の next を決め、直前の節点の next を書き換える」2 本の操作で終わり、要素数にも位置にも依存しません。ア と ウ は配列で実現した場合の特徴です。エ は誤りで、単方向リストの末尾削除は末尾の 1 つ前までたどる必要があり、たどる回数が最も多くなる操作です。
Q7. 「名前と値の組」および「値の順序付きリスト」という 2 種類の構造でデータを表現し、Web API のデータ交換に広く使われている仕様はどれか。
- CSV
- DTD
- JSON
- XML
正解: ウ JSON はオブジェクト(名前と値の組の集まり)と配列(値の順序付きリスト)の 2 構造でデータを表します。ア は行と項目の区切りだけの平坦な形式、イ は XML や SGML の文書構造を定義する文書型定義、エ は独自タグで階層を表すマークアップ言語で、「名前と値の組」という説明には当たりません。
Q8. n 件のデータを m 件ずつのブロックに分け、まず各ブロックの代表値を先頭から線形探索して該当ブロックを決め、次にそのブロック内を線形探索する。目的のデータが必ず存在するとき、平均比較回数を表す式はどれか。
- m ÷ 2 + n ÷ (2m)
- m + n ÷ m
- m ÷ 2 + n ÷ m
- n ÷ (2m)
正解: ア ブロック数は n ÷ m 個なので代表値の線形探索の平均は n ÷ (2m) 回、ブロック内の線形探索の平均は m ÷ 2 回で、2 段階の和になります。イ は両方の ÷ 2 を落とした最悪値、ウ はブロック間の ÷ 2 を落とした式、エ は第 2 段階を数え忘れた式です。
Q9. 非負整数 n の階乗 n! を返す関数 fact(n) の再帰的な定義として、正しいものはどれか。
- n = 0 のとき 0、それ以外のとき n × fact(n − 1)
- n = 0 のとき 1、それ以外のとき n × fact(n − 1)
- n = 0 のとき 1、それ以外のとき n × fact(n)
- n = 1 のとき 0、それ以外のとき n × fact(n − 1)
正解: イ 0! = 1 という停止条件と、引数が 1 ずつ 0 に近づく呼出しの両方がそろっています。ア は停止条件の戻り値が 0 なので結果が常に 0 になります。ウ は引数が減らず停止しません。エ は停止条件の値が誤っているうえ、n = 0 で呼ぶと止まりません。
Q10. 分割統治法を用いている整列アルゴリズムはどれか。
- 挿入ソート
- 選択ソート
- バブルソート
- クイックソート
正解: エ クイックソートは基準値より小さい群と大きい群に分割し、それぞれを独立に再帰で整列してから結合します(マージソートも同じ型です)。ア・イ・ウ はいずれも配列を 1 本のまま走査する二重ループで、分割も再帰も行いません。
Q11. 完全 2 分木を、根を要素番号 1 とし「節点 i の左の子が 2i、右の子が 2i + 1」となるように配列へ格納した。この配列を要素番号の小さい順に調べる処理は、木のどの走査に相当するか。
- 幅優先探索
- 先行順の深さ優先探索
- 中間順の深さ優先探索
- 後行順の深さ優先探索
正解: ア この配列表現では要素番号が「根 → 深さ 1 の節点 → 深さ 2 の節点 …」と浅い順に並ぶので、添字順の走査はレベル順、すなわち幅優先探索です。イ・ウ・エ はいずれも 1 本の枝を葉まで降りてから戻る深さ優先の走査で、根をいつ訪れるかが違うだけです。
Q12. BNF で <英字> ::= A | B | C、<名前> ::= <英字> | <名前><英字> と定義するとき、<名前> として生成できる文字列はどれか。
- AB1
- 1AB
- CAB
- A-B
正解: ウ <名前> は再帰的に定義されており、A・B・C を 1 文字以上並べた列すべてを表します。CAB は C、A、B の順に 3 文字を連ねたもので生成できます。ア と イ は数字の 1、エ はハイフンを含み、<英字> の定義に無い記号なので生成できません。
Q13. 2 分探索木から、子を 2 つ持つ節点を削除する。削除した位置に移す値として適切なものはどれか。
- 削除する節点の左の子の値
- 削除する節点の右の子の値
- 木全体の最小値
- 削除する節点の右部分木の最小値
正解: エ 右部分木の最小値は、中間順で削除対象の次に来る値(後継)なので、そこへ移しても「左部分木 < 節点 < 右部分木」が保たれます。ア・イ は子がさらに子を持つ場合に条件が崩れます。ウ は木全体の最小値なので、削除位置に移すと左部分木に残る値の方が大きくなり、「左部分木 < 節点」の関係が崩れます。
Q14. 「処理 P を実行してから条件 C を判定し、C が真である間は処理 P を繰り返す」という後判定の繰返しがある。これと同じ動作を、前判定の繰返し(条件を判定してから処理を実行する形)だけを使って表したものはどれか。
- 処理 P を 1 回実行した後、「C が真である間、処理 P を繰り返す」前判定の繰返しを置く。
- 「C が真である間、処理 P を繰り返す」前判定の繰返しだけを置く。
- 「C が偽である間、処理 P を繰り返す」前判定の繰返しだけを置く。
- 処理 P を 1 回実行した後、「C が偽である間、処理 P を繰り返す」前判定の繰返しを置く。
正解: ア 後判定は「必ず 1 回は実行する」ので、その 1 回を繰返しの外に出し、残りを前判定の繰返しで書けば等価になります。イ は最初から C が偽のとき実行回数が 1 回ではなく 0 回になり一致しません。ウ・エ は判定条件を反転させており、繰り返す向きが逆になります。
まとめ
この章の得点源は「式」と「対比」に集約されます。次の表の数値と切り分け方を口に出して言えるようになれば、科目 A の目標である 3 問はほぼ確保できます。
| 論点 | 覚える形 | 典型の誤答 |
|---|---|---|
| 配列とリスト | 配列=添字で O(1) アクセス/リスト=挿入削除がポインタ 1〜2 本 | 長所短所の入れ替え。末尾削除が一定時間だと思う |
| スタックの出力順列 | A・B・C なら 5 通り(CAB は不可) | 3! = 6 通り |
| 逆ポーランド記法 | 式の木の後行順。評価は数値プッシュ・演算子で 2 個ポップ | 優先順位無視の並び、減算の被演算子順の逆転 |
| 2 分木の配列表現 | 左の子 2i、右の子 2i + 1、親 i div 2。添字順=幅優先 | 深さ優先・先行順と取り違える |
| 2 分探索木の削除 | 子が 2 個なら右部分木の最小値を昇格 | 直接の子をそのまま繰り上げる |
| 線形探索 | 平均 (n + 1) ÷ 2、最悪 n | 常に n ÷ 2 とする |
| ブロック分割探索 | m ÷ 2 + n ÷ (2m)。最小値は m = √n のとき √n | どちらかの ÷ 2 を落とす |
| 2 分探索 | 最大 ⌈log2(n + 1)⌉ 回。整列済みが前提 | 未整列データに使えると考える |
| ハッシュの衝突 | h(x) = x mod n で a − b が n の倍数 | 和が倍数、各々が倍数 |
| 整列の同定 | バブル=隣接/末尾確定、選択=全体/先頭確定、挿入=差込み | 比較対象と確定位置の取り違え |
| 計算量 | O(n log n)=クイック・マージ・ヒープ(クイックの最悪は O(n2)) | クイックを常に O(n log n) とする |
| 安定な整列 | バブル・挿入・マージが安定 | 選択・クイック・ヒープを安定とする |
| 再帰の定義 | 停止条件+引数が停止条件へ近づく呼出し | 停止条件の戻り値や引数を崩した定義 |
| 分割統治法 | 分割 → 独立に解く → 結合(クイック・マージ・2 分探索) | 動的計画法(解の再利用)と混同する |
| プログラムの 4 性質 | 再入可能=同時、再使用可能=続けて、再配置可能=アドレス非依存 | 4 つの定義の入れ替え |
| 擬似言語 | ← が代入、配列は 1 始まり、for は終端を含む | 0 始まりで div/mod を計算して 1 ずれる |
- 探索・整列で 10 年 17 問と、この中分類の 4 分の 1 を占めます。式(平均比較回数・計算量)から先に固めるのが投資効率が最も高い部分です。
- 木構造は直近 3 年で知識型から操作型(走査・削除・回転)へ移りました。図 3-4 と図 3-5 の動きを自分で描き直せるかで差がつきます。
- 科目 B の問 3 は 19 回連続で固定配置。穴埋め約 58%・計算約 20% で、勝負どころは擬似言語の記法とトレースの精度です。
次の第 4 章では、これらのアルゴリズムを実際に動かすハードウェア側、すなわちコンピュータの構成要素(プロセッサ・記憶階層・入出力)を扱います。計算量が「手数」の話だったのに対し、次章は「1 手あたりの時間」を決める仕組みの話です。