VBA二维数组自定义排序
缘起
排序是Coder最常做的事情之一。对于Excel来说,常常是二维表的排序,今天我们权且当练练手,做一个冒泡排序。
- 时间复杂度:
- 平均情况:O(n^2)
- 最好情况: O (n)
- 最坏情况:O(n^2)
- 空间复杂度: O (1)
- 稳定性: 稳定
- 基本思想
- 重复的遍历待排序的数组,依次比较两个相邻的元素,若它们的顺序错误则调换位置,直至没有元素再需要交换为止。
- 具体步骤
- '比较两个相邻元素,如果前一个比后一个大,则交换这两个相邻元素
- '从头至尾对每一对相邻元素进行步骤1的操作,完成1次对整个待排序数字列表的遍历后,最大的元素就放在了该列表的最后一个位置上了
- '对除最后一个元素的所有元素重复上述步骤,这第二次遍历后第二大的元素就也放在了正确的位置(整个列表的倒数第二位置上)
- '不断重复上述步骤,每次遍历都会将一个元素放在正确的位置上,从而下次遍历的元素也会随之减少一个,直至没有任何一对数字需要比较
实现
类名:mySorts
'本类仅用于VBA,不可直接用于VB.net
'Base CodeFrom https://blog.csdn.net/m0_46067540/article/details/108941053
'原排序BubbleSort()原封未动
'该类用于二维数组的排序,包括
'直接赋予数组或直接赋予表格地址
'正序和逆序排列
'定义用于排序的列
'表头行(在数组内但不用于排序)
'改善部分:原排序修改为类,更方便使用
'2022/11/29
Option Explicit
Private myArray(), mySortArray()
Private myPositive As Boolean
Private myColumn As Long
Private myTitleRows As Integer
'----初始化----------------------------------------------------
Private Sub Class_Initialize()
myPositive = True
myColumn = 1
myTitleRows = 0
End Sub
'--------------------------------------------------------------
'----获取二维数组----------------------------------------------
Public Property Let Arr(ByVal Val)
If VBA.IsObject(Val) Then
myArray = Val.Value
myArray = Val
End If
End Property
Public Property Get Arr()
Arr = myArray
End Property
'--------------------------------------------------------------
'----设定Sort正反序--------------------------------------------
Public Property Let Positive(ByVal Val As Boolean)
myPositive = Val
End Property
Public Property Get Positive() As Boolean
Positive = myPositive
End Property
'--------------------------------------------------------------
'----设定排序列------------------------------------------------
Public Property Let Column(ByVal Val As Long)
myColumn = Val
End Property
Public Property Get Column() As Long
Column = myColumn
End Property
'--------------------------------------------------------------
'----设定标题行数----------------------------------------------
Public Property Let TitleRows(ByVal Val As Integer)
myTitleRows = Val
End Property
Public Property Get TitleRows() As Integer
TitleRows = myTitleRows
End Property
'--------------------------------------------------------------
'----一趟水的做法----------------------------------------------
Public Function SetAll(setArr, setPositive As Boolean, setColumn As Long, setTitleRows As Integer)
If VBA.IsObject(setArr) Then
myArray = setArr.Value
myArray = setArr
End If
myPositive = setPositive
myColumn = setColumn
myTitleRows = setTitleRows
Call DoSort
SetAll = mySortArray
End Function
'--------------------------------------------------------------
'----分步的做法------------------------------------------------
Public Property Get SortArray()
DoSort
SortArray = mySortArray
End Property
'--------------------------------------------------------------
'----核心排序--------------------------------------------------------------------------------------
Private Sub DoSort()
mySortArray = myArray
mySortArray = BubbleSort(mySortArray, myPositive, myColumn, myTitleRows)
End Sub
Private Function BubbleSort(ByRef SnArray(), Sort As Boolean, Column As Long, Title As Integer) '冒泡排序
'Sort 为升降序标记
'Column为需排序列
'Title为标题行数(不参与排序的行数)
Dim IOuter As Long, IInner As Long, ILbound As Long, IUbound As Long
Dim IsSort As Boolean
Dim Count As Integer, Temp As Integer, SORTED As Integer
Dim Lastindex As Double, TLbound As Double, TUbound As Double
Dim Itemp
ReDim Itemp(1, LBound(SnArray, 2) To UBound(SnArray, 2))
Lastindex = 0
TLbound = LBound(SnArray, 2)
TUbound = UBound(SnArray, 2)
ILbound = LBound(SnArray) + Title
IUbound = UBound(SnArray)
SORTED = IUbound - IOuter - 1
Select Case Sort
Case 0 '参数为0时升序
For IOuter = ILbound To IUbound - 1
IsSort = True
For IInner = ILbound To SORTED 'iubound - iouter - 1
If SnArray(IInner, Column) > SnArray(IInner + 1, Column) Then
For Temp = TLbound To TUbound '数组整行数据交换
Itemp(1, Temp) = SnArray(IInner, Temp)
SnArray(IInner, Temp) = SnArray(IInner + 1, Temp)
SnArray(IInner + 1, Temp) = Itemp(1, Temp)
Next Temp
IsSort = False '标记是否有排序动作
Count = Count + 1 '记录排序次数,可删除
Lastindex = IInner '记录最后排序位置
End If
Next IInner
If IsSort = True Then Exit For '如果没有排序动作则为全部排序完成,跳出循环,排序结束
SORTED = Lastindex '接下来的循环只到最后排序位置
Next IOuter
Case 1 '参数为1时降序
For IOuter = ILbound To IUbound - 1
IsSort = True
For IInner = ILbound To SORTED 'iubound - iouter - 1
If SnArray(IInner, Column) < SnArray(IInner + 1, Column) Then
For Temp = TLbound To TUbound '数组整行数据交换
Itemp(1, Temp) = SnArray(IInner, Temp)
SnArray(IInner, Temp) = SnArray(IInner + 1, Temp)
SnArray(IInner + 1, Temp) = Itemp(1, Temp)
Next Temp
IsSort = False '标记是否有排序动作
Count = Count + 1 '记录排序次数,可删除
Lastindex = IInner '记录最后排序位置
End If
Next IInner
If IsSort = True Then Exit For '如果没有排序动作则为全部排序完成,跳出循环,排序结束
SORTED = Lastindex '接下来的循环只到最后排序位置
Next IOuter
Case Else
End Select
'Sheets(1).Range("I1") = Count
BubbleSort = SnArray
End Function
'--------------------------------------------------------------------------------------------------
调用
新建模块测试类的结果。如有Bug则有必要调整输入内容的格式,比如某些长纯数字字符串代表的编码。
'本类仅用于VBA,不可直接用于VB.net
Private Sub SortsTest()
'锁定表,具体SthName请自行指定
Dim SthName As String
SthName = "指定的表名"
'锁定单元格,具体Range地址请自行指定
Dim GetAddress As String, PutAddress As String
GetAddress = "F13:K24"
PutAddress = "M13:R24"
'With方式
With New mySorts
Sheets(SthName).Range(PutAddress) = .SetAll(Sheets(SthName).Range(GetAddress), 1, 2, 0)
End With
'定义类方式
Dim NewSort As New mySorts
'极简方式
Sheets(SthName).Range(PutAddress) = NewSort.SetAll(Sheets(SthName).Range(GetAddress), 1, 2, 0)
'分步方式
'以下两种定义方式结果一致
NewSort.Arr = Sheets(SthName).Range(GetAddress).value
NewSort.Arr = Sheets(SthName).Range(GetAddress)