
前回では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 )を参照します。