概念:
算法步骤:
也就是可以看成是两个有序数组,取小的插入。
归并排序的步骤可以分为以下几个阶段:
-
分解:将待排序的数组递归地分成两半,直到每个子数组只包含一个元素或为空(即每个子数组已经是有序的)。
-
解决:对于每个只有一个元素的子数组,它们已经是有序的,不需要进一步排序。
-
合并:将两个有序的子数组合并为一个有序数组。这是归并排序的关键步骤,需要额外的空间来存放合并后的数组。
-
递归结束:当子数组的大小增加到原始数组大小时,整个数组就变得有序。
注意:除法会丢失数据,因为(偶数a)+(偶数a+1),对应的middle还是(偶数a),这也就是为什么分区间的时候是【begin,mid】【mid+1,end】而不是【begin,mid-1】【mid,end】,用【0,9】进行观察就可以发现
代码实现:
递归:
C++实现:
#include <iostream>
#include <vector>
// 合并两个有序数组
void merge(std::vector<int>& arr, int l, int m, int r) {
int n1 = m - l + 1; // 左子数组的大小
int n2 = r - m; // 右子数组的大小
// 创建临时数组
std::vector<int> L(n1), R(n2);
// 复制数据到临时数组
for (int i = 0; i < n1; i++)
L[i] = arr[l + i];
for (int j = 0; j < n2; j++)
R[j] = arr[m + 1 + j];
// 合并临时数组回到原数组arr[l..r]
int i = 0; // 左子数组的初始索引
int j = 0; // 右子数组的初始索引
int k = l; // 合并后数组的初始索引
while (i < n1 && j < n2) {
if (L[i] <= R[j]) {
arr[k] = L[i];
i++;
} else {
arr[k] = R[j];
j++;
}
k++;
}
// 复制左子数组中的剩余元素
while (i < n1) {
arr[k] = L[i];
i++;
k++;
}
// 复制右子数组中的剩余元素
while (j < n2) {
arr[k] = R[j];
j++;
k++;
}
}
// 递归函数,用于归并排序
void mergeSort(std::vector<int>& arr, int l, int r) {
if (l >= r) { // 如果子数组只有一个元素或为空,直接返回
return;
}
int m = l + (r - l) / 2; // 找到中间索引,切记,不要在这犯错
mergeSort(arr, l, m); // 递归地对左半部分进行排序
mergeSort(arr, m + 1, r); // 递归地对右半部分进行排序
merge(arr, l, m, r); // 合并两个有序数组
}
非递归:
非递归实现归并排序的核心思想是使用迭代代替递归。我们可以通过模拟递归过程中的每一步来实现这一点。下面是非递归归并排序的详细步骤:
-
初始化:首先,确定数组的初始大小为1,即每个元素都是一个有序的子数组。
-
迭代:使用循环来迭代数组的大小,每次迭代都将当前大小翻倍。
-
分割:对于每个大小为
size
的子数组,找到它的起始索引start
和结束索引end
。 -
合并:使用
merge
函数将两个相邻的有序子数组合并为一个更大的有序数组。 -
更新索引:更新索引以指向下一个需要合并的子数组。
-
重复:重复上述步骤,直到子数组的大小大于或等于整个数组的长度。
让我们通过一个具体的例子来可视化非递归归并排序的过程。假设我们有一个数组:arr = [3, 5, 1, 2, 4]
,我们将使用非递归的方式对其进行归并排序。
步骤 1: 初始化:
我们从大小为1的子数组开始,数组中的每个元素都是有序的。
[3] [5] [1] [2] [4]
步骤 2: 第一轮合并
我们将相邻的子数组进行合并,每次合并后的子数组大小为2。
- 合并
[3]
和[5]
,结果为[3, 5]
- 合并
[1]
和[2]
,结果为[1, 2]
4
保持不变,因为它已经是一个有序的单个元素。
合并后的数组:
[3, 5] [1, 2] [4]
步骤 3: 第二轮合并
现在我们将大小为2的子数组进行合并,合并后的子数组大小为4。
- 合并
[3, 5]
和[1, 2]
,由于1 < 3
,我们先取1
,然后是2
,接着是3
,最后是5
,合并结果为[1, 2, 3, 5]
4
保持不变。
合并后的数组:
[1, 2, 3, 5] [4]
步骤 4: 最后一轮合并
最后,我们将剩下的两个子数组合并,大小为5。
- 合并
[1, 2, 3, 5]
和[4]
,由于4
在[1, 2, 3, 5]
中的3
和5
之间,最终结果为[1, 2, 3, 4, 5]
合并后的数组:
[1, 2, 3, 4, 5]
至此,整个数组已经完全排序。
可视化表示
以下是每一步合并后的数组状态:
-
初始状态:
[3] [5] [1] [2] [4]
-
第一轮合并后:
[3, 5] [1, 2] [4]
-
第二轮合并后:
[1, 2, 3, 5] [4]
-
最后一轮合并后(排序完成):
[1, 2, 3, 4, 5]
这个例子展示了非递归归并排序的整个过程,每一步都通过迭代的方式合并相邻的有序子数组,直到整个数组变得有序。
C++实现:
#include <iostream>
#include <vector>
#include <algorithm> // 用于std::min函数
// 合并两个有序子数组
void merge(std::vector<int>& arr, int start, int mid, int end) {
std::vector<int> temp(end - start + 1);
int i = start; // 左子数组的起始索引
int j = mid + 1; // 右子数组的起始索引
int k = 0; // 临时数组的索引
// 合并过程
while (i <= mid && j <= end) {
if (arr[i] <= arr[j]) {
temp[k++] = arr[i++];
}
else {
temp[k++] = arr[j++];
}
}
// 复制左子数组中的剩余元素
while (i <= mid) {
temp[k++] = arr[i++];
}
// 复制右子数组中的剩余元素
while (j <= end) {
temp[k++] = arr[j++];
}
// 将合并后的元素复制回原数组
for (i = start, k = 0; i <= end; ++i, ++k) {
arr[i] = temp[k];
}
}
// 非递归归并排序
void iterativeMergeSort(std::vector<int>& arr) {
int n = arr.size();
int size = 1; // 初始子数组大小
while (size < n) {
// 处理每个大小为size的子数组
for (int start = 0; start < n; start += 2 * size) {
int mid = std::min(start + size - 1, n - 1);
int end = std::min(start + 2 * size - 1, n - 1);
// 合并相邻的有序子数组
merge(arr, start, mid, end);
}
// 将子数组的大小翻倍
size *= 2;
}
}
// 打印数组的函数
void printArray(const std::vector<int>& arr) {
for (int num : arr) {
std::cout << num << " ";
}
std::cout << std::endl;
}
// 主函数
int main() {
std::vector<int> arr = { 3, 5, 1, 2, 4 };
int n = arr.size();
std::cout << "Original array: ";
printArray(arr);
iterativeMergeSort(arr);
std::cout << "Sorted array: ";
printArray(arr);
return 0;
}
特性:
- 时间复杂度为 O(nlogn)、非自适应排序:划分产生高度为 logn 的递归树,每层合并的总操作数量为 n ,因此总体时间复杂度为 O(nlogn) 。
- 空间复杂度为 O(n)、非原地排序:递归深度为 logn ,使用 O(logn) 大小的栈帧空间。合并操作需要借助辅助数组实现,使用 O(n) 大小的额外空间。
- 稳定排序:在合并过程中,相等元素的次序保持不变。