前回では500,000件ほどでダウンしましたので、別な方法でクイックソートを考えてみました。
EXCELでクイックソートコードを作成
エクセルのA列に、ソートしたいデータを作りC列にソートした結果を表示しています。1,045,075件をソートしてみましたところ40秒でできました。
私のPCのスペックは、CPU INTEL i7-3632QM memory 16GB 9年ほど前のものですので、現在のPCだともっと早いかもしれません。
EXCELのソート機能、メニューの「データ」「並べ替え」列A-セルの値-昇順で行ったところおよそ60秒かかりました。
私のソートが勝利しました。
EXCELのVBAコード
ソートを実行します。
Private Sub QuickSortB() Dim SortArray As Variant Dim EndRow As Long With ActiveSheet EndRow = .Cells(.Rows.Count, 1).End(xlUp).Row SortArray = .Range(.Cells(1, 1), .Cells(EndRow, 1)).Value End With Call QuickB(SortArray, LBound(SortArray, 1), UBound(SortArray, 1)) With ActiveSheet .Range(.Cells(LBound(SortArray, 1), 3), .Cells(UBound(SortArray, 1), 3)) = SortArray End With End Sub
QuickB プロシージャの作成
Pivot_Posi 関数で、ソート対象の配列の中の任意の値を Pivot として、そのインデックスを取得します。この関数を実行すると、Pivot のインデクスよりも若いインデクスの配列には Pivot よりも小さい値を保持しています。また、Pivot よりも大きいインデックスの配列は Pivot よりも大きい値を保持しています。Pivot のインデクスよりも若いインデクスの配列、Pivot よりも大きいインデックスの配列と分け再帰して実行します。
Private Sub QuickB(ByRef SortArray As Variant, _ ByVal topPosi As Long, _ ByVal tailPosi As Long) Dim PivotPosi As Long If tailPosi - topPosi <= 0 Then '再帰の終了条件です。 Else PivotPosi = Pivot_Posi(SortArray, topPosi, tailPosi) Call QuickB(SortArray, topPosi, PivotPosi - 1) Call QuickB(SortArray, PivotPosi + 1, tailPosi) End If End Sub
Pivot_Posi関数
この関数は、SortArray:ソート対象の配列、ソート範囲を指定する先頭インデックス:topPosi、テイルインデックス:tailPosi を引数とし、戻り値は、関数の中で指定した値( Pivot )がソートしたときにあるべきインデックスです。
Private Function Pivot_Posi(ByRef SortArray As Variant, _ ByVal topPosi As Long, _ ByVal tailPosi As Long) Dim Pivot As String Dim smallPosi As Long Dim largePosi As Long Dim BUF As String '軸を配列の中央の値にします。(下図 置き換え0) Pivot = SortArray(Int((topPosi + tailPosi) / 2), 1) 'topPosiのインデックスにある値と入れ替えます。 SortArray(Int((topPosi + tailPosi) / 2), 1) = SortArray(topPosi, 1) SortArray(topPosi, 1) = Pivot 'topPosi は Pivot のインデックスなので、探査は topPosi +1 となります。 smallPosi = topPosi + 1 largePosi = tailPosi '無限ループを開始します。 Do While True 'この例だと、上に Pivot より小さい値の領域、下に超える値の領域とします。 '上から下へ探査して Pivot より大きい値のインデックスを探します。 'このループは Pivot より小さい値の場合進み、超える場合は進みません。 'したがって、大きい値のインデックスで止まります。 '= を忘れずに必ずつけます。忘れると無限ループになります。 Do While SortArray(smallPosi, 1) <= Pivot And smallPosi < tailPosi smallPosi = smallPosi + 1 Loop '逆に、下から上へ探査して Pivot より小さい値のインデックスを探します。 'このループは Pivot を超える値の場合進み、超えない場合は進みません。 'したがって、 Pivot 以下の値のインデックスで止まります。 Do While SortArray(largePosi, 1) > Pivot And largePosi > topPosi largePosi = largePosi - 1 Loop '入れ子のループが終わり交差(smallPosiとlargePosi)しているか調べます。 If smallPosi < largePosi Then '交差していなければ置き換えます。(下図 置き換え1) BUF = SortArray(smallPosi, 1) SortArray(smallPosi, 1) = SortArray(largePosi, 1) SortArray(largePosi, 1) = BUF Else 'smallPosiとlargePosiは交差し領域の区分は終わったので、ループを抜けます。 Exit Do End If Loop 'smallPosiとlargePosiは交差して無限ループを抜けているので、 'smallPosi は Pivot を超える値の最終位置、 'また largePosi は Pivot 以下の値の最終位置となります。 'したがって、Pivot のあるべきインデックスが求まったので、 'largePosi の値と、topPosi の値(Pivot)と置き換えます。(下図 置き換え2) SortArray(topPosi, 1) = SortArray(largePosi, 1) SortArray(largePosi, 1) = Pivot Pivot_Posi = largePosi End Function
交差について
インデックスを1から10までの配列で、値は配列1の場合を考えてみます。Pivot をINDEXが 1 で、値が 12 とします。SmallPosi は 2 で、 LargePosi は 10 となります。上のコードの While の入れ子の最初で、SmallPosi の INDEX が 2 から 1 づつ増え INDEX 5 で止まります。2つ目の入れ子で、LargePosi の INDEX が 10 から 1 つづ減って INDEX 6 で止まります。次に IF の条件 smallPosi < largePosi を満たすため置き換え(置き換え1)が行われ配列2になります。
次に、外の While が最初から行われ、SmallPosi が示す INDEX 5 は 15 ではなく 8 であるから 1 つ増え SmallPosi は 6 となり、LargePosi が示す INDEX 6 は 8 ではなく 15 であるからは 1 つ減り 5 となります。図で見るように矢印線(赤色→)は交差することになります。
そして、LargePosi が Pivot より小さい値の境界になっているのが分かると思います。また、LargePosi が ソートした時の Pivot の正しい INDEX であることも理解できると思います。
IF文の条件を満たさなったので、無限ループを抜け LargePosi が示す INDEX 5 の 8 を Pivot TopPosi の INDEX にコピーし、Pivot の値を正しい INDEX の LargePosi にコピーします。(置き換え2)
配列3 のようになります。
配列3の INDEX 1 から 4 までの配列4( QuickB(SortArray, 1, 4) )、配列3の INDEX 6 から 10 までの配列5( QuickB(SortArray, 6, 10) ) を再帰します。
配列は参照して使われますので、配列1、配列2、配列3、配列4、配列5は同じ実体(プロシージャ QuickSortB で作成した配列 SortArray )を参照します。