第10回:ヒープ + グラフとその表現方法¶
前回までで、平衡二分探索木(AVL)なら、どんな順で入れても検索も追加も最悪 O(log n) を守れることを見た。「全部きれいに並んだ状態」を常に保つから、好きな順に取り出せる。
でも、欲しいのが「いちばん小さい 1 個」だけのとき、わざわざ全体を整列して保つのはやりすぎな場面がある。
今日の問い: 常に「いちばん小さい 1 個」を最速で取り出したい。例えばタスク管理(締切が近いものから処理する)。新しいデータもどんどん入ってくる(追加も頻繁)。全部を並べ替えずに、取り出しも追加も速い入れ物は作れるか?
ヒープのルール(親 ≤ 子)¶
全部をきれいに並べる必要はない。保証するのは「てっぺんが必ず最小」だけ。そのための約束:
- どの節も 親 ≤ 自分の子(兄弟同士は比べない)
- 親より小さい子がいない → だから根(てっぺん)が必ず最小
二分探索木の「左の子孫 < 自分 < 右の子孫(全部)」より、ずっと弱いルール。弱い = 守るのが簡単。
形のルール¶
木は 上から、左から、びっしり詰める(=完全二分木)。途中を飛ばさない。だから高さは必ず ≈ log n。
graph TD
A((1)) --> B((3))
A --> C((2))
B --> D((7))
B --> E((4))
C --> F((5))
この木は親 ≤ 子を全部の節で満たしている。根の 1 が最小。
演習10-1¶
下の木はヒープのルール(親 ≤ 子)を満たすか? 満たさないなら、違反している親子をすべて挙げよ。さらに、満たすための最短の入れ替え操作を実施せよ。
graph TD
A((2)) --> B((4))
A --> C((1))
B --> D((5))
B --> E((3))
考えるヒント
各親子について「親 ≤ 子」が成り立つかをひと組ずつ確認する。違反している組を見つけたら、その親子を入れ替えると何が起きるかをイメージする。
追加(push)¶
新しい値は、左詰めの次の場所(=完全二分木を保つ位置)に置く。そこから:
- 親と比べる
- 親より小さければ入れ替えて 1 段上へ(浮上)
- 親より大きくなったら止まる
動くのは根までの道のりだけ = 高さ分 = O(log n)。
取り出し(pop)¶
根が答え(最小)。空いた根に末尾の値を持ってくる。そこから:
- 小さい方の子と比べる
- 子より大きければ入れ替えて 1 段下へ(沈降)
- 子より小さくなったら止まる
やはり高さ分 = O(log n)。
演習10-2¶
次の配列は最小値ヒープではない(左詰めで木に並べたと思って読む)。それぞれ、たった 1 組だけ入れ替えて最小値ヒープにできるか? できるならどこを、できないなら理由を一言。
- (a)
[1, 5, 2, 7, 4] - (b)
[9, 1, 2, 3, 4, 5, 6]
考えるヒント
まずそれぞれを木として描いてみよう(添字 0 が根、左から順に番号を振る)。違反している親子はいくつあるか? 1 組の入れ替えで全部直せるか?
何を捨てて速いのか(対決)¶
ヒープは「任意のキー検索」を捨てている(そこは苦手)。代わりに「最小の取り出し」と「追加」だけを、両方 O(log n) でこなす。
| 最小を取り出す | 追加 | 任意のキーで検索 | 全体を整列 | |
|---|---|---|---|---|
| ソート済み配列 | O(1)(先頭) | O(n)(ずらす) | O(log n)(二分探索) | 済み |
| 平衡二分探索木 | O(log n)(最左) | O(log n) | O(log n) | O(n) |
| ヒープ | O(log n)(見るだけなら O(1)) | O(log n) | O(n)(整列なし) | O(n log n) |
速さの正体は「全部並べないこと」¶
- 二分探索木/AVL: 通読すれば整列が出てくる = 全順序(すべての要素に上下がついている)
- ヒープ: 親 ≤ 子 だけ、兄弟は無順序 = 部分順序(一部にしか上下がない)
全部を並べる仕事をしないから速い。これが今日の問いの答え。第9回「全部きれいに保つ」の、ちょうど裏返し。
添字だけで親子が決まる¶
完全二分木(隙間なし)なら、上から左へ番号を振るだけで木が表せる。ポインタも枝も要らない。配列の添字で親子が分かる(0 始まり):
- 節
iの 左の子 = 2i + 1 - 右の子 = 2i + 2
- 親 = (i − 1) // 2
// は整数除算
// は整数除算(商を求める)。5 // 2 は 2、(4 - 1) // 2 は 1。
木と配列の対応¶
先ほどの形のルールで描いた木(値 [1, 3, 2, 7, 4, 5])を例に:
- 添字 4(値
4)の親 =(4 − 1) // 2= 添字 1(値3)。図と一致 - 添字 1(値
3)の子 =2·1 + 1, 2·1 + 2= 添字 3, 4(値7, 4) - 葉(子を持たない節)は添字
⌊n/2⌋以降(この例n = 6なら添字 3 以降)
なぜ配列だと得か¶
- 隙間がない → 配列が詰まって連続(メモリの無駄ゼロ)
- 上下移動が足し算・割り算だけ(ポインタを辿らない)
- BST/AVL は節をポインタで繋ぐ必要があった。ヒープは要らない
優先度付きキュー¶
ヒープの正体は「次に処理すべき 1 個を、最速で出す」入れ物。並んだ順ではなく、優先度の高い(小さい)順に出てくるキュー。
- 救急のトリアージ: 来た順でなく重症度の高い人から
- 仕事の山: 締切が近いタスクから取り出す
第4回の FIFO キュー(先入れ先出し)の「優先度版」。中身がヒープになっている。
ヒープソート と、この先¶
全部ヒープに入れて最小を 1 個ずつ取り出すと、出る順が整列になる(=ソート)。
- 取り出し O(log n) × n 回 = O(n log n)
そして最短経路探索の心臓部:
- 「いまいちばん近い点」を取り出して確定、を繰り返す
- = まさに優先度付きキュー
ヒープ(優先度付きキュー)は「次に確定すべき 1 個」を最速で出す。これは最短経路(駅から駅、第12回)の心臓部になる。
でも、その前に問題が 1 つ。「駅と線路のつながり(路線図)」を、どうコンピュータに覚えさせる?
グラフとは¶
点(頂点 / node) と、点どうしを結ぶ 線(辺 / edge) だけでできる図。「つながり」を表す道具:
- 路線図、地図の交差点、配管、作業の依存関係、ソーシャルネットワーク……
木で出てきた「根・節・葉」も、もとはこの点と辺の話だった。
グラフで表現・問題解決できる例¶
- 乗り換え案内の最短経路(各ノード間は x 分……)
- 運転見合わせ時の迂回経路(上りのみ停止……)
路線図はそのまま、駅=頂点/線路=辺のグラフとして扱える。各辺に「所要時間」や「料金」を載せれば、最短経路や最安経路の問題になる。
グラフの種類(2 つの軸)¶
向きがあるか:
- 無向: 双方向(線路・通路)
- 有向: 一方向(一方通行の道・「A は B の前提科目」)
重みがあるか:
- 重みなし: つながっているかだけ
- 重みあり: 辺に距離・時間・料金などの数がつく(最短経路で使われる)
木は、グラフの特殊例¶
木(第8〜9回)も、実はグラフの一種。制約がついているだけ:
- どの節も親は 1 つ
- そして輪(ループ)がない
グラフはこの制約を外した、もっと自由な「つながり」。
次数(degree)¶
ある点に辺が何本ついているか = その点の 次数。
- 有向グラフでは、入る本数(入次数) と 出る本数(出次数) を分けて数える
あとで「どの点から手をつけるか」を考えるときに効いてくる。
どうやって覚えさせるか¶
つながり(グラフ)をコンピュータに持たせる方法は、大きく 2 通り。
表現①:隣接行列¶
n × n の表。点 i と点 j がつながっていれば 1(重みありならその値)、なければ 0。
- 無向グラフなら左右対称(
i→jとj→iが同じ) - 物理アナロジー: クラスの総当たり表
- 長所: 2 点がつながっているか一発で分かる(1 マス見るだけ)
- 短所: マスが
n²個。線が少ない(疎な)グラフだと 0 だらけで無駄
隣接行列の例¶
- 行(or 列)の
1の数 = その点の次数。Dの行は1が 3 つ → 次数 3 - 有向なら対称が崩れ、重みありなら
1のかわりに距離が入る
表現②:隣接リスト¶
各点ごとに「つながっている点のリスト」だけ持つ。
- 物理アナロジー: 連絡網(自分の連絡先だけを各自が持つ)
- 長所: 線が少ない(疎)なら省メモリ。ある点の隣を全部見るのが速い
- 短所: 2 点がつながっているかの確認は、リストをたどる必要がある
隣接リストの例¶
- リストの長さ = その点の次数
- 重みありなら各項目に「相手と距離」を持たせる(例:
D : (B,4), (C,2), (E,7))
どちらを選ぶ(疎か密か)¶
辺の数を e、点の数を n とすると、辺は最大で約 n² / 2 本まで。
- 密(
eがn²に近い)→ 隣接行列(どうせ埋まる) - 疎(
eがnよりちょっと多い程度)→ 隣接リスト(0 を持たずに済む)
地図・路線図・配管はたいてい疎(1 点に辺は数本)→ 隣接リストが標準。
演習10-3¶
次の隣接行列で表されるグラフについて答えよ。
- (a) これを隣接リストに書き直せ
- (b) このグラフの辺は何本か(数え方も一言)
- (c) 次数がいちばん高い点はどれか
考えるヒント
(b) 行の 1 を全部数えると、無向グラフでは 1 本の辺が i→j と j→i の 2 回数えられている。何で割ればいい?
演習10-4¶
次の状況をグラフで表したい。それぞれ 〔頂点は何か/辺は何か/向きは要るか/重みは要るか〕 を決めよ。
- (a) パーティで握手した二人をリストアップ
- (b) 大学構内の建物と通路。建物間の徒歩の分数を気にする
- (c) 履修の前提条件(科目 A を取らないと科目 B が取れない、など)
- (d) 一方通行が混じる道路網。目的地まで最短時間を知りたい
考えるヒント
- 「行き来できるか」が片方向だけ/双方向かで、向きの有無が決まる
- 「単につながっているか知りたい」のか「距離・時間・コストも知りたい」のかで、重みの有無が決まる
演習10-5¶
下のグラフ(無向)について答える。まず図に描くとやりやすい。
- (a)
AからFへ行けるか? 行けるなら、通る点を並べて経路を 1 つ書け - (b)
AからFへ、最短で何本の辺をたどるか? - (c) 同じ点を 2 度通らずに一周して戻る道(閉路)はあるか? あれば 1 つ書け
考えるヒント
- 隣接リストをそのまま図に起こす(頂点 6 つ、辺は左の関係をたどって 1 本ずつ)
- (b) は (a) で見つけた経路がそのまま最短とは限らない。本数を数えて比較する
- (c) は「同じ点を 2 度通らない」「最初の点に戻る」両方が必要
まとめ¶
- ヒープ: 親 ≤ 子。根が必ず最小。push も pop も O(log n)
- 配列の添字だけで木を表せる・ポインタ不要
- 速さの正体は全部を並べないこと(部分順序)。任意検索は捨てて、最小取り出し+追加に尖る
- 優先度付きキュー
- 「次の 1 個」を最速で(キューの優先度版)
- ヒープソートは O(n log n)
- グラフ: 点と辺で「つながり」を表す
- 有向/無向・重み・次数
- 持ち方は隣接行列(密)/隣接リスト(疎)
- 何を頂点・辺にするかは問題が決める
次回予告¶
今日:つながり(グラフ)を、行列やリストで覚えさせるところまで来た。でもまだ「覚えた」だけ。次はその上を実際に歩いて、点から点へ探しに行く。
- ある駅から別の駅へ、どうやってたどり着ける?(迷路の出口を見つける、と同じ)
探し方は大きく 2 通り:
- 手前から順に、近いところから輪を広げる(BFS: 幅優先探索)
- 一本道を奥までしらみ潰しに進み、行き止まりで戻る(DFS: 深さ優先探索)
探し方が違うと、見つかる順番が変わる。「つながっているか(連結)」「輪があるか(閉路)」も、この探索で分かる。
次回:グラフ探索(DFS・BFS)。