分治算法(5)_归并排序_排序数组

个人主页:C++忠实粉丝
欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C++忠实粉丝 原创

分治算法(5)_归并排序_排序数组

收录于专栏【经典算法练习
本专栏旨在分享学习算法的一点学习笔记,欢迎大家在评论区交流讨论💌

目录

1. 归并排序简介

分治思想:

归并排序的步骤:

归并排序的时间与空间复杂度

归并排序的优缺点: 

2. 题目链接

3. 题目描述

4. 解法

算法思路:

代码展示:

结果分析:

5. 快速排序与归并排序的比较 

算法思想:

时间与空间复杂度:

稳定性: 

实际应用:


1. 归并排序简介

分治思想:

分(Divide):将待排序数组分成两半,递归地对每一半进行归并排序。
治(Conquer):当子数组的大小为1时,数组自然是有序的。
合(Combine):将两个已排序的子数组合并成一个排序好的数组。 

归并排序的步骤:

分割:将数组不断分割,直到每个子数组只包含一个元素。
合并:将相邻的两个子数组合并成一个排序后的数组。合并的过程需要一个辅助数组来存放合并结果。 

归并排序的时间与空间复杂度

时间复杂度
最佳情况:O(n log n)
平均情况:O(n log n)
最坏情况:O(n log n)
归并排序的时间复杂度在所有情况下都是 O(n log n),因为它的合并过程总是需要遍历所有元素,而分割的深度是 log n。

空间复杂度
空间复杂度:O(n)
归并排序需要额外的空间来存储合并后的数组,因此其空间复杂度是 O(n)。

归并排序的优缺点: 

优点
稳定性:归并排序是一种稳定的排序算法,即相同元素的相对位置不会改变。
适用性广:对于大规模数据集或链表等数据结构,归并排序表现良好。
并行性:归并排序容易实现并行化,适合在多处理器环境中运行。
缺点
额外空间开销:需要 O(n) 的额外空间来存储合并结果,不适合内存有限的环境。
比较复杂:相比其他简单排序算法(如插入排序),实现起来相对复杂。 

2. 题目链接

OJ链接 : 排序数组 

3. 题目描述

给你一个整数数组 nums,请你将该数组升序排列。

你必须在 不使用任何内置函数 的情况下解决问题,时间复杂度为 O(nlog(n)),并且空间复杂度尽可能小。

示例 1:

输入:nums = [5,2,3,1]
输出:[1,2,3,5]

示例 2:

输入:nums = [5,1,1,2,0,0]
输出:[0,0,1,1,2,5]

提示:

  • 1 <= nums.length <= 5 * 104
  • -5 * 104 <= nums[i] <= 5 * 104

4. 解法

算法思路:

归并排序的流程充分体现了[分而治之]的思想,大体过程为两步:

1. 分 : 将数组一分为二,一直分解到数组的长度为1,使整个数组的排序过程被分为[左半部排序] + [右半部排序];

2. 治 : 将两个较短额[有序数组组合成一个长的有序数组], 一直合并到最初的长度. 

代码展示:

class Solution {
    vector<int> tmp;
public:
    vector<int> sortArray(vector<int>& nums) {
        tmp.resize(nums.size());
        mergeSort(nums, 0, nums.size() - 1);
        return nums;
    }

    void mergeSort(vector<int>& nums, int left, int right)
    {
        if(left >= right) return;
        //选择中间点划分区间
        int mid = (left + right) >> 1;
        
        //[left, mid] [mid + 1, right]

        //2. 把左右区间排序
        mergeSort(nums, left, mid);
        mergeSort(nums, mid + 1, right);

        //3. 合并两个有序数组
        int cur1 = left, cur2 = mid + 1, i = 0;
        while(cur1 <= mid && cur2 <= right)
            tmp[i++] = nums[cur1] <= nums[cur2] ? nums[cur1++] : nums[cur2++];
        //处理没有遍历完的数组
        while(cur1 <= mid) tmp[i++] = nums[cur1++];
        while(cur2 <= right) tmp[i++] = nums[cur2++];

        //还原
        for(int i = left; i <= right; i++)
            nums[i] = tmp[i - left];
    }
};

结果分析:

 示例分析:

5. 快速排序与归并排序的比较 

算法思想:

快速排序:
采用分治法(Divide and Conquer)策略。
选择一个“基准”元素,将待排序数组分成两部分:左侧为小于基准的元素,右侧为大于基准的元素。然后对左右两部分分别递归地进行快速排序。
归并排序:
同样采用分治法策略。
将待排序数组递归地分成两半,直到每个子数组只包含一个元素,然后再将两个已排序的子数组合并成一个有序数组。 

快速排序类似于二叉树的前序遍历(根左右)--确定好根然后递归遍历左右子树

归并排序类似于二叉树的后序遍历(左右根)--确定好左右子树在确定根 

时间与空间复杂度:

时间复杂度: 

快速排序:
最佳情况:O(n log n)(当基准元素均匀分布时)
平均情况:O(n log n)
最坏情况:O(n²)(当数组已经是有序的,且每次选择的基准都是最大或最小的元素)

归并排序:
最佳情况:O(n log n)
平均情况:O(n log n)
最坏情况:O(n log n)

空间复杂度: 

快速排序:
空间复杂度为 O(log n),主要是递归调用栈的空间消耗。实际上是一个原地排序算法,不需要额外的存储空间。
归并排序:
空间复杂度为 O(n),需要额外的数组来存储合并后的结果。 

稳定性: 

快速排序:
不稳定。在排序过程中,可能会改变相同元素的相对位置。

归并排序:
稳定。在合并的过程中,保持相同元素的相对位置不变。

实际应用:

快速排序:
通常在实际应用中比归并排序更快,特别是对于小规模数据和随机分布的数据。
适合在内存中排序,通常是许多标准库的默认排序算法(如C++的std::sort)。

归并排序:
适合处理大规模数据,尤其是链表等数据结构,因为它可以在合并的过程中避免内存的重新分配。
在外部排序(如排序大文件)中表现良好,因为可以高效地处理数据流。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值