情報工学概論 (アルゴリズムとデータ構造) 第3回 今までのソーティングアルゴリズム 1. 挿入ソート: (n2) 時間だが,入力サイズが 小さいときには高速.in-place 2. マージソート: (n lg n) 時間だが,実行に は一時的な配列が必要 2 新しいソーティングアルゴリズム 1. ヒープソート: (n lg n) 時間, in-place. 2. クイックソート: 最悪 (n2) 時間だが平均実行 時間は(n lg n).実際上は高速.in-place. 3 クイックソート • n 個の数に対して最悪実行時間(n2)のソー ティングアルゴリズム • 平均実行時間は(n log n) • 記法に隠された定数も小さい • in-place (一時的な配列が必要ない) 4 クイックソートの記述 • 分割統治法に基づく • 部分配列 A[p..r] のソーティング 1. 部分問題への分割: – 配列 A[p..r] を2つの部分配列 A[p..q] と A[q+1..r] に再配置する. – A[p..q] のどの要素も A[q+1..r] の要素以下にする. – 添字 q はこの分割手続きの中で計算する. 2. 部分問題の解決 (統治): • 部分配列 A[p..q] と A[q+1..r] を再帰的にソート 3. 部分問題の統合 • A[p..r] はすでにソートされているので何もしない 5 クイックソートのコード void QUICKSORT(data *A, int p, int r) { int q; if (p < r) { q = PARTITION(A,p,r); QUICKSORT(A,p,q); QUICKSORT(A,q+1,r); } } main() { QUICKSORT(A,0,n-1); } 6 注:これはin-placeではない def quick_sort(a) (一時的に余分な記憶領域が必要) n = a.length if n <= 1 then return a else pivot = a[0] less = a.select{|x| x < pivot} equal = a.select{|x| x == pivot} greater = a.select{|x| x > pivot} return quick_sort(less) + equal + quick_sort(greater) end end a = [27,17,3,16,13,10,1,5,7,12,4,8,9,0] b = quick_sort(a) pb 7 配列の分割 int PARTITION(data *A, int p, int r) // O(n) 時間 { int q, i, j; data x, tmp; x = A[p]; i = p-1; j = r+1; while (1) { do {j = j-1;} while (A[j] > x); do {i = i+1;} while (A[i] < x); if (i < j) { tmp = A[i]; A[i] = A[j]; A[j] = tmp; } else { return j; } } } 8 A[p..r] 5 3 2 6 4 1 3 7 i j 5 3 2 6 4 1 3 7 i A[i] x A[j] x となる最初の i, j 7 は正しい分割位置にある j 3 3 2 6 4 1 5 7 i 初期状態: i と j は配列の範囲外 x = A[p] = 5 によって分割 x: 枢軸 (pivot) と呼ぶ j A[i] と A[j] を交換 正しい分割位置になる 9 3 3 2 6 4 1 5 7 i j 3 3 2 1 4 6 5 7 i A[i] x A[j] x となる最初の i, j A[i] と A[j] を交換 j 3 3 2 1 4 6 5 7 j i i j となったので q = j を返す A[p..q] は x 以下 A[q+1..r] は x 以上 10 PARTITIONの正当性 • PARTITIONの返り値を q とする • A[p..r] の分割後に満たされるべき条件 – A[p..q] はある pivot x 以下 – A[q+1..r] は x 以上 –pq<r • q = r となると,QUICKSORTは停止しないため, どんな A に対しても q < r となることを保障する 必要がある 11 • 初期状態は i < j • do {j = j-1;} while (A[j] > x); do {i = i+1;} while (A[i] < x); の終了後 – p i, j r – A[j+1..r] は x 以上 – A[p..i-1] は x 以下 – A[i] x A[j] • i < j のとき,A[i] と A[j] を交換すると – A[j..r] は x 以上 – A[p..i] は x 以下 • i j のとき q = j 12 • PARTITIONの終了時に q = j = r とする – while (1) のループを実行する毎に j は1以上減る – 終了時に j = r ならばこのループは1度しか実行され ていない – しかし1回目のループでは x = A[p] なので i = p • PARTITIONは p < r の場合のみ呼ばれるので, 1回目のループでは p = i < j = r • つまり1回目のループでは終了しない • よってPARTITION終了時に q = r とはならない. つまり q < r 13 8.2 クイックソートの性能 • クイックソートの実行時間は分割が平均化さ れているかどうかに依存 • これはpivotの選択に依存 • 分割が平均化されていれば実行時間は漸近 的にマージソートと同じ ((n log n)) • 最悪時は (n2) 14 最悪の分割 • 最悪の場合は,PARTITIONによって領域が 1 要素と n-1 要素に分けられる場合 • 分割には (n) 時間かかる • 実行時間に対する漸化式は – T(n) = T(n1) + (n), T(1) = (1) • T(n) = (n2) • 最悪実行時間は挿入ソートと同じ • 入力が完全にソートされているときに起こる (挿入ソートなら O(n) 時間) 15 n n n 1 1 n 1 n2 1 1 n n n2 n 3 3 2 2 1 再帰木 1 Total : n 2 16 最良の分割 • クイックソートが最も速く動作するのは, PARTITIONによってサイズ n/2 の2つの領域 に分割されるとき. • T(n) = 2T(n/2) + (n) • T(n) = (n lg n) • ちょうど半分に分割される場合が最速 17 n n n 2 n 2 n 4 n 4 n 4 n n 4 n lg n n n n n n n n n 8 8 8 8 8 8 8 8 n n Total : nlg n 18 平衡分割 • PARTITIONで常に 9 対 1 の比で分割されると 仮定する • T(n) = T(9n/10) + T(n/10) + (n) • 再帰木の各レベルのコストは O(n) • 再帰の深さは log 109 n lg n • 漸近的には中央で分割するのと同じ • 分割の比が 99 対 1 でも同じ (定数比なら良 い) 19 8.3 クイックソートの 確率的アルゴリズム • クイックソートの平均的な場合の実行時間を解析 する場合,入力の頻度を仮定する必要がある. • 通常は,すべての順列が等確率で現れると仮定 • しかし実際にはこの仮定は必ずしも期待できない • この仮定が成り立たなくてもうまく動作するクイック ソートの確率的アルゴリズムを示す 20 確率的 (randomized) アルゴリズム • 動作が入力だけでなく乱数発生器 (randomnumber generator)に依存するアルゴリズム • 関数 RANDOM(a,b): a 以上 b 以下の整数を 等確率で返すとする. • プログラミング言語は擬似乱数発生器 (pseudorandom-number generator) を備え る • 擬似乱数: 統計的にはランダムに見えるが, 決定的に作られる数(の列) 21 確率的アルゴリズム1 • クイックソートを行う前に入力配列の要素をラン ダムに並び替える • 実行時間は入力順序に依存しなくなる • アルゴリズムがうまく動作しないのは,乱数発生 器によって運の悪い順列を作る場合のみ • 最悪の実行時間は改善されない ((n2)) • 最悪の場合はほとんど起きない 22 確率的アルゴリズム2 • 配列を A[p..r] を分割する前に, A[p] と A[p..r] からランダムに選んだ要素を交換 • pivotが rp+1 個の要素から等確率で選ばれ ることを保障する • 分割が平均的にはバランスのとれたものにな ることが期待できる 23 int RANDOMIZED_PARTITION(data *A, int p, int r) { int i; data tmp; i = RANDOM(p,r); tmp = A[i]; A[i] = A[p]; A[p] = tmp; // 値の交換 return PARTITION(A,p,r); } void RANDOMIZED_QUICKSORT(data *A, int p, int r) { int q; if (p < r) { q = RANDOMIZED_PARTITION(A,p,r); RANDOMIZED_QUICKSORT(A,p,q); RANDOMIZED_QUICKSORT(A,q+1,r); } } 24 8.4.1 最悪の場合の解析 • T(n): サイズ n の入力に対するQUICKSORT の最悪実行時間 T (n) max T (q) T (n q) (n) 1 q n 1 • T(n) = O(n2) を示す • m < n に対し T(m) cm2 と仮定 25 T (n) max T (q) T (n q ) (n) 1 q n 1 c max q (n q) (n) 2 2 1 q n 1 c 1 (n 1) (n) 2 2 cn 2c(n 1) (n) 2 cn 2 c は 2c(n1) が (n) の項よりも大きくなるように 十分大きくとる よって T(n) = O(n2) が示された 26 8.4.2 平均的な場合の解析 • T(n): サイズ n の入力に対するRANDOMIZED QUICKSORTの平均実行時間 • T(n) に関する漸化式を解く • 入力データはすべて異なる数とする 27 分割の解析 • RANDOMIZED_PARTITIONでPARTITIONが呼 ばれるとき,A[p] の要素は A[p..r] のランダムな 要素と置き換えられている. • 観察:PARTITIONによって返される q の値は A[p..r] の中での x = A[p] のランクのみに依存 • x のランク rank(x) = 集合中の x 以下の要素数 • 要素数 n = rp+1 • rank(x) = i となる確率は 1/n 28 • rank(x) = 1 のとき,PARTITIONでのループは i = j = p で終了 • このとき q = j = p つまり分割の小さい方のサ イズは 1.この事象が起きる確率は 1/n • rank(x) 2 のとき,x = A[p] より小さい値が少 なくとも1つ存在 • PARTITIONでの最初のループ実行後は i = p,j > p • A[i] と A[j] を入れ替えるため,x = A[p] は右 の分割に入る 29 • PARTITIONが停止したとき,左の分割には rank(x)1 個の要素があり,それらは x より小さい • rank(x) 2 のとき,左の分割のサイズが i である 確率は 1/n (i = 1,2,...,n-1) • rank(x) が1の場合と2以上の場合を合わせると, • 左の分割のサイズ rp+1 が – 1 になる確率: 2/n – i になる確率: 1/n (i = 2,3,...,n-1) 30 平均時に対する漸化式 • T(n): n 要素の入力配列をソートするのに必要 な平均時間 • T(1) = (1) • 長さ n の配列に対してソートする場合 – 配列の分割: (n) 時間 – 統治: 長さ q と nq の部分配列を再帰的にソート n 1 1 T (n) T (1) T (n 1) (T (q) T (n q)) (n) n q 1 31 T(1)=(1), T(n1) = O(n2) より 1 1 T (1) T (n 1) (1) O(n 2 ) n n O(n) よって T(n) は次のように書ける 1 n 1 T (n) (T (q ) T (n q )) (n) n q 1 2 n 1 T ( k ) ( n ) n k 1 32 m < n に対し T(m) am lg m + b (a>0, b>0) と仮定 2 n 1 T ( n ) T ( k ) ( n ) n k 1 2 n 1 (ak lg k b) (n) n k 1 2a n 1 2b k lg k (n 1) (n) n k 1 n n 1 1 2 1 2 k lg k n lg n n を用いる 2 8 k 1 a n が (n)+b 以上になるように a を選ぶと 4 33 2a 1 2 1 2 2b T ( n) n lg n n (n 1) (n) n 2 8 n a an lg n n 2b (n) 4 a an lg n b (n) b n 4 an lg n b T(n) においても成り立つ よってクイックソートの平均実行時間は O(n lg n) 34 n 1 1 2 1 2 k lg k n lg n n の証明 2 8 k 1 n 1 n / 2 1 n 1 k 1 k 1 k n / 2 k lg k k lg k k lg k n / 2 1 k 1 n 1 n 1 n / 2 1 n k lg k lg n (lg n 1) k lg n k 2 k n / 2 k 1 k n / 2 n 1 n / 2 1 k 1 k 1 lg n k k 1 1n n n(n 1) lg n 1 2 22 2 1 2 1 2 n lg n n 2 8 35 9.1 ソーティングの下界 • ソーティングの入力: 〈a1, a2, ..., an〉 • 比較ソートでは要素間の比較のみを用いて ソートを行う • 2つの要素 ai, aj が与えられたとき,それらの相 対的な順序を決定するためにテストを行う – ai aj, ai aj, ai aj, ai aj, ai aj のみ • これ以外の方法では要素のテストはできない 36 仮定 • すべての入力要素は異なると仮定する – ai aj という比較は行わないと仮定できる • ai aj, ai aj, ai aj, ai aj は全て等価 • 全ての比較は ai aj という形と仮定できる 37 決定木モデル • 比較ソートは決定木 (decision tree) とみなせる • 決定木はソーティングアルゴリズムで実行される 比較を表現している • アルゴリズム中における制御,データの移動など の要因は無視する 38 5,4,3 2,4,1 2,4,1 a1 : a2 入力:数の列 各ノードでは ai aj の比較を行う 5,4,3 a2 : a3 a1 : a3 2,4,1 a1 : a3 a1, a2, a3 a2, a1, a3 a1, a3, a2 a3, a1, a2 1,2,4 葉は入力の置換に対応 5,4,3 a2 : a3 a2, a3, a1 a3, a2, a1 3,4,5 39 決定木の高さと比較回数 • 決定木はソートアルゴリズム A から決まる • 入力数列を与えると決定木の対応する葉が決まる • 根から葉までのパスの長さ =Aを実行する際の比較回数 • 根から葉までのパス長の最大値 =実行されるソートアルゴリズムの最悪比較回数 • 比較ソートでの最悪の比較回数は決定木の高さに 対応 40 最悪時の下界 • 決定木の高さの下界=任意の比較ソートアルゴ リズムの実行時間の下界 定理 9.1 n 要素をソートするどんな決定木の高さも (n lg n) 証明 n 要素をソートする高さ h の決定木を考える. ソートを正しく行うには,n 要素の n! 通りの置換全 てが葉に現れなければならない. 高さ h の2分木の葉の数は高々 2h. よって n! 2h ではソートできない. つまり h lg(n!) 41 h lg( n!) Stirling n n! e n の公式よりn! 2n e n 1 1 n n n n h lg e n lg n n lg e (n lg n) 42 系 9.2 ヒープソートとマージソートは漸近的に 最適な比較ソートである 証明 これらの実行時間の上界 O(n lg n) は 定理 9.1 の最悪時の下界 (n lg n) と一致する. 43
© Copyright 2024 ExpyDoc