排序算法时间O(n log2n)篇概述
在排序算法中,平均时间O(n log2n)是除特殊排序算法之外最快的啦,其中快速排序更是人们心目中最实用的排序算法。于是,蒟蒻君就为大家整理的最常见的三种时间复杂度O(n log2n)的排序算法。
大家最好从第一篇开始看。
算法思路
归并排序,就是先归再并,是典型的分治法。
归
将数组不停地分为两半,直到每部分只有一个元素。
并
不停地将所有相邻两组数排成一组有序的数,直到整个数组中只有一组数。
举个栗子
给定8个元素的数组{8, 5, 3, 6, 1, 2, 7, 4}。
我们先把数组每次二分,直到每组数都只有1个数。
自底向上地将所有相邻的两组数排序成一组有序的数。此时,每组数的个数均为2个。
然后,我们将两个数一组的数每两组放在一起排序。此时,每组数的个数均为4个。
最后,我们将这两组数排序,就得到了最终的结果。
所以,我们目前最大的问题就是如何将两个有序的子序列合并成一个有序的序列,最短又能用多长时间合并呢?
我们分别定义两个指针(红/绿色箭头),开始分别指向两个子序列的开头。
我们每次需要将目前两个指针指向的元素取最小的放入答案数组,因为若两个数组a和b均为升序排列,则a数组与b数组中的最小值也就是a[0]和b[0]中的最小值。
在这里,a数组和b数组就是数组目前部分的前半部分和后半部分啦
因为1<3,所以将1存入答案数组中。b数组指针同时后移一位,指向下一个元素。
继续比较,2<3,将2存入答案数组,b数组指针后移一位。
3<4,将3存入答案数组,a数组指针后移一位。
后面以此类推~
还有一种特殊情况,就是一个数组已经全部进入答案数组,而另一个还有一部分。此时直接将这个数组的剩余部分全部放入答案数组就ok了,因为此数组本来就是有序的。
小伙伴,你学会了吗?
动画演示
代码实现
void merge(int a[], int l, int r) { // 数组,排序部分的范围(左,右)
int tmp[r - l + 1]; // 答案数组
int mid = l + r >> 1; // 更快速的(l + r) / 2
int i = l, j = mid + 1, k = 0;
while (i <= mid && j <= r) {
if (a[i] <= a[j]) { // 答案数组存储两个指针指向的数中最小的数,并让指针往后移一位
tmp[k] = a[i];
++i;
} else {
tmp[k] = a[j];
++j;
}
++k; // 答案数组该存下一位啦
}
while (i <= mid) { // 如果有一个数组还未存储完
tmp[k] = a[i];
++k;
++i;
}
while (j <= r) {
tmp[k] = a[j];
++k;
++j;
}
for (int i = 0; i < r - l + 1; ++i) { // 将答案数组copy回a数组
a[l + i] = tmp[i];
}
}
void merge_sort(int a[], int l, int r) {
if (l < r) { // 序列长度最少为1
int mid = l + r >> 1;
merge_sort(a, l, mid); // 前半部分排序
merge_sort(a, mid + 1, r); // 后半部分排序
merge(a, l, r); // 两个子序列排序
}
}
算法特点
- 由于每次必须将整个序列二分,再合并,即归并排序的最优/最差/平均时间复杂度均为O(n log2n)。
- 由于每次排序两个有序序列都需要一个答案数组,空间消耗较大,空间复杂度为O(n)。
- 若两个元素的值相等,答案数组会优先存储前面的数,即归并排序算法有稳定性。
总结
由此,我们可以总结出这张图。
这节课我们学习了经典的排序算法——归并排序,下节课我们会继续学习快速排序。