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 图解
合并两个有序数组的流程:
归并排序的流程:
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);
}
}