1.基本思想
归并排序(Merge Sort)的核心是分治,将一个复杂问题分解成多个相同或相似的子问题,然后把子问题分解成更小的问题,知道子问题可以简单地求解,最原始问题的解就是子问题解的合并。
一开始先将数组从中间分成两个子数组,一直递归把子数组划分成更小的子数组,直到子数组里只包含一个元素,这时才开始排序。排序的方法就是按照大小的顺序合并两个元素,接着依次按照递归返回的顺序,不断合并排好序的子数组,直到最后把整个数组的顺序排好。
例题:利用归并排序算法对数组 [2, 1, 7, 9, 5, 8] 进行排序。
2.C++代码实现
编译环境:win10系统,vs2013
#include <iostream>
#include <vector>
using namespace std;
//合并函数
void merge(vector<int> &arr, int l, int m, int r) {
vector<int> arr_copy(arr);
//定义一个变量k,表示从什么位置开始改变原来的数组
//i表示左边部分的起始位置的下标,j表示右边部分的起始位置的下标
int k = l, i = l, j = m + 1;
while (k <= r) {
if (i > m) {
arr[k++] = arr_copy[j++];
}
else if (j > r) {
arr[k++] = arr_copy[i++];
}
else if (arr_copy[i] > arr_copy[j]) {
arr[k++] = arr_copy[j++];
}
else {
arr[k++] = arr_copy[i++];
}
}
}
//归并排序
void mergeSort(vector<int> &arr, int l, int r) {
//判断是否只剩下一个元素
if (l >= r) return;
//将数组从中间分成两部分呢
int m = l + (r - l) / 2;
mergeSort(arr, l, m);
mergeSort(arr, m + 1, r);
//将排好序的左右两半合并
merge(arr, l, m, r);
}
//打印vector
void printVector(vector<int> &v) {
for (auto &i : v)
cout << i << " ";
cout << endl;
}
int main()
{
vector<int> vec = { 2, 1, 7, 9, 5, 8 };
cout << "给定数组为:";
printVector(vec);
mergeSort(vec, 0, vec.size() - 1);
cout << "归并排序后:";
printVector(vec);
system("pause");
return 0;
}
merge函数
中,While 语句中,while的结束条件为k<=l
,一共可能会出现四种情况。
- 左半边的数都处理完毕,只剩下右半边的数,只需要将右半边的数逐个拷贝过去。
- 右半边的数都处理完毕,只剩下左半边的数,只需要将左半边的数逐个拷贝过去就好。
- 右边的数小于左边的数,将右边的数拷贝到合适的位置,j 指针往前移动一位。
- 左边的数小于右边的数,将左边的数拷贝到合适的位置,i 指针往前移动一位。
输出
给定数组为:2 1 7 9 5 8
归并排序后:1 2 5 7 8 9
请按任意键继续. . .
3.算法分析
空间复杂度:O(n)
由于合并n个元素需要额外分配一个大小为n的数组,合并完成后释放,所以算法的空间复杂度为O(n)。归并排序也是稳定的排序算法
。
时间复杂度:O(nlogn)
归并排序是一种递归算法,时间复杂度可以表示为以下递归关系:T(n) = 2×T(n/2) + O(n)
公式解释:
举例:数组的元素个数是 n
,时间复杂度是T(n)
的函数。
解法:把这个规模为n的问题分解为两个规模为n/2
的子问题,每个子问题的时间复杂度为T(n/2)
,则两个子问题的复杂度之和为2xT(n/2)
。当两个子数组都排好序了,需要将它们合并,一共有n
个元素,需要进行n-1
次的比较,所以合并的复杂度为O(n)
。因此递归复杂度的公式为:T(n) = 2×T(n/2) + O(n)
对于公式的求解,是将规模为n
的问题分解为规模为n/2
的问题,一直分解为规模为1。如果n为2,需要分1次,如果n为4,需要分2次,以此类推,如果规模就为n,则需要分logn
次。而在每一次的合并中,所涉及到的元素就是数组中的所有元素,因此每次合并的复杂度都是O(n)
。所有整体的复杂度为O(nlogn)
。