ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

16 归并排序(Merge Sort)

2021-11-04 22:33:40  阅读:218  来源: 互联网

标签:Sort 16 -- 元素 合并 Merge stroke 分组 数组


目录

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 图解

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

graph TD A[A--'1, 2, 5, 7'<br/>B--'3, 4, 6, 8, 9'] --> X X[/比较头元素'1'和'3', 较小元素放入:C--'1'\] --> A1 A1[A--'_, 2, 5, 7'<br/>B--'3, 4, 6, 8, 9'] --> X1 X1[/比较头元素'2'和'3', 较小元素放入:C--'1, 2'\]--> A2 A2[A--'_, _, 5, 7'<br/>B--'3, 4, 6, 8, 9'] --> X2 X2[/比较头元素'5'和'3', 较小元素放入:C--'1, 2, 3'\]--> A3 A3[A--'_, _, 5, 7'<br/>B--'_, 4, 6, 8, 9'] --> X3 X3[/比较头元素'5'和'4', 较小元素放入:C--'1, 2, 3, 4'\]--> A4 A4[A--'_, _, 5, 7'<br/>B--'_, _, 6, 8, 9'] --> X4 X4[/比较头元素'5'和'6', 较小元素放入:C--'1, 2, 3, 4, 5'\]--> A5 A5[A--'_, _, _, 7'<br/>B--'_, _, 6, 8, 9'] --> X5 X5[/比较头元素'7'和'6', 较小元素放入:C--'1, 2, 3, 4, 5, 6'\]--> A6 A6[A--'_, _, _, 7'<br/>B--'_, _, _, 8, 9'] --> X6 X6[/比较头元素'7'和'8', 较小元素放入:C--'1, 2, 3, 4, 5, 6, 7'\]--> A7 A7[A--'_, _, _, _'<br/>B--'_, _, _, 8, 9'] --> X7 X7(A已空, B直接放入:C--'1, 2, 3, 4, 5, 6, 7, 8, 9', 有序数组C)

归并排序的流程:

graph TD A1['3, 5, 7, 4, 9, 8, 2, 10, 1, 6'] A1 -- 二分拆分 --> B1['3, 5, 7, 4, 9'] A1 -- 二分拆分 --> B2['8, 2, 10, 1, 6'] B1 -- 拆分 --> C1['3, 5, 7'] B1 -- 拆分 --> C2['4, 9'] B2 -- 拆分 --> C3['8, 2, 10'] B2 -- 拆分 --> C4['1, 6'] C1 -- 拆分 --> D1['3, 5'] C1 -- 拆分 --> D2['7'] C2 -- 拆分 --> D3['4'] C2 -- 拆分 --> D4['9'] C3 -- 拆分 --> D5['8, 2'] C3 -- 拆分 --> D6['10'] C4 -- 拆分 --> D7['1'] C4 -- 拆分 --> D8['6'] D1 -- 拆分 --> E1['3'] D1 -- 拆分 --> E2['5'] D5 -- 拆分 --> E3['8'] D5 -- 拆分 --> E4['2'] E1 -- 合并有序数组 --> F1['3, 5'] E2 -- 合并有序数组 --> F1 E3 -- 合并有序数组 --> F2['2, 8'] E4 -- 合并有序数组 --> F2 F1 -- 合并 --> G1['3, 5, 7'] D2 -- 合并有序数组 --> G1 D3 -- 合并有序数组 --> G2['4, 9'] D4 -- 合并有序数组 --> G2 F2 -- 合并 --> G3['2, 8, 10'] D6 -- 合并有序数组 --> G3 D7 -- 合并有序数组 --> G4['1, 6'] D8 -- 合并有序数组 --> G4 G1 -- 合并有序数组 --> H1['3, 4, 5, 7, 9'] G2 -- 合并有序数组 --> H1 G3 -- 合并 --> H2['1, 2, 6, 8, 10'] G4 -- 合并 --> H2 H1 -- 合并 --> I1['1, 2, 3, 4, 5, 6, 7, 8, 9, 10'] H2 -- 合并 --> I1 style E1 fill: #f9f, stroke: #333, stroke-width: 4px; style E2 fill: #f9f, stroke: #333, stroke-width: 4px; style D2 fill: #f9f, stroke: #333, stroke-width: 4px; style D3 fill: #f9f, stroke: #333, stroke-width: 4px; style D4 fill: #f9f, stroke: #333, stroke-width: 4px; style E3 fill: #f9f, stroke: #333, stroke-width: 4px; style E4 fill: #f9f, stroke: #333, stroke-width: 4px; style D6 fill: #f9f, stroke: #333, stroke-width: 4px; style D7 fill: #f9f, stroke: #333, stroke-width: 4px; style D8 fill: #f9f, stroke: #333, stroke-width: 4px;

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);
    }
}

标签:Sort,16,--,元素,合并,Merge,stroke,分组,数组
来源: https://www.cnblogs.com/seedoubleu/p/15510888.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有