アルゴリズムイントロダクショ
ン 輪講
#1
id:motemen
よろしくお願いします
今日やること
1 - 3章 イントロダクション アルゴリズムの設計・解析 挿入ソート マージソート オーダーの記法 関数の増加教科書の構成
1 - 5章 数学的基礎 6 - 9章 ソート 10 章 - 14 章 データ構造 1巻ここまで 2 巻以降 さらに高度な話題へこの本で学べること
アルゴリズムそのもの
アルゴリズムの設計と解析の技法 データ構造
アルゴリズムを学ぶ理由
計算機が無限の速度を持ち、メモリがただで 手に入っても アルゴリズムが停止するか? 正しい答えが得られるか? 現実に、計算時間・メモリの面で効率の良い アルゴリズムが必要 新たな問題に取り組む足掛かりが欲しい なんかかっこいいアルゴリズムとは
”ある値または値の集合を入力とし、ある値 または値の集合を出力する、明確に定義され た計算手続き” → 疑似コード ”明確に定義された計算問題を解くための 道具” 問題の例: ソーティング問題 入力 n 個の数の列 < a1, a2, . . . , an > 出力 a01 ≤ a02 ≤ · · · ≤ a0n であるよう な入力列の並べ替え < a01, a02, . . . , a0n >最初のアルゴリズム
:
挿入ソート
トランプで手札を並べ替えるときに普通やる 方法
Google: ”insertion sort” site:youtube.com
ソートされていないカードから 1枚ずつ、 ソートされているカードの適切な位置に挿入 していく
挿入ソート
:
疑似コード
procedure Insertion-Sort(A) for j ← 2 to length[A]
do key ← A[j]
i ← j − 1
while i > 0 and A[i] > key do A[i + 1] ← A[i]
i ← i − 1 A[i + 1] ← key 配列は 1 ベース
代入は←
length[A]というのは JS や Ruby の A.length の意 (構造体のメンバへのアクセス)
アルゴリズムができたらすることは?
アルゴリズムの正当性の検証 アルゴリズムは終了するか? アリゴリズムは正しい答えを返すか? アルゴリズムの性能の評価 どのくらいの時間がかかるのか挿入ソート
:
正当性
アルゴリズムの正当性を検証するために、 「ループ不変式」を使用 ループ前・中・後で常に成り立つ条件に注目 することで、ループの意味を検証する 1回目のループで成り立つ n回目のループで成り立つなら、n + 1 回目でも 成り立つ → ループ終了時に成り立つ挿入ソート
:
正当性
アルゴリズムの正当性を検証するために、 「ループ不変式」を使用 ループ前・中・後で常に成り立つ条件に注目 することで、ループの意味を検証する 1回目のループで成り立つ n回目のループで成り立つなら、n + 1 回目でも 成り立つ → ループ終了時に成り立つ不変式が満たすべき
3
つの性質
初期条件 ループの実行開始直前で成り立つ ループ内条件 ループ中のある繰り返しの直前で 成り立つなら、つぎの繰り返しの直前 でも成り立つ 終了時条件 ループが終了したとき、アルゴリズ ムの正当性の証明を手助けする性質が 得られる →不変式: for 文の最初で、部分列 A[1..j − 1] はソートされている不変式が満たすべき
3
つの性質
初期条件 ループの実行開始直前で成り立つ ループ内条件 ループ中のある繰り返しの直前で 成り立つなら、つぎの繰り返しの直前 でも成り立つ 終了時条件 ループが終了したとき、アルゴリズ ムの正当性の証明を手助けする性質が 得られる →不変式: for 文の最初で、部分列 A[1..j − 1] はソートされている不変式が満たすべき
3
つの性質
初期条件 A[1..j − 1] は A[1] だけからなる配列 なのでソート済み ループ内条件 ループ中のある繰り返しの直前で 成り立つなら、つぎの繰り返しの直前 でも成り立つ 終了時条件 ループが終了したとき、アルゴリズ ムの正当性の証明を手助けする性質が 得られる →不変式: for 文の最初で、部分列 A[1..j − 1] はソートされている不変式が満たすべき
3
つの性質
初期条件 A[1..j − 1] は A[1] だけからなる配列 なのでソート済み ループ内条件 A[j] が正しい場所に挿入されてい るので OK (while の解析はここではし ない) 終了時条件 ループが終了したとき、アルゴリズ ムの正当性の証明を手助けする性質が 得られる →不変式: for 文の最初で、部分列 A[1..j − 1] はソートされている不変式が満たすべき
3
つの性質
初期条件 A[1..j − 1] は A[1] だけからなる配列 なのでソート済み ループ内条件 A[j] が正しい場所に挿入されてい るので OK (while の解析はここではし ない) 終了時条件 A[1..j − 1] は A 全体なので配列は ソートできた → アルゴリズムは正 しい →不変式: for 文の最初で、部分列 A[1..j − 1] はソートされている不変式が満たすべき
3
つの性質
初期条件 A[1..j − 1] は A[1] だけからなる配列 なのでソート済み ループ内条件 A[j] が正しい場所に挿入されてい るので OK (while の解析はここではし ない) 終了時条件 A[1..j − 1] は A 全体なので配列は ソートできた → アルゴリズムは正 しい →不変式: for 文の最初で、部分列 A[1..j − 1] はソートされているアルゴリズムの実行にかかる時間
実行にかかる時間は… 入力に依存 始めからソートされていると早い 逆順にソートされていると一番遅い → 通常、最悪の場合を考慮 入力のサイズに依存 → 実行にかかる時間を入力のサイズの関数 T (n)で表すInsertion-Sort
の実行時間を計算
各ステップの実行回数を計算 (n = length[A]) for j ← 2 to length[A] n
do key ← A[j] n− 1
i ← j − 1 n− 1
while i > 0 and A[i] > key ∑nj=2tj do A[i + 1] ← A[i] ∑nj=2(tj − 1) i ← i − 1 ∑nj=2(tj − 1) A[i + 1]← key n− 1 tj というのは挿入する場所を探す回数 (ループ毎 に別々) ループの判定文は本体より 1 回多く実行される
Insertion-Sort
の実行時間を計算
各行の実行コストに、それぞれの実行回数を 掛けたものの和を計算 もともとソート済みのとき (最良) while は 1 回しか実行されない: tj = 1 T (n) = an + b の形になる 逆順にソートされていたとき (最悪) while が i = 0 になるまで実行される: tj = j T (n) = an2 + bn + c の形になるInsertion-Sort
の実行時間を計算
各行の実行コストに、それぞれの実行回数を 掛けたものの和を計算 もともとソート済みのとき (最良) while は 1 回しか実行されない: tj = 1 T (n) = an + b の形になる 逆順にソートされていたとき (最悪) while が i = 0 になるまで実行される: tj = j T (n) = an2 + bn + c の形になるInsertion-Sort
の実行時間を計算
各行の実行コストに、それぞれの実行回数を 掛けたものの和を計算 もともとソート済みのとき (最良) while は 1 回しか実行されない: tj = 1 T (n) = an + b の形になる 逆順にソートされていたとき (最悪) while が i = 0 になるまで実行される: tj = j T (n) = an2+ bn + c の形になるアルゴリズムの実行時間の評価
本当に興味があるのは、実行時間の「増加の オーダー」 (増加率) なので、主要でない項を削ぎ落とす 挿入ソートの最悪実行時間は T (n) = an2+ bn + c 挿入ソートの最良実行時間は T (n) = an + b Θ については後でアルゴリズムの実行時間の評価
本当に興味があるのは、実行時間の「増加の オーダー」 (増加率) なので、主要でない項を削ぎ落とす 挿入ソートの最悪実行時間は T (n) = an2+ bn + c 挿入ソートの最良実行時間は T (n) = an + b Θ については後でアルゴリズムの実行時間の評価
本当に興味があるのは、実行時間の「増加の オーダー」 (増加率) なので、主要でない項を削ぎ落とす 挿入ソートの最悪実行時間は T (n) = an2+ bn + c = Θ(n2) 挿入ソートの最良実行時間は T (n) = an + b = Θ(n) Θ については後でアルゴリズムの実行時間の評価
本当に興味があるのは、実行時間の「増加の オーダー」 (増加率) なので、主要でない項を削ぎ落とす 挿入ソートの最悪実行時間は T (n) = an2+ bn + c = Θ(n2) 挿入ソートの最良実行時間は T (n) = an + b = Θ(n) Θ については後で第
2
のアルゴリズム
:
マージソート
マージすべき配列を 2つに分け、それぞれに マージソートをかけた後マージする
Google: ”merge sort” site:youtube.com
分割統治法 (divide-and-conquer)
ちなみに挿入ソートは 逐次添加法 (incremental approach)
分割統治法
問題を部分問題に分解し、その解を組み合わ せる
再帰的なアルゴリズムの多くが分割統治法に 基づいている
分割統治の
3
段階
分割 問題をいくつかの部分問題に分割する 統治 部分問題を再帰的に解く ただし、部分問題のサイズが十分小さ い場合は直接的な方法で解く 結合 部分問題の解を組み合わせて解を得るマージソートでの各段階
分割 入力の配列を半分ずつに分割する 統治 マージソートを用いて部分列を再帰的 にソートする ただし、入力の長さが 1 であれば何も しない 結合 ソートされた部分列をマージすること で解を得るマージ
2つのソートされた配列をマージして 1つの ソートされた配列にする 「2つの配列の先頭から小さい方を取る」を 繰り返す: Θ(n) その際、配列の最後に ∞ を追加しておくと便利 (番兵)マージ
2つのソートされた配列をマージして 1つの ソートされた配列にする 「2つの配列の先頭から小さい方を取る」を 繰り返す: Θ(n) その際、配列の最後に ∞ を追加しておくと便利 (番兵)マージ
2つのソートされた配列をマージして 1つの ソートされた配列にする 「2つの配列の先頭から小さい方を取る」を 繰り返す: Θ(n) その際、配列の最後に ∞ を追加しておくと便利 (番兵)マージ
(
疑似コード
)
procedure Merge(A, p, q, r) n1 ← q − p + 1 n2 ← r − q for i← 1 to n1 do L[i]← A[p + i − 1] for j← 1 to n2 do R[j]← A[q + j] L[n1+ 1]← ∞ R[n2+ 1]← ∞ i← 1 j ← 1 for k← p to r do if L[i]≤ R[j]then A[k]← L[i]
i← i + 1
else A[k]← R[j]
手続き
Merge
の正当性
ループ不変式を以下のようにする A[p..k− 1] には、L と R の要素中で小さい方 から k− p 個がソートされて入っている L[i] と R[j] は、L と R でまだ A に書き戻さ れていない要素のなかでそれぞれ最小マージソート
Merge-Sort(A, p, r) 先の Merge 手続きを利用して A[p..r] を ソート p≤ r ならソート済み (0 か 1 要素) なので何も しない 分割: dn/2e 要素の A[p..q] と bn/2c 要素の A[q + 1..r]に分割マージソート
Merge-Sort(A, p, r) 先の Merge 手続きを利用して A[p..r] を ソート p≤ r ならソート済み (0 か 1 要素) なので何も しない 分割: dn/2e 要素の A[p..q] と bn/2c 要素の A[q + 1..r]に分割マージソート
Merge-Sort(A, p, r) 先の Merge 手続きを利用して A[p..r] を ソート p≤ r ならソート済み (0 か 1 要素) なので何も しない 分割: dn/2e 要素の A[p..q] と bn/2c 要素の A[q + 1..r]に分割マージソート
(
疑似コード
)
配列 A の添字 p から r までをソートする procedure Merge-Sort(A, p, r) if p < r then q ← b(p + r)/2c Merge-Sort(A, p, q) Merge-Sort(A, q + 1, r) Merge(A, p, q, r)分割統治アルゴリズムの実行時間
実行時間を T (n) として… 問題のサイズ n が小さいとき 定数時間しかかからない (何もしない) T (n) = Θ(1) 問題を a 個の部分問題に分割し、サイズを 1/b にしたとき T (n) = aT (n/b) + D(n) + C(n) D(n): 分割にかかる時間 C(n): 結合にかかる時間マージソートの実行時間
分割 bn/2c を計算するだけなので D(n) = Θ(1) 統治 問題を 2 個に分割し、サイズが 1/2 に なるので a = b = 2, 2T (n/2) 結合 Merge には Θ(n) 時間かかるので C(n) = Θ(n) なので T (n) = { Θ(1) if n = 1 2T (n/2) + Θ(1) + Θ(n) if n > 1マージソートの実行時間
マージソートの実行時間
T(n)
マージソートの実行時間
T(n/2) cn
マージソートの実行時間
T(n/2) cn
T(n/2)
マージソートの実行時間
cn/2 cn cn/2 T(n/4) T(n/4) T(n/4) T(n/4)マージソートの実行時間
cn/2 cn cn/2 T(n/4) T(n/4) T(n/4) T(n/4) T (n/4) = 2T (n/8) + cn/4, . . .マージソートの実行時間
cn/2 cn cn/2 T(n/4) T(n/4) T(n/4) T(n/4) c c c cマージソートの実行時間
cn/2 cn cn/2 × 2 = cn cn/2 cn/4 cn/4 cn/4 × 4 = cn cn/4 cn/4 c c c × n = cn c cマージソートの実行時間
cn/2 cn cn/2 × 2 = cn cn/2 cn/4 cn/4 cn/4 × 4 = cn cn/4 cn/4 c c c × n = cn c c 各段階が cn, 木の深さは log2n なので T (n) = cn log2nlog
2n
って
2log2n = n 大雑把に言うと「n を表すのに必要なビッ ト数」 n に対して増え方が遅い log2256 = 8 log265536 = 16 log24294967296 = 32 → マージソートは挿入ソートより高速log
2n
って
2log2n = n 大雑把に言うと「n を表すのに必要なビッ ト数」 n に対して増え方が遅い log2256 = 8 log265536 = 16 log24294967296 = 32 → マージソートは挿入ソートより高速log
2n
って
2log2n = n 大雑把に言うと「n を表すのに必要なビッ ト数」 n に対して増え方が遅い log2256 = 8 log265536 = 16 log24294967296 = 32 → マージソートは挿入ソートより高速関数の増加のオーダーを測る
アルゴリズムの実行時間 T (n) の評価 nが増加したときにどのくらいのスピードで増 加するかに興味がある 定数部分には興味がない → アルゴリズムの「漸近的」効率を調べる 漸近記号 Θ, O, Ω, . . . の導入関数の増加のオーダーを測る
アルゴリズムの実行時間 T (n) の評価 nが増加したときにどのくらいのスピードで増 加するかに興味がある 定数部分には興味がない → アルゴリズムの「漸近的」効率を調べる 漸近記号 Θ, O, Ω, . . . の導入関数の増加のオーダーを測る
アルゴリズムの実行時間 T (n) の評価 nが増加したときにどのくらいのスピードで増 加するかに興味がある 定数部分には興味がない → アルゴリズムの「漸近的」効率を調べる 漸近記号 Θ, O, Ω, . . . の導入Θ-
記法
f (n) = Θ(g(n)) ⇐⇒ すべての n ≥ n0に対して 0 ≤ c1g(n)≤ f(n) ≤ c2g(n) であるような正の定数 c1, c2および n0が存在する 関数の漸近的な増え方を (上下から) 押さ える 挿入ソートの実行時間は Θ(n2) ではない 最悪時の実行時間は Θ(n2) ここでの = の使い方は 濫用 なので注意O-
記法
f (n) = O(g(n)) ⇐⇒ すべての n ≥ n0に対して 0≤ f(n) ≤ cg(n) であるような正の定数 c および n0が存在する 関数の漸近的な増え方を (上から) 押さえる 挿入ソートの実行時間は O(n2)Ω-
記法
f (n) = Ω(g(n)) ⇐⇒ すべての n ≥ n0に対して 0≤ cg(n) ≤ f(n) であるような正の定数 c および n0が存在する 関数の漸近的な増え方を (下から) 押さえるo-
記法、
ω-
記法
f (n) = o(g(n)) ⇐⇒ 任意の定数 c > 0 に対してある定数 n0 > 0が存在して、 すべての n ≥ n0に対して 0≤ f(n) ≤ cg(n) である 「漸近的にタイトでない」上界 ω はこの反対関数の増え方
log(x) vs x vs x2 vs x3 vs 2x 0..5 0 20 40 60 80 100 120 140 1 1.5 2 2.5 3 3.5 4 4.5 5 log(x) x x*x x*x*x 2**x関数の増え方
log(x) vs x vs x2 vs x3 vs 2x 10..20 0 200000 400000 600000 800000 1e+06 1.2e+06 6 8 10 12 14 16 18 20 log(x) x x*x x*x*x 2**x関数の増え方
log(x) vs x vs x2 vs x3 vs 2x 10..20 0 200000 400000 600000 800000 1e+06 1.2e+06 6 8 10 12 14 16 18 20 log(x) x x*x x*x*x 2**x 指数関数ヤバイオーダーに対する簡単な感覚
O(n!) >> O(kn) >> (越えられない壁) >> O(nk) > . . .
O(n3) > O(n2) > O(n log n) > O(n) > O(log n) >