アルゴリズムと データ構造 第12回 ソート(3): シェルソート、クイックソート 先々週の復習 O(n2)のソート 配列の後ろから先頭に向かってスキャ ンし,隣り合う2つの要素の大小関係 が逆であったら入れかえる 1 3 挿入ソート 未ソート バブルソート 現在処理対象となっているデータを, 整列済みのデータ列内の適切な位置 に挿入する 5 9 10 7 10 11 7 20 17 6 ソート済 3 5 8 選択ソート 整列されていない部分から最小要素 を選び,先頭と入れかえる処理を繰り 返す 未ソート 1 3 5 入れかえ 9 10 7 20 17 18 最小値 先週の復習 配列 A バケットソート O(n) 値kの要素を入れる箱(バケット B[k]、ただし、kは0≦k≦m-1)を準 備し、各要素A[i]をB[A[i]]に入れ たあと、バケットの中身を連結する 基数ソート O(n) 整列対象となるキーを,いくつか のサブキーに分割し,下位から上 位の順に,サブキーをもとに安定 な整列を行う [0] [1] [2] [3] [4] 1 4 0 6 2 バケットB [0] [1] [2] [3] [4] [5] [6] A[2] A[0] A[4] A[1] A[3] B U T K I D F A N F A N A N Y B U T K I D A N Y 先週の演習問題 (問1)次の5個の整数 {4, 7, 5, 6, 7}をバケットソートでバ ケットB[i]( 0 ≦ i < 10) を用いて整列する手順を図を 用いて説明せよ。 問1 解答例1 (抽象的な説明例) A [0] [1] [2] [3] [4] 5個の整数が配列Aに格納 されているものとする 4 7 5 6 7 バケットBにデータを格納する B [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] A[1] A[0] A[2] A[3] A[4] バケットの先頭から順に 取り出し、配列Aに戻す A’ [0] [1] [2] [3] [4] 4 5 6 7 7 問1 解答例2 (ポインタを用いた実装を想定した例) バケットBにデータを格納する A [0] [1] [2] [3] [4] B [0] 4 7 5 6 7 [1] [2] [3] [4] [5] [6] バケットの先頭から順に取り出し、配列Aに戻す [7] A’ [0] [1] [2] [3] [4] [8] 4 5 6 7 7 [9] A[0] A[2] A[3] A[4] A[1] (問2)次の3文字の系列を基数ソートで辞書式順序に並べて いく過程を示せ。 (B U T ), 1回目 ( F A N ), 2回目 ( A N Y ), 3回目 ( K I D ) 問2 解答 1回目 2回目 3回目 KID FAN BUT ANY FAN KID ANY BUT ANY BUT FAN KID 本日の内容 シェルソート クイックソート シェルソート(Shell sort) 1959年に D. L. Shellが発 表したアルゴリズム 挿入ソートを変形 計算量O(n1.25)~O(n1.5) 安定ではない シェルソート 単純挿入ソートの特徴 ソート済みに近い場合は高速 挿入先が遠く離れていると移動回数が増大 1 0 2 1 3 2 4 3 5 4 0 5 6 シェルソートの考え方 単純挿入ソートの長所を生かしつつ、回数の多い移動を避ける 最初にまずグループ化を行い、グループ内で大まかにソート(地なら し) グループを次第に小さくしていき、最終的に単純挿入ソートを行って ソート完了 前処理を含めても、移動回数が少なくてすみ、全体として高速化 シェルソートの実例 4-ソート 8 7 1 4 3 2 7 8 6 3 4 5 3 7 1 4 3 2 7 8 6 5 8 4 5 6 1 3 1 3 2 3 4 2 4 5 7 7 6 5 7 8 8 6 2-ソート 1-ソート シェルソート シェルソートのプログラム for (h = n / 2; h > 0; h / 2) for (i = h; i < n; i++) { int tmp = a[i]; for (j = i - h; j >= 0 && a[j] > tmp; j -= h) a[j + h] = a[j]; a[j + h] = tmp; } シェルソート 増分の選択 ある値から減少して1になればよい 増分のとり方によっては、同じグループ内でのみ ソートして、グループ分けが十分に機能しなくなる 増分が互いに倍数にならないようにする h = ・・・、121、40、13、4、1 1から始め、3倍して1を足していく 最初が大きすぎても効果なし:n / 9を超えない値 シェルソート 適切増分シェルソートのプログラム for (h = 1; h < n / 9; h = h * 3 + 1) ; for ( ; h > 0; h /= 3) for (i = h; i < n; i++) { int tmp = a[i]; for (j = i - h; j >= 0 && a[j] > tmp; j -= h) a[j + h] = a[j]; a[j + h] = tmp; } 練習問題 以下のデータをシェルソートで昇順に整列しなさい 整列の間隔は4、2、1の順で減らすこと。 10, 5 , 4, 27, 2, 6, 3, 20, 1, 0, 8, 9, 7, 23, 13, 42 解答 10 5 4 27 2 6 3 20 1 0 8 9 7 23 13 42 4ずつ離れたものをソートした結果 1 0 3 9 2 5 4 20 7 6 8 27 10 23 13 42 1 0 3 9 2 5 4 20 7 6 8 27 10 23 13 42 2ずつ離れたものをソートした結果 1 0 2 5 3 6 4 9 7 20 8 23 10 27 13 42 1ずつ離れたものをソートした結果 0 1 2 3 4 5 6 7 8 9 10 13 20 23 27 42 シェルソート(Shell sort)のまとめ 挿入ソートを変形 計算量O(n1.25)~O(n1.5) 安定ではない クイックソート クイックソートとは 1962年に C. A. R. Hoareが発表したアルゴリズム 内部整列アルゴリズムの中で最速 不安定な整列アルゴリズム ある要素を決め、その要素より大きいグループと 小さいグループに分類 ある要素:枢軸 各グループで枢軸を決め直して同じように分割 計算量O(n log n) ただし,最悪の場合O (n2) 1. 2. 3. 4. クイックソートの原理 ソートする範囲のデータから適当な軸要素(枢軸、pivot) Xを選ぶ。 要素をひとつずつ調べて、X より小さいグループと大きいグループに分割す る。 小グループと大グループ各々に上記1.と2.を適用する。 各々のグループが分割できなくなるまで分割を繰り返す。 実行例1 8 4 1 1 3 2 2 6 3 4 7 1 4 2 5 3 2 5 5 3 4 5 6 1 8 8 6 6 7 7 7 8 分割の手順 枢軸をx、配列左端をpl、配列右端をprとし pl位置の要素がxより大きくなるまでplを右に移動 pr位置の要素がxより小さくなるまでprを左に移動 pl、pr両位置の要素を交換 操作を続け、plとprの位置が交差した時点で終了 配列左端からpl位置の1つ前まで:枢軸より小さい集合 pr位置の1つ後から配列右端まで:枢軸より大きい集合 実行例2 5 pl 7 3 1 枢軸以下 4 6 2 2 6 3 7 9 8 枢軸以上 pr クイックソート ソートの手順 分割されたグループそれぞれに、同じ処理を再 度適用して再分割 分割されたグループの要素数が1になった時点で 分割終了 一種の分割統治法:再帰で簡単に実現 クイックソートのコード void quicksort(int l, int r, int A[]) { int k; /* 枢軸より大きい部分の開始位置 */ 軸要素を決める; if (キーがすべて同じ) 終了; xより小さい部分と大きい部分に分割し, 大きい部分の先頭の位置kを返す; quicksort(l, k – 1,A); quicksort(k, r, A); } /* 小さい部分を整列 */ /* 大きい部分を整列 */ 枢軸の選択 枢軸の選び方がクイックソートの効率に影響 配列の値の最小値または最大値を選ぶと最悪 できるだけ配列の値の中央値が望ましい ただし厳密に求める処理に時間がかかると本末転倒 方針1:先頭要素、中央要素、最後尾要素の3値 の中間値を使う 方針2:方針1に加え、3値をソートして、中央要素 と最後尾1つ前の値を入れ替え、ソート範囲を3要 素分絞る 先頭は必ず枢軸以下、最後尾とその手前は必ず枢軸 以上の値になっているので、それぞれの内側をソート 範囲とすればよくなる 実行例3 X= 3の場合 pl→ ←pr A[0] A[1] A[2] A[3] A[4] A[5] A[6] A[7] A[8] A[9] Start 3 1 4 1 5 9 2 6 5 3 (1) 2 1 4 1 5 9 3 6 5 3 (2) 2 1 1 4 5 9 3 6 5 3 分割終了 pr = 2 pl = 3 pl, pr : カーソル v : 枢軸 実行例3:各々のグループをさらに分割 a[0] a[1] レベル2 レベル5 ソート終了 a[4] a[5] a[6] a[7] a[8] a[9] 4 5 9 3 6 5 3 9 6 5 5 1 1 1 1 2 4 3 3 2 4 3 3 3 3 X=2 1 終了 キーが 全て同じ レベル4 a[3] 2 1 レベル3 a[2] 終了 X=5 X=4 3 3 終了 9 6 5 5 4 5 6 5 9 4 5 6 5 5 5 6 5 5 6 終了 X=9 X=6 終了 終了 9 終了 クイックソートの計算時間 分割の段数≒log2 n 各段での時間 O ( n ) 平均時間 → O ( n log n ) 最悪の場合 n= 8 比較回数 7 : 即ち n-1 6 5 4 3 2 1 選択ソートの実行と同じようになり n-1 Σi i=1 = { n ( n - 1) } / 2 → O ( n 2) クイックソートのまとめ 1962年に C. A. R. Hoareが発表したアルゴリズム 内部整列アルゴリズムの中で最速 不安定な整列アルゴリズム ある要素を決め、その要素より大きいグループと 小さいグループに分類 ある要素:枢軸 各グループで枢軸を決め直して同じように分割 計算量O(n log n) ただし,最悪の場合O (n2) ソート全体のまとめ 名称 計算量 内部/外 部 安定/不安定 バブルソート O(n2) 内部 安定 挿入ソート O(n2) 内部 安定 選択ソート O(n2) 内部 不安定 バケットソート O(n) 内部 安定 データに制限 有 基数ソート O(n) 内部 安定 データに制限 有 シェルソート O(n1.25)~ O(n1.5) 内部 不安定 内部 不安定 クイックソート O(n log n) 備考 最悪O(n2) 演習問題 次の配列を、クイックソートにより並べ替える。 並べ替え領域の左端の添え字を pl、右端の添え字を pr と設定したとき、枢軸は (pl+pr)/2(小数点以下切り捨て)で示される添え字の位置とする(この配列では添え字 5 の 59)。ここで 1. pl 位置の値が枢軸の値以上になるまで pl を右へずらす。 2. pr 位置の値が枢軸の値以下になるまで pr を左へずらす。 3. pl 位置の値と pr 位置の値を入れ替える。 4. pl と pr が交差して入れ替わるまで、1. から 3. までを繰り返す。 という操作を行うと、配列は となり、添え字 0 から 5 までの集合と 6 から 11 までの集合に分割される。このとき、以下の問い に答えよ。 (1) 添え字 0 から 5 までの集合において、pl、pr および枢軸の位置を再設定し、同様に分割したと き、分割後の配列の並びと pl、pr の添え字位置を示せ。 (2) 添え字 6 から 11 までの集合において、pl、pr および枢軸の位置を再設定し、同様に分割した とき、分割後の配列の並びと pl、pr の添え字位置を示せ。 名前と学籍番号をご記入のうえ、解答用紙(A4)を提出する 提出先: 工学部電子情報実験研究棟5階 NO.5506室のドアのポストに入れてください 締め切り時間: 来週月曜日(7月7日) 午前9時まで
© Copyright 2024 ExpyDoc