プログラミング言語 Standard ML 入門 (問題の解答例)
13 配列を用いたプログラミング

13.2 配列のソートアルゴリズム

問 13.1

sort 関数中にコメントで記述されている 配列の2つの要素を入れ替える関数 swap を定義せよ.

解答例 13.3の解答を参照。

問 13.2

以上の方針に従い,sort 関数中にコメントで記述されている pivot の設定処理を書け.

解答例 13.3の解答を参照。

問 13.3

partition 関数の中にコメントで記述されている scanRightscanLeft 関数を作成し, ArrayQuickSortストラクチャを完成させよ.

解答例  ArrayQuickSortストラクチャ全体のコード例を以下に示す。

   signature SORT = sig
     val sort : ’a array * (’a * ’a -> order) -> unit
   end
   structure ArrayQuickSort : SORT  =
   struct
   local
     open Array
   in
     fun sort (array,comp) =
       let
         fun swap (i,j) =
             let val temp = sub(array,i)
             in (update(array,i,sub(array,j)); update(array,j,temp))
             end
           fun getPivot (i,j) =
               let
                 val delta = (j-i) div 4
                 val i = i + delta
                 val j = i + delta * 3
                 val mid = i + (j-i) div 2
                 val ie = sub(array,i)
                 val je = sub(array,j)
                 val me = sub(array,mid)
               in
                 case (comp(ie,me),comp(me, je))
                  of (LESS, LESS) => (mid,me)
                   | (LESS, _) => (case comp(ie, je) of LESS => (j,je) | _ => (i,ie))
                   | (_, GREATER) => (mid,me)
                   | _ => (case comp(ie, je) of LESS => (i,ie) | _ => (j,je))
               end
         fun qsort (i,j) =
             if j <= i+1 then ()
             else
               let
                 val pivot =
                   let val (pi,pivot) = getPivot(i,j-1)
                   in update(array,pi,sub(array,i));
                      update(array,i,pivot);
                       pivot
                   end
                 fun partition (a,b) =
                     if b < a then (a - 1)
                     else
                       let
                         fun scanRight a =
                             if a > b then a
                             else
                               case comp(sub(array,a),pivot) of
                                 GREATER => a
                               | _ => scanRight (a+1)
                         val a = scanRight a
                         fun scanLeft b =
                             if b < a then b
                             else
                               case comp(sub(array,b),pivot) of
                                 GREATER => scanLeft (b - 1)
                               | _ => b
                         val b = scanLeft b
                       in
                         if b < a then (a - 1)
                         else (swap(a,b);partition (a+1,b-1))
                       end
                 val k = partition (i+1,j-1)
                 val _ =  swap(i,k)
               in
                 (qsort (i,k); qsort (k+1,j))
               end
       in
         qsort (0,Array.length array)
       end
   end
   end
問 13.4

以下の改良を加えよ.

  1. 1.

    配列の大きさが小さい場合,クイックソートアルゴリズムは最適ではない. qsort 関数の冒頭で部分配列の大きさを判定し, 大きさが23の場合を特別に処理するように変更せよ.

  2. 2.

    pivot要素と先頭要素の入れ替え処理を伴うpivotの選択処理は,配列の大きさ が小さいときかえって効率を低下させる. 配列の大きさが30以下の場合,先頭要素をpivotとするように変更せよ.

解答例  以下に例を示す。この例では、pivotの選択も多少の洗練を試みている。 ただし、効果があるかは計算実行環境にもより、一様に最適なものがあるかは、 今後の探求によるところである。

   structure ArrayQuickSortOpt : SORT  =
   struct
   local
     open Array
   in
     fun sort (array,comp) =
         let
           fun swap (i,j) =
               let val temp = sub(array,i)
               in (update(array,i,sub(array,j)); update(array,j,temp))
               end
           fun sort3 i =
               case comp(sub(array,i),sub(array,i+1))
               of GREATER =>
                 (case comp(sub(array,i+1),sub(array,i+2))
                  of GREATER => (* 3 2 1 *)
                     swap(i,i+2)
                   | _ => (case comp(sub(array,i),sub(array,i+2))
                           of GREATER => (* 3 1 2 *)
                              let val ei = sub(array,i) in
                                (update(array,i,sub(array,i+1));
                                 update(array,i+1,sub(array,i+2));
                                 update(array,i+2,ei))
                              end
                            | _ => (* 2 1 3 *)
                              (swap(i,i+1))
                          )
                 )
                | _ =>
                  (case comp(sub(array,i+1),sub(array,i+2))
                    of GREATER =>
                       (case comp(sub(array,i),sub(array,i+2))
                         of GREATER => (* 2 3 1 *)
                            let val ei = sub(array,i) in
                              (update(array,i,sub(array,i+2));
                               update(array,i+2,sub(array,i+1));
                               update(array,i+1,ei))
                            end
                          | _ => (* 1 3 2 *)
                            (swap(i+1,i+2))
                       )
                     | _ =>  (* 1 2 3 *)
                       ())
           fun getPivot (i,j) =
               let
                 val delta = (j-i) div 4
                 val i = i + delta
                 val j = i + delta * 3
                 val mid = i + (j-i) div 2
                 val ie = sub(array,i)
                 val je = sub(array,j)
                 val me = sub(array,mid)
               in
                 case (comp(ie,me),comp(me, je))
                  of (LESS, LESS) => (mid,me)
                   | (LESS, _) => (case comp(ie, je) of LESS => (j,je) | _ => (i,ie))
                   | (_, GREATER) => (mid,me)
                   | _ => (case comp(ie, je) of LESS => (i,ie) | _ => (j,je))
               end
           fun qsort (i,j) =
               if j < i+2 then ()
               else if j = i+2 then case (comp(sub(array,i),sub(array,i+1)))
                                    of GREATER => swap(i,i+1)
                                     | _ => ()
               else if j = i + 3 then sort3  i
               else
                 let
                   val pivot = if (j-i) < 30 then sub(array,i)
                               else
                                 let val (pi,pivot) = getPivot(i,j-1)
                                 in update(array,pi,sub(array,i));
                                    update(array,i,pivot);
                                    pivot
                                 end
                   fun partition (a,b) =
                       if b < a then a
                       else
                         let
                           fun scanRight a =
                               if a > b then a
                               else
                                 case comp(sub(array,a),pivot)
                                 of GREATER => a
                                  | _ => scanRight (a+1)
                           val a = scanRight a
                           fun scanLeft b =
                               if b < a then b
                               else
                                 case comp(sub(array,b),pivot)
                                 of GREATER => scanLeft (b - 1)
                                  | _ => b
                           val b = scanLeft b
                         in
                           if b < a then a
                           else (swap(a,b);
                                 partition (a+1,b-1))
                         end
                   val a = partition (i+1,j-1)
                   val _ =  swap(i,a-1)
                 in
                   (qsort (i,a-1); qsort (a,j))
                 end
         in
           qsort (0,Array.length array)
         end
   end
   end