排序算法之归并排序

目录

排序算法介绍

归并算法

算法流程

算法实现

python

C++


排序算法介绍

《Hello算法》是GitHub上一个开源书籍,对新手友好,有大量的动态图,很适合算法初学者自主学习入门。而我则是正式学习算法,以这本书为参考,写写笔记,有错误的地方还请指正,下面我会用python和C++实现其中的实例

排序介绍:排序简介 - Hello 算法 (hello-algo.com)

这里有更详细的介绍。 

归并算法

Merge Sort是算法中“分治思想”的典型体现,共有“划分”、“合并”两个阶段:

  • 划分阶段,通过递归不断地将数组从中间划分开,将长数组地排序问题转化为短数组地排序问题;
  • 合并阶段,当划分地子数组长度为1时,开始向上合并,不断地将左右两个短排序数组合并为一个长排序数组,直至合并至原数组时完成排序;

算法流程

划分是从上到下将数组从中点切为两个子数组,直至长度为1;

  1. 计算数组中点mid,划分左子数组([left,mid])以及右子数组([mid+1,right]);
  2. 递归执行1.步骤,直至子数组区间长度为1时,终止递归划分;

合并从底至顶地将左子数组和右子数组合并为一个有序数组;

需要注意的是,由于从长度为 1 的子数组开始合并,所以 每个子数组都是有序的 。因此,合并任务本质是要 将两个有序子数组合并为一个有序数组 。

观察发现,归并排序的递归顺序就是二叉树的后序遍历。

  • 后序遍历: 先递归左子树、再递归右子树、最后处理根结点。
  • 归并排序: 先递归左子树、再递归右子树、最后处理合并

算法实现

下面将以python与C++为例

python

def merge(nums, left, mid, right):
    # 初始化辅助数组 借助 copy模块
    tmp = nums[left:right + 1]
    # 左子数组的起始索引和结束索引
    left_start, left_end = left - left, mid - left
    # 右子数组的起始索引和结束索引
    right_start, right_end = mid + 1 - left, right - left
    # i, j 分别指向左子数组、右子数组的首元素
    i, j = left_start, right_start
    # 通过覆盖原数组 nums 来合并左子数组和右子数组
    for k in range(left, right + 1):
        # 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++
        if i > left_end:
            nums[k] = tmp[j]
            j += 1
        # 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++
        elif j > right_end or tmp[i] <= tmp[j]:
            nums[k] = tmp[i]
            i += 1
        # 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++
        else:
            nums[k] = tmp[j]
            j += 1

""" 归并排序 """
def merge_sort(nums, left, right):
    # 终止条件
    if left >= right:
        return                        # 当子数组长度为 1 时终止递归
    # 划分阶段
    mid = (left + right) // 2         # 计算中点
    merge_sort(nums, left, mid)       # 递归左子数组
    merge_sort(nums, mid + 1, right)  # 递归右子数组
    # 合并阶段
    merge(nums, left, mid, right)

C++

void merge(vector<int>& nums, int left, int mid, int right) {
    // 初始化辅助数组
    vector<int> tmp(nums.begin() + left, nums.begin() + right + 1);   
    // 左子数组的起始索引和结束索引  
    int leftStart = left - left, leftEnd = mid - left;
    // 右子数组的起始索引和结束索引       
    int rightStart = mid + 1 - left, rightEnd = right - left;
    // i, j 分别指向左子数组、右子数组的首元素
    int i = leftStart, j = rightStart;                
    // 通过覆盖原数组 nums 来合并左子数组和右子数组
    for (int k = left; k <= right; k++) {
        // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++
        if (i > leftEnd)
            nums[k] = tmp[j++];
        // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++
        else if (j > rightEnd || tmp[i] <= tmp[j])
            nums[k] = tmp[i++];
        // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++
        else
            nums[k] = tmp[j++];
    }
}

/* 归并排序 */
void mergeSort(vector<int>& nums, int left, int right) {
    // 终止条件
    if (left >= right) return;       // 当子数组长度为 1 时终止递归
    // 划分阶段
    int mid = (left + right) / 2;    // 计算中点
    mergeSort(nums, left, mid);      // 递归左子数组
    mergeSort(nums, mid + 1, right); // 递归右子数组
    // 合并阶段
    merge(nums, left, mid, right);
}

重点解释一下合并方法 merge() 的流程:

  1. 初始化一个辅助数组 tmp 暂存待合并区间 [left,right] 内的元素,后续通过覆盖原数组 nums  的元素来实现合并;
  2. 初始化指针 i,j,k 分别指向左子数组、右子数组、原数组的首元素;
  3. 循环判断 tmp[ i ] 和 tmp[ j ] 的大小,将较小的先覆盖至 nums[ k ] ,指针 i,j 根据判断结果交替前进(指针 k 也前进),直至两个子数组都遍历完,即可完成合并。

合并方法 merge() 代码中的主要难点:

  • nums 的待合并区间为 [left,right] ,而因为 tmp 只复制了 nums 该区间元素,所以 tmp 对应区间为 [0,right - left] ,需要特别注意代码中各个变量的含义
  • 判断 tmp[ i ] 和 tmp[ j ] 的大小的操作中,还 需考虑当子数组遍历完成后的索引越界问题,即  i >leftEnd 和 j>rightEnd 的情况,索引越界的优先级是最高的,例如如果左子数组已经被合并完了,那么不用继续判断,直接合并右子数组元素即可。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
归并排序(Merge Sort)是一种稳定的、基于比较的排序算法,最坏时间复杂度为 O(nlogn)。其基本思想是将待排序序列分成若干个子序列,每个子序列都是有序的,然后将子序列合并成整体有序的序列。 归并排序的实现方法有两种:自顶向下和自底向上。 自顶向下的归并排序算法实现: 1. 将待排序序列分成两个子序列,分别对这两个子序列进行递归排序。 2. 将两个已经排好序的子序列合并为一个有序序列。 自底向上的归并排序算法实现: 1. 将待排序序列每个元素看成一个独立的有序序列,进行两两合并。 2. 得到 n/2 个长度为 2 的有序序列,再两两合并。 3. 重复步骤 2,直到得到一个长度为 n 的有序序列。 下面是自顶向下的归并排序算法的实现代码(使用了递归): ``` void MergeSort(int arr[], int left, int right) { if (left >= right) return; int mid = left + (right - left) / 2; MergeSort(arr, left, mid); MergeSort(arr, mid + 1, right); int* temp = new int[right - left + 1]; int i = left, j = mid + 1, k = 0; while (i <= mid && j <= right) { if (arr[i] <= arr[j]) temp[k++] = arr[i++]; else temp[k++] = arr[j++]; } while (i <= mid) temp[k++] = arr[i++]; while (j <= right) temp[k++] = arr[j++]; for (int p = 0; p < k; p++) arr[left + p] = temp[p]; delete[] temp; } ``` 下面是自底向上的归并排序算法的实现代码(使用了迭代): ``` void MergeSort(int arr[], int n) { int* temp = new int[n]; for (int len = 1; len < n; len *= 2) { for (int left = 0; left < n - len; left += len * 2) { int mid = left + len - 1; int right = min(left + len * 2 - 1, n - 1); int i = left, j = mid + 1, k = 0; while (i <= mid && j <= right) { if (arr[i] <= arr[j]) temp[k++] = arr[i++]; else temp[k++] = arr[j++]; } while (i <= mid) temp[k++] = arr[i++]; while (j <= right) temp[k++] = arr[j++]; for (int p = 0; p < k; p++) arr[left + p] = temp[p]; } } delete[] temp; } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夏天是冰红茶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值