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步骤,直到两个数组中的一个为空;
- 将另一个不为空的数组所有元素放入新数组;
- 此时的新数组内的元素即为有序的。
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 源码
注意事项:
- 需要使用一个临时数组,存放每次递归过程中合并两个有序数组之后的结果,排序完成再复制会原数组,否则直接原地排序会打乱原数组的元素。
- 归并排序是递归算法,注意递归的三要素,防止死循环。
- 合并有序数组时候,需要注意左右两个数组的起点位置。
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);
}
}