Excel VBA 按降序对数字数组进行排序的最快方法?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11504418/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 13:33:31  来源:igfitidea点击:

Excel VBA Quickest way to sort an array of numbers in descending order?

arraysexcelvbasorting

提问by AZhu

What is the quickest way (in terms of computational time) to sort an array of numbers (1000-10000 numbers but could vary) in descending order? As far as I know the Excel build-in functions is not really efficient and in-memory sorting should be a lot faster than the Excel functions.

按降序对数字数组(1000-10000 个数字,但可能会有所不同)进行排序的最快方法(就计算时间而言)是什么?据我所知,Excel 内置函数并不是真正有效,内存排序应该比 Excel 函数快得多。

Note that I can not create anything on the spreadsheet, everything has to be stored and sorted in memory only.

请注意,我无法在电子表格上创建任何内容,所有内容都必须仅在内存中存储和排序。

回答by trincot

You could use System.Collections.ArrayList:

你可以使用System.Collections.ArrayList

Dim arr As Object
Dim cell As Range

Set arr = CreateObject("System.Collections.ArrayList")

' Initialise the ArrayList, for instance by taking values from a range:
For Each cell In Range("A1:F1")
    arr.Add cell.Value
Next

arr.Sort
' Optionally reverse the order
arr.Reverse

This uses Quick Sort.

这使用快速排序。

回答by Tanner

Just so that people don't have to click the link that I just did, here is one of the fantastic examples from Siddharth's comment.

只是为了让人们不必单击我刚刚所做的链接,这是 Siddharth 评论中的一个很好的例子。

Option Explicit
Option Compare Text

' Omit plngLeft & plngRight; they are used internally during recursion
Public Sub QuickSort(ByRef pvarArray As Variant, Optional ByVal plngLeft As Long, Optional ByVal plngRight As Long)
    Dim lngFirst As Long
    Dim lngLast As Long
    Dim varMid As Variant
    Dim varSwap As Variant

    If plngRight = 0 Then
        plngLeft = LBound(pvarArray)
        plngRight = UBound(pvarArray)
    End If
    lngFirst = plngLeft
    lngLast = plngRight
    varMid = pvarArray((plngLeft + plngRight) \ 2)
    Do
        Do While pvarArray(lngFirst) < varMid And lngFirst < plngRight
            lngFirst = lngFirst + 1
        Loop
        Do While varMid < pvarArray(lngLast) And lngLast > plngLeft
            lngLast = lngLast - 1
        Loop
        If lngFirst <= lngLast Then
            varSwap = pvarArray(lngFirst)
            pvarArray(lngFirst) = pvarArray(lngLast)
            pvarArray(lngLast) = varSwap
            lngFirst = lngFirst + 1
            lngLast = lngLast - 1
        End If
    Loop Until lngFirst > lngLast
    If plngLeft < lngLast Then QuickSort pvarArray, plngLeft, lngLast
    If lngFirst < plngRight Then QuickSort pvarArray, lngFirst, plngRight
End Sub

回答by Margus

If you want efficient algorithm, then take a look at Timsort. It is adaptation of merge sort that fixes it's problems.

如果你想要高效的算法,那么看看Timsort。它是对合并排序的改编,解决了它的问题。

Case    Timsort     Introsort   Merge sort  Quicksort   Insertion sort  Selection sort
Best    ?(n)        ?(n log n)  ?(n log n)  ?(n)        ?(n^2)          ?(n)
Average ?(n log n)  ?(n log n)  ?(n log n)  ?(n log n)  ?(n^2)          ?(n^2)  
Worst   ?(n log n)  ?(n log n)  ?(n log n)  ?(n^2)      ?(n^2)          ?(n^2)  

However 1k - 10k data entries are far too little amount of data for you to worry about built in search efficiency.

然而,1k - 10k 数据条目的数据量太少,您不必担心内置的搜索效率。



Example : If you have data from column A to Dand header is at row 2and you want to sort by column B.

示例:如果您有从A到 D数据并且标题位于第 2 行,并且您想按B 列排序。

Dim lastrow As Long
lastrow = Cells(Rows.Count, 2).End(xlUp).Row
Range("A3:D" & lastrow).Sort key1:=Range("B3:B" & lastrow), _
   order1:=xlAscending, Header:=xlNo

回答by jdrago

I have used the Shell sort algorithm successfully. Runs in the blink of an eye when tested for N=10000 using an array generated with VBA Rnd() function - don't forget to use the Randomize statement for generating test arrays. It was easy to implement and short and efficient enough for the number of elements I was dealing with. Reference is given in the code comments.

我已经成功地使用了 Shell 排序算法。使用 VBA Rnd() 函数生成的数组测试 N=10000 时,眨眼间运行 - 不要忘记使用 Randomize 语句生成测试数组。对于我正在处理的元素数量来说,它很容易实现,而且足够简短和高效。代码注释中给出了参考。

' Shell sort algorithm for sorting a double from largest to smallest.
' Adopted from "Numerical Recipes in C" aka NRC 2nd Edition p330ff.
' Speed is on the range of N^1.25 to N^1.5 (somewhere between bubble and quicksort)
' Refer to the NRC reference for more details on efficiency.
' 
Private Sub ShellSortDescending(ByRef a() As Double, N As Integer)

    ' requires a(1..N)

    Debug.Assert LBound(a) = 1

    ' setup

    Dim i, j, inc As Integer
    Dim v As Double
    inc = 1

    ' determine the starting incriment

    Do
        inc = inc * 3
        inc = inc + 1
    Loop While inc <= N

    ' loop over the partial sorts

    Do
        inc = inc / 3

        ' Outer loop of straigh insertion

        For i = inc + 1 To N
            v = a(i)
            j = i

            ' Inner loop of straight insertion
            ' switch to a(j - inc) > v for ascending

            Do While a(j - inc) < v
                a(j) = a(j - inc)
                j = j - inc
                If j <= inc Then Exit Do
            Loop
            a(j) = v
        Next i
    Loop While inc > 1
End Sub

回答by Jorge Jaime

I answered this question myself a long time ago, meaning I had to come back to my very first VBA archived files. So I found this old code, which I took from a book. First it saves values (from selection intersected with a table column) to array ar(x) then sort them from smallest to biggest. To sort there are 2 bucles, the first one (Do Loop Until sw=0) and the second one (For x=1 To n Next) compares value a(x) with value a(x+1), keeping in a(x) the biggest number and in ar(x+1) the smallest number. The first bucle repeats until is sorted form smallest to biggest. I actually used this code to insert a rows above every selected cell in a budget column (TblPpto[Descripcion]). Hope it helps!

很久以前我自己回答了这个问题,这意味着我必须回到我的第一个 VBA 存档文件。所以我找到了这个旧代码,这是我从一本书上拿来的。首先它将值(来自与表列相交的选择)保存到数组 ar(x) 然后将它们从最小到最大排序。排序有 2 个 bucles,第一个(Do Loop until sw=0)和第二个(For x=1 To n Next)将值 a(x) 与值 a(x+1) 进行比较,保持在 a( x) 最大的数,在 ar(x+1) 中最小的数。第一次重复,直到从小到大排序。我实际上使用此代码在预算列(TblPpto [Description])中的每个选定单元格上方插入一行。希望能帮助到你!

Sub Sorting()
Dim ar() As Integer, AX As Integer
Set rng = Intersect(Selection, Range("TblPpto[Descripcion]")) 'Cells selected in Table column
n = rng.Cells.Count 'Number of rows
ReDim ar(1 To n)
x = 1
For Each Cell In rng.Cells
    ar(x) = Cell.Row 'Save rows numbers to array ar()
    x = x + 1
Next
Do 'Sort array ar() values
    sw = 0  'Condition to finish bucle
    For x = 1 To n - 1
        If ar(x) > ar(x + 1) Then 'If ar(x) is bigger
            AX = ar(x)            'AX gets bigger number
            ar(x) = ar(x + 1)     'ar(x) changes to smaller number
            ar(x + 1) = AX        'ar(x+1) changes to bigger number
            sw = 1                'Not finished sorting
        End If
    Next
Loop Until sw = 0
'Insert rows in TblPpto
fila = Range("TblPpto[#Headers]").Row
For x = n To 1 Step -1
    [TblPpto].Rows(ar(x) - fila).EntireRow.Insert
Next x
End Sub

回答by Amiya

For 1 or 2-dimensional array sorting (think table) one can use component WSTools by WizardSoft. It implements the QuickSort algorithm and can sort on a column ascending or decending. It also contains a very fast string builder.

对于一维或二维数组排序(思考表),可以使用 WizardSoft 的组件 WSTools。它实现了 QuickSort 算法,可以按升序或降序对列进行排序。它还包含一个非常快速的字符串生成器。

Function Test()
    Dim CTX, WST
    Set CTX = CreateObject("Microsoft.Windows.ActCtx")
    CTX.Manifest = "C:\WSTools\WSTools64.sxs.manifest" ' or WSTools32.sxs.manifest if using a 32-bit client
    Set WS = CTX.CreateObject("WS.Tools")
    Const SORT_Ascending = 0, SORT_Decending = 1
    Const SORT_Case = 0, SORT_IgnoreCase = 1

    Dim i, ar2(9999, 2) '2-dimensional array,: a table with 10000 rows (0..9999) with 3 columns (0..2) each
    For i = 0 To UBound(ar2)
        ar2(i, 0) = CLng(Int((100000 - 100 + 1) * Rnd + 100)) 'random values between 100 and 100000
        ar2(i, 1) = CLng(100 + i)
        ar2(i, 2) = CLng(200 - i)
    Next
    Debug.Print "Part of unsorted 2-dimensional array: "
    For i = 0 To 10
        Debug.Print ar2(i, 0) & vbTab & ar2(i, 1) & vbTab & ar2(i, 2)
    Next

    ' Case-insensitive sort on first column of array ar2 in ascending order
    WS.ArraySort ar2, 0, SORT_Ascending, SORT_IgnoreCase

    Debug.Print "Part of sorted 2-dimensional array: "
    For i = 0 To 10
        Debug.Print ar2(i, 0) & vbTab & ar2(i, 1) & vbTab & ar2(i, 2)
    Next
End Function

回答by Cool Blue

I know the OP specified not using worksheets but its worth noting that creating a new WorkSheet, using it as a scratch pad to do the sort with worksheet functions, then cleaning up after is longer by less than a factor of 2. But you also have all of the flexibility delivered by the parameters of the Sort WorkSheet Function.

我知道 OP 指定不使用工作表,但值得注意的是,创建一个新的工作表,将其用作便笺本以使用工作表功能进行排序,然后清理后的时间不到 2 倍。但您也有Sort WorkSheet 函数的参数提供的所有灵活性。

On my system, the difference was 55 msec for the very nice recursive routine by @tannman357 and 96 msec for the method below. Those are average times over several runs.

在我的系统上,@tannman357 的非常好的递归例程的差异是 55 毫秒,而下面的方法的差异是 96 毫秒。这些是多次运行的平均时间。

Sub rangeSort(ByRef a As Variant)
Const myName As String = "Module1.rangeSort"
Dim db As New cDebugReporter
    db.Report caller:=myName

Dim r As Range, va As Variant, ws As Worksheet

  quietMode qmON
  Set ws = ActiveWorkbook.Sheets.Add
  Set r = ws.Cells(1, 1).Resize(UBound(a), 1)
  r.Value2 = rangeVariant(a)
  r.Sort Key1:=r.Cells(1), Order1:=xlDescending
  va = r.Value2
  GetColumn va, a, 1
  ws.Delete
  quietMode qmOFF

End Sub

Function rangeVariant(a As Variant) As Variant
Dim va As Variant, i As Long

  ReDim va(LBound(a) To UBound(a), 0)

  For i = LBound(a) To UBound(a)
    va(i, 0) = a(i)
  Next i
  rangeVariant = va

End Function

Sub quietMode(state As qmState)
Static currentState As Boolean

  With Application

    Select Case state
    Case qmON
      currentState = .ScreenUpdating
      If currentState Then .ScreenUpdating = False
      .Calculation = xlCalculationManual
      .DisplayAlerts = False
    Case qmOFF
      If currentState Then .ScreenUpdating = True
      .Calculation = xlCalculationAutomatic
      .DisplayAlerts = True
    Case Else
    End Select

  End With
End Sub