17 归并排序(Merge Sort)

1 题目

题目:Sort Integer II

lintcode题号——464,难度——easy

描述:给一组整数,请将其在原地按照升序排序。可使用归并排序,快速排序,堆排序或者任何其他O(n*log n)的排序算法。

样例1:

输入:[3,2,1,4,5],
输出:[1,2,3,4,5]。

样例2:

输入:[2,3,1],
输出:[1,2,3]。

2 思路

  归并排序基于分治法,将复杂的大问题分解成足够简单的小问题,分而治之。先进行分解的步骤,将原本无序的序列至顶向下不断进行二分,直到不可再分,即每个分组都只剩一个元素;之后进行合并的步骤,将分组按照原来拆分的过程,两两合并,在合并的过程中需要按照升序或者降序的需求对元素进行比较,使得合并后的每个小序列都是有序的,直到合并成完整的大序列。

归并排序的步骤:

  1. 将序列不断向下二分;
  2. 直到不可再分,每个分组只有单个元素;(此时每个分组都是有序的)
  3. 将有序分组两两向上合并成有序;(合并两个有序数组)
  4. 直到所有分组合并成完整的序列。

  关于将两个原本就有序(假设升序)的分组合并成一个完整有序的分组,我们只需要依次比较分组的头元素,每次都将较小的元素有序放入新分组,最后一定有一个分组先放完,另一个分组剩下若干个元素,直接将不为空的分组剩余元素(原本就是有序的)放入新分组,这样新分组里的元素就全部都是有序的了。

合并两个有序数组的步骤:(新数组存放合并后的结果)

  1. 比较两个数组的头元素;
  2. 将较小的元素从原数组中取出,放入新数组;
  3. 重复上述1、2步骤,直到两个数组中的一个为空;
  4. 将另一个不为空的数组所有元素放入新数组;
  5. 此时的新数组内的元素即为有序的。

2.1 图解

合并两个有序数组的流程:

A--'1, 2, 5, 7'
B--'3, 4, 6, 8, 9'
比较头元素'1'和'3', 较小元素放入:C--'1'
A--'_, 2, 5, 7'
B--'3, 4, 6, 8, 9'
比较头元素'2'和'3', 较小元素放入:C--'1, 2'
A--'_, _, 5, 7'
B--'3, 4, 6, 8, 9'
比较头元素'5'和'3', 较小元素放入:C--'1, 2, 3'
A--'_, _, 5, 7'
B--'_, 4, 6, 8, 9'
比较头元素'5'和'4', 较小元素放入:C--'1, 2, 3, 4'
A--'_, _, 5, 7'
B--'_, _, 6, 8, 9'
比较头元素'5'和'6', 较小元素放入:C--'1, 2, 3, 4, 5'
A--'_, _, _, 7'
B--'_, _, 6, 8, 9'
比较头元素'7'和'6', 较小元素放入:C--'1, 2, 3, 4, 5, 6'
A--'_, _, _, 7'
B--'_, _, _, 8, 9'
比较头元素'7'和'8', 较小元素放入:C--'1, 2, 3, 4, 5, 6, 7'
A--'_, _, _, _'
B--'_, _, _, 8, 9'
A已空, B直接放入:C--'1, 2, 3, 4, 5, 6, 7, 8, 9', 有序数组C

归并排序的流程:

二分拆分
二分拆分
拆分
拆分
拆分
拆分
拆分
拆分
拆分
拆分
拆分
拆分
拆分
拆分
拆分
拆分
拆分
拆分
合并有序数组
合并有序数组
合并有序数组
合并有序数组
合并
合并有序数组
合并有序数组
合并有序数组
合并
合并有序数组
合并有序数组
合并有序数组
合并有序数组
合并有序数组
合并
合并
合并
合并
'3, 5, 7, 4, 9, 8, 2, 10, 1, 6'
'3, 5, 7, 4, 9'
'8, 2, 10, 1, 6'
'3, 5, 7'
'4, 9'
'8, 2, 10'
'1, 6'
'3, 5'
'7'
'4'
'9'
'8, 2'
'10'
'1'
'6'
'3'
'5'
'8'
'2'
'3, 5'
'2, 8'
'3, 5, 7'
'4, 9'
'2, 8, 10'
'1, 6'
'3, 4, 5, 7, 9'
'1, 2, 6, 8, 10'
'1, 2, 3, 4, 5, 6, 7, 8, 9, 10'

2.2 时间复杂度

  归并排序使用递归实现,对包含n个元素的数组,计算其时间复杂度,先分析拆分过程:

第一层将n个数划分为2个分组,每个分组包含n/2个元素;
第二层将n个数划分为4个分组,每个分组包含n/4个元素;
第三层将n个数划分为8个分组,每个分组包含n/8个元素;
……
第log n层将n个数划分为n个分组,每个分组包含1个元素。

  再看合并过程:

第log n层将n分组合并成n/2个分组,每个元素都会被遍历一次,每个元素耗时O(1),该层耗时O(n);
……
第3层将8分组合并成4个分组,每个元素都会被遍历一次,每个元素耗时O(1),该层耗时O(n);
第2层将4分组合并成2个分组,每个元素都会被遍历一次,每个元素耗时O(1),该层耗时O(n);
第1层将2分组合并成1个分组,每个元素都会被遍历一次,每个元素耗时O(1),该层耗时O(n);

  所以每层耗时为O(n),层数为log n,算法的时间复杂度为O(n*log n)。

2.3 空间复杂度

  算法不能原地排序,需要用到一个与原数组一样大的空间用来存放临时数据,算法的空间复杂度为O(n)。

3 源码

  注意事项:

  1. 需要使用一个临时数组,存放每次递归过程中合并两个有序数组之后的结果,排序完成再复制会原数组,否则直接原地排序会打乱原数组的元素。
  2. 归并排序是递归算法,注意递归的三要素,防止死循环。
  3. 合并有序数组时候,需要注意左右两个数组的起点位置。

  C++版本:

/**
* @param A: an integer array
* @return: nothing
*/
void sortIntegers2(vector<int> &A) {
    // write your code here
    if (A.size() <= 1)
    {
        return;
    }

    vector<int> temp(A.size());
    mergeSort(A, 0, A.size() - 1, temp);
}

// 递归的定义
void mergeSort(vector<int> & A, int start, int end, vector<int> & temp)
{
    if (start == end) // 递归的出口
    {
        return;
    }

    int mid = start + (end - start) / 2; // 取中点
    mergeSort(A, start, mid, temp); // 递归的拆解
    mergeSort(A, mid + 1, end, temp);
    merge(A, start, mid, end, temp);
}

// 合并有序数组
void merge(vector<int> & A, int start, int mid, int end, vector<int> & temp)
{
    int left = start;
    int right = mid + 1; // 注意此处的起点是中点的下一个元素
    int index = start;
    while (left <= mid && right <= end)
    {
        if (A.at(left) < A.at(right))
        {
            temp.at(index++) = A.at(left++);
        }
        else
        {
            temp.at(index++) = A.at(right++);
        }
    }

    while (left <= mid)
    {
        temp.at(index++) = A.at(left++);
    }
    while (right <= end)
    {
        temp.at(index++) = A.at(right++);
    }

    // 将临时数组中排好序的元素复制会原数组
    for (int i = start; i <= end; i++)
    {
        A.at(i) = temp.at(i);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值