VBA学习笔记四:数组

VBA学习笔记四:数组第 4 部分数组 介绍了数组的基本概念 在 VBA 中声明数组的方法 如何写入数组以及数组常用的功能 包括数组元素的处理 清空数组 数组之间的转置和结合工作表函数使用的方法

大家好,欢迎来到IT知识分享网。

第4部分  数组

4.1 数组的基本概念

4.1.1 数组元素

  • 元素个数:通常情况下,数组的下标从 0 开始计数,例如:dim arr(4)表示数组的索引范围为 0 到 4,共有 5个元素

4.1.2 数组类型

  • 一维数组:只包含一行或一列元素
  • 二维数组:是一个表格形式的数据结构,具有行和列的概念。通过两个索引可以访问二维数组中的元素,第一个索引表示行,第二个索引表示列
  • 多维数组:除了二维数组外,VBA 还支持多维数组,可以是三维、四维甚至更高维度的
  • 嵌套数组:将一个数组作为另一个数组的元素之一,可以理解为数组中包含数组,由多个数组组合在一起形成的一个整体的多层次结构,需要注意的是每一个子数组的大小也可以不同
    • 嵌套数组理解参考:VBA数组嵌套的问题-Excel VBA程序开发-ExcelHome技术论坛 –
  • 以下是不同的数组类型在本地窗口的展开,本地窗口展开的次数与数组的维度相关
VBA学习笔记四:数组
一维数组

          

VBA学习笔记四:数组
二维数组

VBA学习笔记四:数组
三维数组

          

VBA学习笔记四:数组
嵌套数组

4.1.3 数组上限和下限

  • UBound:函数返回数组的限,可以指定维度,比如行或者列,未指定维度,则默认为 1
  • LBound:函数返回数组的限,可以指定维度,比如行或者列,未指定维度,则默认为 1
    Dim matrix(1 To 5, 1 To 3) As Integer ' 声明一个 5 行 3 列的整数数组 Debug.Print LBound(matrix, 1) ' 输出第一维的下限(1) Debug.Print UBound(matrix, 1) ' 输出第一维的上限(5) Debug.Print LBound(matrix, 2) ' 输出第二维的下限(1) Debug.Print UBound(matrix, 2) ' 输出第二维的上限(3) 

4.2   数组声明

4.2.1 静态数组

特点:数组大小固定,声明时可以指定数据元素的个数、维度以及数据类型

  • 一维数组声明:
    Dim arr1D(5) As Integer ' 声明一个包含 6 个元素的一维整数数组 Dim arr1D() As String ' 声明一个字符串类型的一维数组 
  • 多维数组声明:
    Dim arr2D(5, 3) As Double ' 声明一个 6 行 4 列的二维浮点数数组 Dim arr3D(5, 3, 2) As String ' 声明一个 6 行 4 列 3 层的三维字符串数组 
  • 指定下标起始值:设置数组的下标从几开始计数
    Dim arr1D(1 To 5) As Integer ' 声明一个从 1 到 5 的整数数组 

4.2.2 动态数组

特点:在运行时可以动态调整数组大小

  • 动态数组声明
    Dim dynamicArr() As Integer ' 声明一个动态整数数组 
  • ReDim 语句:调整动态数组的大小,并且重新初始化数据内容
    Dim arr() As Integer ' 声明一个动态数组 ReDim arr(5) ' 初始化数组为包含 6 个元素的数组 
  • ReDim Preserve语句:调整数组大小并保留数组中已有数据的语句
    ' 示例:把数组 arr 从长度6调整为11,并保留原数据 Dim arr(5) As Integer ' 声明一个长度为 6 的整数数组 Dim i As Integer ' 给数组赋值 For i = 0 To 5 arr(i) = i * 10 Next i ' 调整数组大小并保留数据 ReDim Preserve arr(10) ' 输出调整后的数组内容 For i = 0 To 10 Debug.Print arr(i) Next i 

4.2.3 隐式类型声明

特点:省略数组声明时的数据类型

  • 静态数组:例如Dim arr
  • 动态数组:例如Dim arr()

4.3  数组写入

  • 单元格数据赋值:例如arr = Range(“A1:A3”)
  • 单元格数据手动写入:选中一部分单元格,按F9键可以转换为数组,再加[ ]便可以手动写入
VBA学习笔记四:数组
图一:选中一部分单元格

VBA学习笔记四:数组
图二:按f9

VBA学习笔记四:数组
图三:加[ ]可以手动写入

          

  • Array 函数初始化数组:通过Array把要写入的数据连接在一起,例如arr = Array(1, 2, 3, 4)
  • 注意点:以上三种方法在声明时不要声明为静态数组(即固定数组大小),并且需要把数组声明成Variant 类型,否则会报错
  • 代码循环写入:通过for循环写入,需要注意数组的维度和数据类型;一般情况下循环次数等于数组的元素数量,循环的嵌套次数等于数组的维度

4.4  数组元素处理

4.4.1 Split 拆分

  • 功能:用于将字符串分割为子字符串并存储在数组中
  • 语法:Split(expression, delimiter, limit, compare)
  • 参数:
    • expression:要分割的字符串
    • delimiter:分隔符,用于指定在哪些字符处分割字符串。如果省略该参数,则默认使用空格作为分隔符
    • limit:可选参数,指定返回的数组的最大维度(字符串的个数)。如果省略该参数或设置为 -1,则返回包含所有分割项的数组
    • compare:可选参数,用于指定字符串比较方式。可以是以下常量:
      • vbBinaryCompare:二进制比较(默认),可以用数字0代替
      • vbTextCompare:文本比较,不区分大小写,可以用数字1代替

4.4.2 Join 合并

  • 功能:将数组元素连接成一个字符串,可以在连接时加入特殊符号标记作为分隔符
  • 语法:Join(array, delimiter)
  • 参数:
    • array: 要连接的数组
    • delimiter: 连接数组元素时使用的分隔符
  • 示例:将数组 arr 中的字符串以”e”为分隔符进行分割,并用”-“连接写入新的数组 brr 中
    Sub SplitAndCopyToArray() Dim arr() As String Dim brr() As String Dim str As String Dim splitArr() As String Dim i As Integer ' 假设 arr 包含需要分割的字符串 arr = Split("Apple,Orange,Banana", ",") ' 初始化 brr 数组,长度与 arr 相同 ReDim brr(0 To UBound(arr)) ' 使用 For 循环遍历 arr 中的每个元素 For i = LBound(arr) To UBound(arr) ' 使用 Split 方法将字符串分割为子字符串,并存储到 splitArr 数组中 splitArr = Split(arr(i), "e") ' 将分割后的子字符串重新组合为新的字符串,存储到 brr 数组中对应的位置 brr(i) = Join(splitArr, "-") Next i ' 输出结果,检查 brr 数组中的内容 For i = LBound(brr) To UBound(brr) Debug.Print brr(i) Next i End Sub 

4.4.3 Filter 筛选

  • 功能:筛选数组中符合条件的元素
  • 语法:Filter(sourcearray, match, [ include, [ compare ]])
  • 参数:
    • sourcearray:要筛选的一维数组,如果不是一维数组,先用join把多维数组中的字符串连接起来再分割
    • match:筛选条件,通常为字符串
    • include:可选,True表示包含,False表示不包含
    • compare:0区分大小写,1不区分大小写
  • 示例:筛选数组中包含“an”的元素,并赋值给新的数组
    Sub TestFilter() Dim arr As Variant Dim filteredArr As Variant ' 初始化原始数组 arr = Array("apple", "banana", "cherry", "grape", "orange") ' 调用 Filter 函数进行筛选包含"an"的元素 filteredArr = Filter(arr, "an") ' 在 Immediate 窗口显示筛选后的数组 Dim i As Integer For i = LBound(filteredArr) To UBound(filteredArr) Debug.Print filteredArr(i) Next i End Sub 

4.5  清空数组

  1. 重新声明数组:通过重新声明数组,把数组初始化为一个空数组
    Dim arr() As String ' 声明一个字符串数组 ReDim arr(0) ' 重新初始化数组,长度为0,即为空数组 
  2. 使用Erase关键字:Erase 数组,会释放数组占用的内存,并将数组重置为空数组
    Dim arr() As String ' 声明一个字符串数组 Erase arr ' 清空数组,arr现在是一个空数组 

4.6  数组转置

转置前 转置后 转置次数
单列(n行1列)二维数组 一维数组 1次
单行(1行n列)二维数组(先变成列二维再转一维) 一维数组 2次
多行多列二维数组 多行多列二维数组(行列互换) 1次
嵌套数组 二维数组 2次

示例:在Sheet1工作表A1:D1中写入随机数值,在本地窗口可以查看数组的状态

Sub TransposeExample() Dim ws As Worksheet Dim rng As Range Dim arr1 As Variant, arr2 As Variant, flatArr As Variant ' 获取工作表对象 Set ws = ThisWorkbook.Sheets("Sheet1") ' 获取范围对象(假设范围是单行) Set rng = ws.Range("A1:D1") ' 从范围获取二维数组(单行) arr1 = rng.value ' 第一次转置(行变列,变成单列的二维数组) arr2 = Application.Transpose(arr1) ' 第二次转置(单列的二维数组变成一维数组) flatArr = Application.Transpose(arr2) End Sub

引申:EXCEL中一列(行)转多行多列或多行多列转一列(行) – 知乎 (zhihu.com)

4.7  结合工作表函数

  • INDEX 函数
    • 功能:返回指定单元格或单元格数组的值
    • 语法:INDEX(array, row_num, [column_num])
    • 参数:
      • array:要从中返回值的数组或范围
      • row_num:要返回的值所在的行号。如果省略 row_num,则需使用 column_num
      • column_num:要返回的值所在的列号。如果省略 column_num,则需使用 row_num
      • 备注:如果将 row_num 或 column_num 设置为 0(零),则 INDEX 将分别返回整列或整行值的数组
    • 示例:Application.index(arr, 0, 1) 返回arr第一列的数组; Application.index(arr, 2, 2) 返回 arr 中第 3 行、第 2 列的值
  • MATCH 函数:
    • 功能:在数组或范围中查找值,并返回其位置
    • 语法:MATCH(lookup_value, lookup_array, [match_type])
    • 参数:
      • lookup_value:要查找的值
      • lookup_array:要在其中查找值的数组或范围
      • match_type:可选参数,指定查找方式,有 1(小于等于)、0(等于)和 -1(大于等于)三种类型。默认为 1
      • 备注:match_type为1时,数组必须以升序排序;为-1时,数组必须以降序排序
    • 示例:Application.Match(“Carol”, brr, 0) 在数组 brr 中查找值为 “Carol” 的位置,精确匹配
  • 综合使用示例:假设有一个名为 “Data” 的工作表,包含学生姓名和对应的成绩,数据如下,找到 “Carol” 的成绩并输出
    A列 B列
    Name Score
    Alice 85
    Bob 92
    Carol 78
    Dave 88
    Ellen 95

    代码如下:

    Sub SearchStudentScore() ' 声明工作表和数组变量 Dim ws As Worksheet Dim arr As Variant Dim brr As Variant Dim searchName As String Dim nameLocation As Integer Dim score As Double ' 忽略错误并继续运行,避免学生姓名不在列表时的报错 On Error Resume Next ' 设置工作表 Set ws = ThisWorkbook.Sheets("Data") ' 将数据范围写入数组 arr = ws.Range("A2:B6").value ' 要查找的学生姓名 searchName = "Carol" ' 使用INDEX获取学生姓名列 brr = Application.index(arr, 0, 1) ' 使用MATCH查找学生姓名在数组中的位置,精确匹配 nameLocation = Application.Match(searchName, brr, 0) ' 判断学生是否存在并获取成绩 If Not IsError(studentLocation) Then ' 使用INDEX获取学生对应的成绩 score = Application.index(arr, nameLocation, 2) MsgBox searchName & "的成绩是:" & score Else MsgBox searchName & "不存在于列表中。" End If End Sub

4.8  数组排序

功能:将数组中的元素按升序或降序排列

说明:排序通常有两层循环,外层循环用于控制迭代次数,每次迭代都会将当前未排序的部分缩小一点,内层循环则用于对未排序部分中的元素进行两两比较,并根据排序规则决定是否交换它们的位置

时间复杂度:描述算法执行时间随输入数据规模增长而变化的规律

4.8.1 冒泡排序(Bubble Sort)

  • 方法:重复地遍历数组,一次比较两个相邻的元素,并且如果它们的顺序错误就把它们交换
  • 案例解释:以升序的方式排列数组中的数据,从第一个数开始每两个数进行比较,如果第一个数大于第二个数则进行交换,第一次循环结束后末尾的值为最大值,然后从第一个数到倒数第二数两两比较,以此类推直到排序完成
    • 步骤
      • 第一次外循环(比较相邻元素并交换):
        • 比较 53,交换:[3, 5, 8, 1, 4]
        • 比较 58,不交换:[3, 5, 8, 1, 4]
        • 比较 81,交换:[3, 5, 1, 8, 4]
        • 比较 84,交换:[3, 5, 1, 4, 8]
        • 第一次循环结束后末尾的值为最大值
      • 第二次外循环(从第一个数到倒数第二个数两两比较):
        • 比较 35,不交换:[3, 5, 1, 4, 8]
        • 比较 51,交换:[3, 1, 5, 4, 8]
        • 比较 54,交换:[3, 1, 4, 5, 8]
      • 第三次外循环(从第一个数到倒数第三个数两两比较):
        • 比较 31,交换:[1, 3, 4, 5, 8]
        • 比较 34,不交换:[1, 3, 4, 5, 8]
      • 第四次外循环(从第一个数到倒数第四个数两两比较):
        • 比较 13,不交换:[1, 3, 4, 5, 8]

    初始数组[5, 3, 8, 1, 4]

                最终数组[1, 3, 4, 5, 8]

  • 扑克牌解释:想象你手上拿着一副杂乱无章的扑克牌,你的目标是让它们按照从小到大的顺序排列。你会从最底下开始,逐个比较相邻的牌,如果它们的顺序错了,你就交换它们的位置,直到没有任何两张牌需要交换为止。这就好像你不断地冒泡最大的牌到顶端一样,直到所有的牌都有序
  • 备注:交换顺序先把移动的值a储存在一个变量中,另外一个值b移动在原先a的位置后,再把变量(a的值)赋值给b的位置
  • 交换次数:取决于数组的大小n和数组中元素的顺序,最坏情况下为n*(n-1)/2次(排序顺序和数组顺序相反)
  • 时间复杂度:O(n^2),不适合处理大规模数据
  • 优点:实现简单,容易理解和实现

4.8.2 选择排序(Selection Sort)

  • 方法:通过每次遍历数组找到最小(或最大)的元素,存放在序列的起始位置,直到整个数组排序完成
  • 案例解释:以升序的方式排列数组中的数据,先假设第一个数是最小值,然后从第一个数开始和后面的每个数进行比较,找到数组中的最小值并与其交换位置,再假设第二个数是最小值和后面的数进行比较,以此类推直到排序完成
    • 步骤
    • 第一次外循环(第一个数开始和后面的每个数进行比较,找到最小值并交换到第一个位置):
      • 比较 53,最小值是 3
      • 比较 38,最小值是 3
      • 比较 31,最小值是 1
      • 比较 14,最小值是 1
      • 交换 15[1, 3, 8, 5, 4]
    • 第二次外循环(第二个数开始和后面的每个数进行比较,找到最小值并交换到第二个位置):
      • 比较 38,最小值是 3
      • 比较 35,最小值是 3
      • 比较 34,最小值是 3
      • 不交换:[1, 3, 8, 5, 4]
    • 第三次外循环(第三个数开始和后面的每个数进行比较,找到最小值并交换到第三个位置):
      • 比较 85,最小值是 5
      • 比较 54,最小值是 4
      • 交换 48[1, 3, 4, 5, 8]
    • 第四次外循环(第四个数开始和后面的每个数进行比较,找到最小值并交换到第四个位置):
      • 比较 58,最小值是 5
      • 不交换:[1, 3, 4, 5, 8]

    初始数组[5, 3, 8, 1, 4]

        最终数组[1, 3, 4, 5, 8]

  • 扑克牌解释:想象你还是手里有一副无序的扑克牌,但这次你打算通过每次选择最小的牌来整理它们。你会从牌堆里挑选出最小的一张,然后放到已经整理好的牌的堆顶。接着,你再从剩下的牌中选择出最小的,放到已经排好序的牌的下一张。你像这样一直重复,直到所有的牌都被选择并放置到正确的位置。
  • 交换次数:取决于数组的大小n和数组中元素的顺序,最坏情况下为n-1次
  • 时间复杂度:O(n^2),不适合处理大规模数据
  • 优点:实现简单,不占用额外的内存空间
  • 示例代码:数组arr中的元素分别为5-3-8-1-4,用两种方式分别进行排序 执行情况:
    Sub SortArrayExample() ' 创建包含一行数据的数组 Dim rowData(1 To 5) As Integer ' 假设初始数据为:5, 3, 8, 1, 4 rowData(1) = 5 rowData(2) = 3 rowData(3) = 8 rowData(4) = 1 rowData(5) = 4 ' 输出原始数据 Debug.Print "原始数据:" PrintArray rowData Dim sortMethod As VbMsgBoxResult sortMethod = MsgBox("请选择排序方式:是-冒泡排序,否-选择排序", vbYesNo) If sortMethod = vbYes Then ' 使用冒泡排序对数组进行排序 BubbleSort rowData Else ' 使用选择排序对数组进行排序 SelectionSort rowData End If ' 输出排序后的数据 Debug.Print "排序后的数据:" PrintArray rowData End Sub ' 打印数组 Sub PrintArray(arr() As Integer) Dim i As Integer For i = LBound(arr) To UBound(arr) Debug.Print arr(i); Next i Debug.Print End Sub ' 冒泡排序算法 Sub BubbleSort(arr() As Integer) Dim n As Integer ' 数组长度 Dim i As Integer ' 外层循环计数器 Dim j As Integer ' 内层循环计数器 Dim temp As Integer ' 临时变量,用于交换数组元素 Dim swapCount As Integer ' 交换次数 n = UBound(arr) - LBound(arr) + 1 ' 获取数组长度 ' 外层循环,遍历数组 For i = 1 To n - 1 ' 内层循环,每次遍历比较相邻两个元素并交换 For j = 1 To n - i If arr(j) > arr(j + 1) Then ' 如果前一个元素大于后一个元素,则交换它们 temp = arr(j) arr(j) = arr(j + 1) arr(j + 1) = temp swapCount = swapCount + 1 ' 打印交换后的数组和外层循环次数 Debug.Print "第 " & i & " 次外层循环,第 " & swapCount & " 次交换后的数组:" PrintArray arr End If Next j Next i End Sub ' 选择排序算法 Sub SelectionSort(arr() As Integer) Dim n As Integer ' 数组长度 Dim i As Integer ' 外层循环计数器 Dim j As Integer ' 内层循环计数器 Dim minIndex As Integer ' 最小值的索引 Dim temp As Integer ' 临时变量,用于交换数组元素 Dim swapCount As Integer ' 交换次数 n = UBound(arr) - LBound(arr) + 1 ' 获取数组长度 ' 外层循环,遍历数组 For i = 1 To n - 1 minIndex = i ' 假设当前索引处的值是最小值 ' 内层循环,从下一个元素开始找到最小值的索引 For j = i + 1 To n If arr(j) < arr(minIndex) Then minIndex = j ' 更新最小值的索引 End If Next j ' 如果找到的最小值的索引不是当前位置,进行交换 If minIndex <> i Then ' 将找到的最小值与当前元素交换 temp = arr(i) arr(i) = arr(minIndex) arr(minIndex) = temp swapCount = swapCount + 1 ' 打印交换后的数组和外层循环次数 Debug.Print "第 " & i & " 次外层循环,第 " & swapCount & " 次交换后的数组:" PrintArray arr End If Next i End Sub 
VBA学习笔记四:数组
冒泡循环,交换6次

VBA学习笔记四:数组
选择循环,交换2次

4.8.3 插入排序(Insertion Sort)

  • 方法:每次将一个待排序的元素,按其关键字的大小插入到前面已经排好序的子序列中去,直到全部插入完为止
  • 案例解释:以升序的方式排列数组中的数据,外层循环从第二个数开始,和前面的每个数进行比较(从离自己最近的数开始),并决定是否交换位置,每结束一次外层循环代表这个数前面的顺序已经排列好了,然后第二次外层循环从第三个数开始,继续和前面的数比较找到合适的位置,以此类推直到外层循环到最后一个数,排序完成
    • 步骤
    • 第一次外循环(从第二个数开始,和前面的每个数比较,将第二个元素插入到合适位置):
      • 比较 53,交换:[3, 5, 8, 1, 4]
    • 第二次外循环(从第三个数开始,和前面的每个数比较,将第三个元素插入到合适位置):
      • 比较 58,不交换:[3, 5, 8, 1, 4]
    • 第三次外循环(从第四个数开始,和前面的每个数比较,将第四个元素插入到合适位置):
      • 比较 81,交换:[3, 5, 1, 8, 4]
      • 比较 51,交换:[3, 1, 5, 8, 4]
      • 比较 31,交换:[1, 3, 5, 8, 4]
    • 第四次外循环(从第五个数开始,和前面的每个数比较,将第五个元素插入到合适位置):
      • 比较 84,交换:[1, 3, 5, 4, 8]
      • 比较 54,交换:[1, 3, 4, 5, 8]
    • 最终数组[1, 3, 4, 5, 8]

    初始数组[5, 3, 8, 1, 4]

  • 扑克牌解释:想象你面前有一张桌子,上面有几张已经有序的扑克牌。你手里拿着一叠乱序的牌,你的任务是把每张牌插入到正确的位置,以使整个桌面的牌都有序。你会逐个取出手上的牌,然后在已经有序的牌中找到合适的位置,将这张牌插入进去。你会重复这个过程,直到手上的牌都被插入到了正确的位置。
  • 交换次数:取决于数组的大小n和数组中元素的顺序,和冒泡排序一样最坏情况下为n*(n-1)/2次(排序顺序和数组顺序相反)
  • 时间复杂度:O(n^2),对于大规模数据排序性能仍然不理想
  • 优点:对于部分有序的数据较为高效,性能优于冒泡排序和选择排序
  • 示例:
    Sub InsertionSortExample() Dim rowData(1 To 5) As Integer ' 假设初始数据为:5, 3, 8, 1, 4 rowData(1) = 5 rowData(2) = 3 rowData(3) = 8 rowData(4) = 1 rowData(5) = 4 ' 输出原始数据 Debug.Print "原始数据:" PrintArray rowData ' 使用插入排序对数组进行排序 InsertionSort rowData ' 输出排序后的数据 Debug.Print "排序后的数据:" PrintArray rowData End Sub ' 打印数组 Sub PrintArray(arr() As Integer) Dim i As Integer For i = LBound(arr) To UBound(arr) Debug.Print arr(i); Next i Debug.Print End Sub ' 插入排序算法 Sub InsertionSort(arr() As Integer) Dim i As Integer, j As Integer 'i为外层循环计数器,j为内层循环计数器 Dim key As Integer Dim loopCount As Integer, swapCount As Integer, temp As Integer ' 记录循环次数和交换次数的变量初始化 loopCount = 0 swapCount = 0 ' 外层循环,从第二个元素开始,依次插入到已排序的部分中 For i = LBound(arr) + 1 To UBound(arr) key = arr(i) ' 当前要比较的元素 j = i - 1 ' j为当前元素的前一个元素 loopCount = loopCount + 1 ' 记录外层循环次数 ' 内层循环,将大于key的元素向后移动 Do While j >= LBound(arr) And arr(j) > key swapCount = swapCount + 1 ' 记录交换次数 temp = arr(j) arr(j) = arr(j + 1) arr(j + 1) = temp j = j - 1 If j < 1 Then Exit Do ' 已到达数组起始位置,结束内层循环 Loop ' 打印每次排序后的数组和交换次数 Debug.Print "外层循环次数: " & loopCount & ", 交换次数: " & swapCount PrintArray arr ' 调用PrintArray子过程打印当前数组 Next i End Sub 
    VBA学习笔记四:数组
    插入排序,交换次数6

4.8.4 快速排序(Quick Sort)

  • 方法:通过选择一个基准点(通常为第一个元素、最后一个元素或中间元素),然后将数组分成两个部分,一部分比基准点小,另一部分比基准点大,再递归地对这两部分进行排序
  • 注意:以基准点对其他数据进行排序的时候,数据之间不会进行大小比较,只考虑是否比基准点大或者小(也就是说一边的数据比基准数小,一边的数比基准点大),它们之间的顺序按照和基准点比较的先后顺序排列
  • 案例解释:先选择3作为基准值,从左到右找到第一个大于基准元素的元素,并从右到左找到第一个小于或等于基准元素的元素,如果找到的两个元素位置没有交叉,则交换它们,交换后左右指针同时往内移动一位,直到两个元素位置有交叉,把基准点放在恰当的位置。此时一边的数据比基准点大,一边比基准点小,而它们之间没有排序,所以继续对左右两边的数据按照上面的方法递归排序,直到最后全部排序完成。
    • 初始数组: [3, 6, 8, 10, 1, 2, 1]
    • 选择基准点:选择 3 作为基准点。
      • 从右到左找到第一个小于或等于基准元素的元素
      • 如果找到的两个元素位置没有交叉,则交换它们,交换后左右指针同时往内移动一位
      • 当找到的两个元素位置有交叉时,代表两边都已排序完成
        • 交换 6 和 1:[3, 1, 8, 10, 1, 2, 6]
        • 交换 8 和 2:[3, 1, 2, 10, 1, 8, 6]
        • 交换 10 和 1: [3, 1, 2, 1, 10, 8, 6]
      • ​​​基准元素位置调整:将基准元素放到正确的位置
        • 继续从右边找到一个小于等于基准的数,交换位置,本例中为基准 3 与 1交换
      • 划分结果:[1, 1, 2, 3, 10, 8, 6]

      从左到右找到第一个大于基准元素的元素

    • Step 2: 递归排序左子数组
      • 对基准 3 左边的子数组 [1, 1, 2] 进行排序
      • 选择 1 作为基准,找到的两个元素位置有交叉,基准位置调整1和1后得到:[1, 1, 2]
      • 左边 [1] 和右边 [2] 已经有序,不需要进一步操作
    • Step 3: 递归排序右子数组
      • 对基准 3 右边的子数组 [10, 8, 6] 进行排序。
      • 选择 10 作为基准,8和6都比10小,基准位置调整10和6后得到:[6, 8, 10]
      • 递归地对左边 [6, 8] 进行排序,选择 6 作为基准,把6放到正确位置(此时已经基准已经在正确位置,无需实际交换,只是代码会运行一遍)分区后得到:[6, 8]
      • 左边 [6] 和右边 [8] 已经有序,不需要进一步操作。

    Step 1: 划分数组

              最终结果:[1, 1, 2, 3, 6, 8, 10]

  • 扑克牌解释:选取扑克牌中的一张作为“基准”值,将扑克牌分成两堆,左边的牌比基准值小,右边的牌比基准值大。然后递归地对这两堆牌进行同样的操作,最终所有牌都按从小到大的顺序排列
  • 交换次数:取决于输入数组的排列情况以及选择的基准元素,本案例为4+1+1+1,一共7次
  • 时间复杂度:平均情况下时间复杂度为 O(nlogn),最坏情况下时间复杂度为 O(n2),但可以通过随机化基准元素选择来缓解
  • 优点:适用于处理大规模数据
  • 示例:
    Sub TestQuickSort() Dim arr() As Variant Dim i As Integer ' 初始化数组 arr = Array(3, 6, 8, 10, 1, 2, 1) ' 调用 QuickSort 对数组进行排序 Debug.Print "整个数组分区" QuickSort arr, LBound(arr), UBound(arr) ' 打印排序后的数组 Debug.Print "排序后的数组:" PrintArray arr End Sub Sub QuickSort(arr() As Variant, low As Long, high As Long) Dim pivot As Variant Dim i As Integer, j As Integer, swapCount As Integer Dim temp As Variant ' 如果低索引小于高索引,进行排序 If low < high Then ' 选择数组的第一个元素作为基准元素 pivot = arr(low) i = low j = high ' 开始分区过程 Do While i < j ' 从左到右找到第一个大于基准元素的元素 Do While arr(i) <= pivot And i < high i = i + 1 Loop ' 从右到左找到第一个小于或等于基准元素的元素 Do While arr(j) > pivot j = j - 1 Loop ' 如果找到的两个元素位置没有交叉,则交换它们 If i < j Then ' 交换 arr(i) 和 arr(j) temp = arr(i) arr(i) = arr(j) arr(j) = temp swapCount = swapCount + 1 ' 打印当前数组状态 Debug.Print "左指针:" & i & ",右指针:" & j & ",交换次数:" & swapCount PrintArray arr End If Loop ' 将基准元素放到正确的位置 arr(low) = arr(j) arr(j) = pivot swapCount = swapCount + 1 Debug.Print "将基准元素放到正确的位置,交换次数:" & swapCount PrintArray arr ' 递归地对左右子数组进行排序 ' 如果左子数组大小大于1,才进行排序并打印 If low < j - 1 Then Debug.Print "左边子数组递归排序" QuickSort arr, low, j - 1 End If ' 如果右子数组大小大于1,才进行排序并打印 If j + 1 < high Then Debug.Print "右边子数组递归排序" QuickSort arr, j + 1, high End If End If End Sub Sub PrintArray(arr() As Variant) Dim i As Integer ' 打印数组为一行 For i = LBound(arr) To UBound(arr) Debug.Print arr(i); Next i Debug.Print "" ' 换行 End Sub 
    VBA学习笔记四:数组
    快速排序

4.8.5 归并排序(Merge Sort)

  • 什么是递归:
    • 日常生活中的递归例子:拆礼物盒
    • 假设你收到了一系列嵌套的礼物盒,每个盒子里还有一个更小的礼物盒,直到最里面的盒子里有真正的礼物。你需要一层一层地打开这些盒子,直到找到最终的礼物,然后再将每层盒子按顺序重新盖好。每次你打开一个盒子(进行递归调用),你都需要记住要把打开的盒子恢复原样(返回上一层递归)。最终,当你完成所有的操作时,你不仅找到了礼物,还恢复了所有的盒子。
    • 结论:每次递归完成后,返回上一层递归,直到程序结束
    • 判断递归是否完成:观察每一层递归是否运行到当前代码的 End Sub 
  • 归并排序:
    • 方法:先分割后合并,将数组从中间分为两个子数组,递归地对这两个子数组进行排序;再将两个已经排好序的子数组合并为一个有序的数组
    • 注意:每次递归完成后,返回上一层递归
    • 案例解释:
      • Step 2: 递归排序左半部分
        • 初次调用 MergeSort(arr, 0, 5)
          • mid = 0 + (5 - 0) \ 2 = 2
          • 递归调用 MergeSort(arr, 0, 2)
        • 第二次调用 MergeSort(arr, 0, 2)
          • mid = 0 + (2 - 0) \ 2 = 1
          • 递归调用 MergeSort(arr, 0, 1)
        • 第三次调用 MergeSort(arr, 0, 1)
          • mid = 0 + (1 - 0) \ 2 = 0
          • 递归调用 MergeSort(arr, 0, 0)(由于 left 不小于 right,直接返回)
          • 递归调用 MergeSort(arr, 1, 1)(由于 left 不小于 right,直接返回)
      • Step 3: 合并左半部分
        • 初次合并 Merge(arr, 0, 0, 1)
          • leftArr = [7]
          • rightArr = [2]
          • 合并后的数组为 [2, 7, 9, 4, 1, 5]
        • 回到 MergeSort(arr, 0, 2),继续调用 MergeSort(arr, 2, 2)(由于 left 不小于 right,直接返回)
      • Step 4: 递归排序右半部分
        • 初次调用 Merge(arr, 0, 1, 2)
          • leftArr = [2, 7]
          • rightArr = [9]
          • 合并后的数组为 [2, 7, 9, 4, 1, 5]
        • 回到最初调用 MergeSort(arr, 0, 5),继续调用 MergeSort(arr, 3, 5)
          • mid = 3 + (5 - 3) \ 2 = 4
          • 递归调用 MergeSort(arr, 3, 4)
          • mid = 3 + (4 - 3) \ 2 = 3
          • 递归调用 MergeSort(arr, 3, 3)(由于 left 不小于 right,直接返回)
          • 递归调用 MergeSort(arr, 4, 4)(由于 left 不小于 right,直接返回)
      • Step 5: 合并右半部分
          • leftArr = [4]
          • rightArr = [1]
          • 合并后的数组为 [2, 7, 9, 1, 4, 5]
        • 继续调用 MergeSort(arr, 5, 5)(由于 left 不小于 right,直接返回)
        • 合并 Merge(arr, 3, 4, 5)
          • leftArr = [1, 4]
          • rightArr = [5]
          • 合并后的数组为 [2, 7, 9, 1, 4, 5]

        合并 Merge(arr, 3, 3, 4)

      • Step 6: 最终合并
        • 最终合并 Merge(arr, 0, 2, 5)
          • leftArr = [2, 7, 9]
          • rightArr = [1, 4, 5]
          • 合并后的数组为 [1, 2, 4, 5, 7, 9]

      调用 MergeSort arr, 0, 5(数组从索引0到索引5)

    Step 1: 初始化数组 arr = [7, 2, 9, 4, 1, 5]

  • 扑克牌解释:将扑克牌分成两半,分别对两半进行排序,然后合并两个有序的部分,直到所有牌都合并成一个有序的整体
  • 时间复杂度:归并排序的时间复杂度是 O(n log n),因为每次分割数组的操作是 O(log n),而合并操作是 O(n)
  • 适用性:归并排序适用于处理大数据集和需要稳定排序的场合,尤其是在链表结构中非常高效
  • 示例:
    Sub MergeSortExample() Dim arr() As Variant arr = Array(7, 2, 9, 4, 1, 5) Debug.Print "原始数组:" PrintArray arr ' 调用归并排序 MergeSort arr, LBound(arr), UBound(arr) Debug.Print "排序后的数组:" PrintArray arr End Sub Sub PrintArray(arr() As Variant) Dim i As Integer For i = LBound(arr) To UBound(arr) Debug.Print arr(i); Next i Debug.Print End Sub Sub MergeSort(arr() As Variant, left As Integer, right As Integer) If left < right Then Dim mid As Integer mid = (left + right) \ 2 ' 递归排序左半部分 MergeSort arr, left, mid ' 递归排序右半部分 MergeSort arr, mid + 1, right ' 合并两个有序部分 Merge arr, left, mid, right End If End Sub Sub Merge(arr() As Variant, left As Integer, mid As Integer, right As Integer) Dim temp() As Integer Dim i As Integer, j As Integer, k As Integer Dim n1 As Integer, n2 As Integer ' 计算两个子数组的大小 n1 = mid - left + 1 n2 = right - mid ' 创建临时数组 ReDim temp(left To right) ' 将左半部分数据拷贝到临时数组 For i = 0 To n1 - 1 temp(left + i) = arr(left + i) Next i ' 将右半部分数据拷贝到临时数组 For j = 0 To n2 - 1 temp(mid + 1 + j) = arr(mid + 1 + j) Next j ' 初始化指针 i = left ' 左半部分起始点 j = mid + 1 ' 右半部分起始点 k = left ' 合并后的数组起始点 ' 合并两个有序子数组 While i <= mid And j <= right If temp(i) <= temp(j) Then arr(k) = temp(i) i = i + 1 Else arr(k) = temp(j) j = j + 1 End If k = k + 1 Wend ' 拷贝剩余的左半部分元素 While i <= mid arr(k) = temp(i) i = i + 1 k = k + 1 Wend ' 拷贝剩余的右半部分元素 While j <= right arr(k) = temp(j) j = j + 1 k = k + 1 Wend ' 打印每次合并后的数组 Debug.Print "合并数组下标位置:" & left & " 到 " & right PrintArray arr End Sub

        

VBA学习笔记四:数组
归并排序

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/113830.html

(0)
上一篇 2025-12-12 14:00
下一篇 2025-12-12 14:15

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信