排序之归并排序

概念:

归并排序( MERGE-SORT )是建立在归并操作上的一种有效的排序算法 , 该算法是采用分治( Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

算法步骤:

 

也就是可以看成是两个有序数组,取小的插入。

归并排序的步骤可以分为以下几个阶段:

  1. 分解:将待排序的数组递归地分成两半,直到每个子数组只包含一个元素或为空(即每个子数组已经是有序的)。

  2. 解决:对于每个只有一个元素的子数组,它们已经是有序的,不需要进一步排序。

  3. 合并:将两个有序的子数组合并为一个有序数组。这是归并排序的关键步骤,需要额外的空间来存放合并后的数组。

  4. 递归结束:当子数组的大小增加到原始数组大小时,整个数组就变得有序。

注意:除法会丢失数据,因为(偶数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. 初始化:首先,确定数组的初始大小为1,即每个元素都是一个有序的子数组。

  2. 迭代:使用循环来迭代数组的大小,每次迭代都将当前大小翻倍。

  3. 分割:对于每个大小为size的子数组,找到它的起始索引start和结束索引end

  4. 合并:使用merge函数将两个相邻的有序子数组合并为一个更大的有序数组。

  5. 更新索引:更新索引以指向下一个需要合并的子数组。

  6. 重复:重复上述步骤,直到子数组的大小大于或等于整个数组的长度。

让我们通过一个具体的例子来可视化非递归归并排序的过程。假设我们有一个数组: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(nlog⁡n)、非自适应排序:划分产生高度为 log⁡n 的递归树,每层合并的总操作数量为 n ,因此总体时间复杂度为 O(nlog⁡n) 。
  • 空间复杂度为 O(n)、非原地排序:递归深度为 log⁡n ,使用 O(log⁡n) 大小的栈帧空间。合并操作需要借助辅助数组实现,使用 O(n) 大小的额外空间。
  • 稳定排序:在合并过程中,相等元素的次序保持不变。
  • 14
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值