归并排序
归并是一种基于分治的算法,就是将一个数组依次折半拆分成一个个小数组依次排序,最后将这些排序好的数组再合并起来。
(分治:大问题拆分为小问题逐个解决)
逐步分析
假设我们有{5,8,1,0,2,9,7,4}这样一个数组。需要我们进行从小到大进行排序。
第一次
我们将其拆分成{5,8,1,0} , {2,9,7,4}这样两个数组。
第二次
再进行拆分:{5,8},{1,0},{2,9},{7,4}四个数组。
第三次就会变成8个单独的数字。
{5}, {8} , {1} , {0} , {2} , {9} , {7} , {4};
最后我们将
5和8排序,1和0排序,2和9排序,7和4排序(按之前分出来的数组进行排序)
就得到了这样四个数组
{5 , 8} , {0, 1} { 2 , 9 } , { 4 , 7 }.
↑ ↑ ↑ ↑
① ② ③ ④
此处需要注意:
我们之前是如何将大数组分割成小数组的,在归并的时候也要按照原本的顺序归并回去。
之后将①②进行排序并合并得到{0, 1, 5, 8},同理通过③④得到{2, 4, 7, 9}。
最后一步就可以将两个刚刚得到的数组合并得到最终排序好的数组啦。
算法分析与实现
在刚刚的分析过程中可以看到,在分割后的每一步都是将两个数组进行合并同时排序得到一个新的数组,处理步骤都是一样的,因此在实际的代码中我们就可以利用递归的方法实现上述算法。
归并排序因为对数组进行持续的折半排序,因此时间复杂度是O(nlogn),最好最坏情况都是一样的,因为都得遍历整个数组,无法进行进化(进化:成为更高时间复杂度的排序算法),同时,归并排序也是一种稳定的排序算法(假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。)如稳定性不理解可查阅相关资料。
现在要注意的问题就是如何将两个数组合并而且排序得到一个新的数组:
这里我们采用一个很常用的算法思想——双指针。
引用刚刚的例子:{0, 1, 5, 8} , {2, 4, 7, 9}两个数组,这里用a,b代替。
先创建一个临时数组temp。用两个指针(迭代器等)pa,pb分别指向a和b的开头,然后比较pa,pb的大小。
将较小的数字放入temp然后将该指针进行自增操作。
就比如:pa和pb一开始指向a[0],b[0]。
比较a[0],b[0],也就是比较0和2,将a[0]的值放入temp,然后pa++,pa就指向了a[1]。
之后我们就可以用a[1]和b[0]比较a[1]<b[0],a[1]放入temp,pa++。
a[2]和b[0]比较,b[0]<a[2],b[0]放入temp,pb++,pb就指向了b[1].
然后我们就可以用a[2]和b[1]按照上述方法进行比较。
以此类推,我们就可以得到一个排序好的数组。
但其实目前为止所说的双指针算法还没有完全完成,可以自行考虑还有哪里有问题,后续代码注释给出答案。
这里给出代码和注释:
(归并排序的算法实现上各有千秋,本文仅简述思想,代码仅供参考,可自行改动以尝试改进效率)
//归并排序
//传入数组,需要进行排序的左右下标,整个数组排序就传入0和vec.size()
//默认从小到大
void mergeSort(vector<int>& vec, int l, int r) {
if (l >= r) return;
//判断是否分割完毕
int mid = (l + r) / 2;
//递归排序数组
mergeSort(vec, l, mid);
//vec[l...mid]代表前半段
mergeSort(vec, mid+1, r);
//vec[mid+1...r]代表后半段
/*
注意这里每次递归后的l和r就是代表vec分组后各个小数组的开头和结尾
l和r代表的意思一定要理解清楚
*/
//下面开始将递归排序好的两个数组依次合并
vector<int> temp(r - l + 1);
for (int i = l;i <= r; i++) {
temp[i-l] = vec[i];
}
//第一步,临时空间赋值
//这里和刚刚解释的有所不同。我们是用临时数组temp存储原数组vec中的值,排序好的数组直接放回vec
//第二步,将分好的两个数组vec[l...mid],vec[mid+1...r]进行双指针的合并操作。
int i = l, j = mid + 1;
for (int k = l;k <= r;k++) {
/*回到代码开始前提到的问题:
完善我们的双指针算法。
利用双指针遍历两个数组的时候,如果其中一个数组遍历完成的时候就需要将剩下一个数组所有数据一起放入temp,
否则会出现数据不完成。
比如{1,3,5,9} {2,4,6,10,12,31};
当我们把1,2,3,4,5,6,9全部放入数组后,10、12、31还没有放进去
就需要我们将这三个数一同放入排序好的数组。
下述①②就是判断是否还有空余数据。
*/
if (i > mid) {//①
vec[k] = temp[j - l];
j++;
}
else if(j>r){//②
vec[k] = temp[i - l];
i++;
}
//下述是双指针的比较并合并
else if (temp[i - l] < temp[j - l]) {
vec[k] = temp[i - l];
i++;
}else{
vec[k] = temp[j - l];
j++;
}
}
}
代码测试截图:
)