半角チルダ

ExcelVBA、その他。
覚え書きや、補足資料などのスクラップブック。
end-u(1037781)

■非再帰QuickSort(カンニング編

2012-10-24 20:00:00 | scrap
と、いう事でここは先人の経験と技術を素直に学ばせていただこう。
#決してカンニングではない..と思う(多分 X)

残念ながら出典サイトはなくなってしまいました。
確か「Excel VBA 実用サンプルコレクション」著者 渡辺ひかる氏のサイト(www.vbasekai.com)に、
以前mougでよく回答してらっしゃった工藤氏のコードが載ってたと思う。
それを参考にさせて頂きました。感謝。

Option Explicit

Private Declare Function timeGetTime Lib "winmm.dll" () As Long
Private QS() As String    'ソートデータ用
Private QL() As Long     'ソートインデックス用
'-------------------------------------------------
Private Sub test()      'テストプロシージャ選択用
  Const cnt As Long = 65536 'テストデータ量
  Dim ret() As String    '書出し用配列
  Dim t(1) As Long
  Dim i   As Long
  Dim p   As Long
  Dim v

  ReDim QS(1 To cnt)
  ReDim QL(1 To cnt)
  ReDim ret(1 To cnt, 0)

  For Each v In Range("A1").Resize(cnt).Value
    i = i + 1
    QL(i) = i
    QS(i) = v
  Next

  p = 1
  t(0) = timeGetTime
  Select Case p
  Case 1
    Call LQuickSort(1, cnt)
  Case 2
    Call LQuickSortS(1, cnt)
  Case 3
    Call LQuickLoop4
  End Select

  For i = 1 To cnt
    ret(i, 0) = QS(QL(i))
  Next

  t(1) = timeGetTime
  'Sheets.Add.Range("A1").Resize(cnt).Value = ret
  Erase QS, QL, ret
  Debug.Print cnt, t(1) - t(0)
End Sub
'-------------------------------------------------
Private Sub LQuickSort(n As Long, x As Long) '再帰
  Dim tmp As String
  Dim i  As Long
  Dim j  As Long
  Dim k  As Long

  i = n
  j = x
  tmp = QS(QL((i + j) ¥ 2))

  Do
    Do While QS(QL(i)) < tmp
      i = i + 1
    Loop
    Do While tmp < QS(QL(j))
      j = j - 1
    Loop

    If j <= i Then
      Exit Do
    End If
    k = QL(i)
    QL(i) = QL(j)
    QL(j) = k
    i = i + 1
    j = j - 1
  Loop

  If n < (i - 1) Then
    LQuickSort n, i - 1
  End If
  If (j + 1) < x Then
    LQuickSort j + 1, x
  End If
End Sub
'-------------------------------------------------
Private Sub LQuickSortS(n As Long, x As Long) '再帰
  Dim tmp As String
  Dim i  As Long
  Dim j  As Long
  Dim k  As Long

  tmp = QS(QL((n + x) ¥ 2))
  i = n - 1
  j = x + 1

  Do
    Do
      i = i + 1
    Loop While QS(QL(i)) < tmp
    Do
      j = j - 1
    Loop While tmp < QS(QL(j))

    If j <= i Then
      Exit Do
    End If
    k = QL(i)
    QL(i) = QL(j)
    QL(j) = k
  Loop

  If n < (i - 1) Then
    LQuickSortS n, i - 1
  End If
  If (j + 1) < x Then
    LQuickSortS j + 1, x
  End If
End Sub
'-------------------------------------------------
Private Sub LQuickLoop4()   '非再帰
  Const p   As Long = 100 '記憶用配列サイズ
  Dim idxi(p) As Long    'Index記憶用
  Dim idxj(p) As Long    'Index記憶用
  Dim Lv   As Long    '深さレベル
  Dim mn   As Long
  Dim mx   As Long
  Dim i    As Long
  Dim j    As Long
  Dim k    As Long
  Dim tmp   As String

  Lv = 1
  idxi(Lv) = LBound(QL)
  idxj(Lv) = UBound(QL)

  Do While Lv > 0
    mn = idxi(Lv)
    mx = idxj(Lv)
    Lv = Lv - 1
    tmp = QS(QL((mn + mx) ¥ 2))
    i = mn - 1
    j = mx + 1

    Do
      Do
        i = i + 1
      Loop While QS(QL(i)) < tmp
      Do
        j = j - 1
      Loop While tmp < QS(QL(j))
      If j <= i Then
        Exit Do
      End If
      k = QL(i)
      QL(i) = QL(j)
      QL(j) = k
    Loop

    If mn < (i - 1) Then
      Lv = Lv + 1
      idxi(Lv) = mn
      idxj(Lv) = i - 1
    End If
    If (j + 1) < mx Then
      Lv = Lv + 1
      idxi(Lv) = j + 1
      idxj(Lv) = mx
    End If
  Loop
End Sub

[LQuickSort]
65536     329
65536     329
65536     328

[LQuickSortS]
65536     312
65536     297
65536     297

[LQuickLoop4]
65536     312
65536     297
65536     314

ver2010。
[LQuickSort]
1000000    6907
1000000    7032
1000000    7017

[LQuickSortS]
1000000    6766
1000000    6800
1000000    6781

[LQuickLoop4]
1000000    6719
1000000    6767
1000000    6735

再帰版と同等の速さです。しかも簡潔。
達人の方が書くとこうも違うものなのですね..
他にも色々なサイトで非再帰版のコードを見て勉強させて頂きましたが、
[LQuickLoop4]の元になった工藤氏のコードが一番勉強になりました。有難うございます。

また、再帰版も再掲してますが、[LQuickSortS]は今回Loop版に合わせて比較の意味で載せました。
以前はこっち使ってたんですが、いつの間にか変わってました。[LQuickSort]のコードの方が馴染みやすいのかな..
#師匠の教えもあってちょっと修正してますが :)
それにしても、ちょっとしたコーディングの違いで差が出るもんなんですね-

あとは余談。
コードを見てもらうとわかる事なので今さらですが、今回の配列ソートは配列そのものを入れ替えずに
ソートインデックス用のLong型配列を作ってこれを入れ替えてます。
これは、セル範囲を配列に取得したVariant型2次元配列をそのままQuickSortする場合と比較して、
ソートデータ用のString型1次元配列、ソートインデックス用Long型1次元配列、
それに書出し用2次元配列にLoop処理で格納し直す処理を入れても、より高速に処理できます。
Comment
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする