210 likes | 315 Views
コンピュータアルゴリズム 2 1. 整列 ( ソーティング )(1). 田浦. http://www.logos.ic.i.u-tokyo.ac.jp /~tau/lecture/computer_software/. 整列とは ?. 小さい順 ( または大きい順 ) に多数の要素を並べ替えること 入力はさしあたり配列 初期状態: n = n , a [0] = a 0 , a [1] = a 1 , ..., a [ n – 1] = a n – 1
E N D
コンピュータアルゴリズム21. 整列(ソーティング)(1) 田浦 http://www.logos.ic.i.u-tokyo.ac.jp/~tau/lecture/computer_software/
整列とは? • 小さい順(または大きい順)に多数の要素を並べ替えること • 入力はさしあたり配列 • 初期状態: n = n, a[0] = a0, a[1] = a1, ..., a[n – 1] = an – 1 • 最終状態: { a[0], a[1], ..., a[n – 1] } = {a0, a1, ..., an – 1 },a[0] a[1] ... a[n – 1] マルチ集合(重複を区別 する集合)とする
なぜ重要? • 日常目にしている応用 • 表計算ソフト「並べ替え」機能 • Unixソートコマンド • 検索エンジンの「ランキング」機能 • 「探索」を効率的に行うための前処理として • 無秩序に並べられたものから目当てを探すのは手間がかかる
整列アルゴリズムさまざま • O(n2) • バブルソート[6],挿入ソート[7] • 平均 O(n log n), 最悪O(n2) • クイックソート[1] • 最悪 O(n log n) • マージソート[3],ヒープソート[4] • データの種類数が一定(少数)の場合に O(n) • バケットソート[5],Radixソート[2] • ([...]内は主観に基づいた重要度) • 本日のもうひとつのテーマ: 計算量の解析手法
バブルソート・挿入ソート • 「遅い」 (O(n2)時間計算量) 整列アルゴリズムの代表 • 挿入ソートの考え方: 左端から「正しい値」で埋めていく • 例: • 8,6,3,5,1,2,9 1,*,*,*,*,*,* 1,2,*,*,*,*,* 1,2,3,*,*,*,* 1,2,3,5,*,*,* 1,2,3,5,6,*,* 1,2,3,5,6,8,* 1,2,3,5,6,8,9 プログラムにできますか? 慣れないうちは図を見ながら 「言葉に」することから始める 「まだ選ばれていない人たちから最小の人を選んでそれらの左端に連れてくる」 をN回繰り返す (Nは配列の要素数)
挿入ソートのプログラム 「まだ選ばれていない人たちから最小の人を選んでそれらの左端に連れてくる」 をN回繰り返す (Nは配列の要素数) for (i = 0; i < N; i++) { a[i] … a[N – 1]中の最小をa[i]へ連れてくる; } for (i = 0; i < N; i++) {m = i; /* 最小値の位置 */ for (j = i + 1; j < N; j++) { if (a[j] < a[m]) m = j; } /* a[i]とa[m]を入れ替える */ t = a[j]; a[j] = a[m]; a[m] = t; } これを見て時間計算量がO(N2) (より詳しくは(N2))を確認
クイックソート • 代表的な「速いソーティング手法」 • 発明者: Turing賞受賞者Hoare • 考え方: • 適当な「大小の境界値」(pivot)を選択 • たとえば先頭要素 • pivotより小さいものと大きいものに分ける • それぞれのグループを整列 • 再帰呼び出しを利用すればすっきりと書ける
コードの概形 • quicksort(a, l, r) { /* a[l], a[l+1], ..., a[r– 1]を整列する */ if (r – l > 1) { piv = ...; ...; ...; /* ここで以下が成り立っているa[l], a[l+1], ..., a[c–1] < piv;a[d], a[d+1], ... , a[r–1] > piv; */ quicksort(a, l, c); quicksort(a, d, r); }} a[l], ..., a[r–1] を < piv, > piv に応じて分割 < piv 部分と > piv 部分を整列
再帰呼び出し,分割統治法 • このように,「もともとの入力に対する解」を, • 複数の小さな入力に「分割」し, • それぞれの解を求め, • それらを統合してもともとの入力の解を作る という解法を「分割統治法」(divide-and-conquer)という • 再帰呼び出しを使って見通しよくコーディングできる • 常に意識しておくと良い • 非常に広範囲の問題に通用する考え方(後述) • それ(再帰呼び出し)なしにはコーディングが複雑化する
分割統治法のテンプレート • solve(X) { if (trivial(X)) { trivial_solve(X); } else { X1, X2, ..., X = divide(X); A1 = solve(X1); A2 = solve(X2); ... A = solve(X); return combine(A1, A2, ..., A); }}
クイックソートの「分割」部分 • pivot選択の実際 • もっとも単純には配列の適当な(e.g., 先頭)要素 • pivotとの大小で配列を分割する • もうひとつの配列bを用意し, • a[l], a[l+1], ...の順に, • < piv の要素はbの左から(b[l], b[l+1], ...), • > piv の要素はbの右から(b[r–1], b[r–2], ...), • 詰めていく • 詰め終わったら,両者の間にpivを置く • b[l], ..., b[r–1]をからa[l], ..., a[r–1]へコピー a b
交換 p i p i 補助配列を使わない分割法 ? i p
コード概形* クイズ:間違い探し void quicksort_rec(a, l, r) { if (r – l > 1) { piv = a[l]; p = l ; for (i = l ; i < r; i++) { if (a[i] <= piv) { /* a[p] と a[i] 交換 */ t = a[p]; a[p] = a[i]; a[i] = t;p++; } } quicksort_rec(a, l, p); quicksort_rec(a, p, r); } }
コード概形 void quicksort_rec(a, l, r) { if (r – l > 1) { piv = a[l]; p = l + 1; for (i = l + 1; i < r; i++) { if (a[i] <= piv) { t = a[p]; a[p] = a[i]; a[i] = t;p++; } } t = a[p–1]; a[p–1] = a[l]; a[l] = t; quicksort_rec(a, l, p–1); quicksort_rec(a, p, r); } }
クイックソートの計算量 • 問題(入力)の大きさ: 配列の要素数n (= r – l) • いくつかの楽観的な仮定の下での典型的計算量 O(n log n) • quicksort(a, l, r) { if (r – l > 1) { piv = ...; ...; ...; quicksort(a, l, p–1); quicksort(a, p, r); }}
直感的説明 n n個の分割 n n / 2個の分割 n / 2 個の分割 n / 4 n / 4 n / 4 n / 4 log n段 ... ... ... ... 1 1 1 1 1 1
一般的手法:漸化式 • T(n) : 大きさ nの問題を解く時間の(以下の楽観的仮定のもとでの)最大値 • 楽観的仮定:分割の結果,大きさ n / 2 の配列二つに分かれると仮定 • T(0) = 1, T(1) = 1 • T(n) cn + 2 T (n / 2) • T(n) O(n log n) を証明せよ • より一般的な場合は後述
クイズ • 楽観的仮定はもちろん常に成り立つわけではない • ではクイックソートの最悪時間計算量はO(n log n)か?
演習課題 • 遅い整列アルゴリズム(バブルソートないし挿入ソート)をひとつ, 速い整列アルゴリズム(クイックソートなど)実際にプログラムにする • Linuxでコンパイル&実行できるように • 基本はC言語. C++, Javaなどでやりたい人はそれも可 • 入力配列は整数(int)の配列とし, 乱数ないし人工的なデータを生成 • 要素数Nを変えられるように作ること • Nと計算時間の関係, アルゴリズムによる速度の(劇的な)違いを体験する
演習(続) • かかった時間の「計測」をプログラム内で行うこと(自分の時計で計るのではなく) • C/C++ : gettimeofday • manコマンド • Java : • System.nanoTime() ( ver. 1.5) • System.currentTimeMillis() • JDK document (HP参照)
練習(gnuplot) • グラフを表示する有用なツール • 配列の大きさとともに計算時間がどう変わるかを表示してみると良い • 授業HPに例題が掲載されている