题目: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]。
归并排序基于分治法,将复杂的大问题分解成足够简单的小问题,分而治之。先进行分解的步骤,将原本无序的序列至顶向下不断进行二分,直到不可再分,即每个分组都只剩一个元素;之后进行合并的步骤,将分组按照原来拆分的过程,两两合并,在合并的过程中需要按照升序或者降序的需求对元素进行比较,使得合并后的每个小序列都是有序的,直到合并成完整的大序列。
归并排序的步骤:
- 将序列不断向下二分;
- 直到不可再分,每个分组只有单个元素;(此时每个分组都是有序的)
- 将有序分组两两向上合并成有序;(合并两个有序数组)
- 直到所有分组合并成完整的序列。
关于将两个原本就有序(假设升序)的分组合并成一个完整有序的分组,我们只需要依次比较分组的头元素,每次都将较小的元素有序放入新分组,最后一定有一个分组先放完,另一个分组剩下若干个元素,直接将不为空的分组剩余元素(原本就是有序的)放入新分组,这样新分组里的元素就全部都是有序的了。
合并两个有序数组的步骤:(新数组存放合并后的结果)
- 比较两个数组的头元素;
- 将较小的元素从原数组中取出,放入新数组;
- 重复上述1、2步骤,直到两个数组中的一个为空;
- 将另一个不为空的数组所有元素放入新数组;
- 此时的新数组内的元素即为有序的。
合并两个有序数组的流程:
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;归并排序使用递归实现,对包含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)。
算法不能原地排序,需要用到一个与原数组一样大的空间用来存放临时数据,算法的空间复杂度为O(n)。
注意事项:
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); } }