冒泡排序、插入排序、选择排序这三种排序算法,它们的时间复杂度都是
O(n²)
,比较高,适合小规模数据的排序。归并排序和快速排序时间复杂度为O(nlogn)
,适合大规模的数据排序。
归并排序和快速排序都用到了分治思想。
归并排序
归并排序的原理
如果要排序一个数组,先从中间的位置将数组分割成两个子数组,然后再对这两个子数组从中间位置分割,如此直到分割之后的子数组只有一个元素。然后将最终分割的数组按大小顺序合并到一个新的数组中,如此递归地合并直到所有子数组合并完毕,就可以得到一个有序的数组了。
例子
假设要对下面这组数据利用归并排序方法进行排序:
1 | $arr = [3, 5, 100, 6, 78]; |
第一次分割成下面两个数组:
1 | $arr1 = [3, 5, 100]; $arr2 = [6, 78]; |
第二次分割:
1 | // $arr1 划分成 |
按递归的思想会先处理 $arr1
分割出来的两个数组:
[3]
里面只有一个元素,无需再分割,[5, 100]
还需要再次分割成 [5]
和
[100]
,分割完成就开始合并了,合并的时候,需要创建一个大小等于两个需要合并的数组大小总和的数组
temp
,然后将小的元素先放入
temp
,如此直到所有元素都放进了 temp
之后,这次合并操作就完成了,就得到了一个有序的数组
temp
。
所以 $arr1
最终合并得到:
1 | [3, 5, 100] |
$arr2
最终合并得到:
1 | [6, 78] |
再将 $arr1
和 $arr2
处理的结果合并,也就是将 [3, 5, 100]
和
[6, 78]
合并,即可得到:
1 | [3, 5, 6, 78, 100] |
PHP 实现
1 | function merge_sort($arr, $p, $r) |
算法分析
- 归并排序是否稳定?
在 merge
实现里面对相等的元素,我们可以将
$arr1
的元素先合并到
$arr
,这样即可实现排序算法的稳定性。
- 时间复杂度是多少?
归并排序的排序过程中不会受原始数据的有序度影响,都是固定的不断拆分成两个数组,直到最终拆分到只有一个元素的时候进行合并,小的在前面,大的在后面。也就是说,归并排序的时间复杂度是固定的
O(nlogn)
,非常稳定。
- 空间复杂度是多少?
在将原始数据一分为二的时候,我们不需要开辟额外的空间来存储数据,但是在合并的过程中,我们需要开辟一个临时数组来存储排序后的数据,然后将这个数组覆盖掉原始数组的那一段范围的数据。
但由于某一时刻 CPU
中最多只有一个合并的操作在进行,所以最大的空间复杂度为
O(n)
。
快速排序
快速排序原理
快速排序的过程中,我们会先从原始数组中随机选一个数 pivot,以这个数为中点,将剩余的其他 n-1 个数划分为两个部分,小于 pivot 的交换到 pivot 的左边,大于 pivot 的交换到 pivot 的右边。然后再对左边和右边两部分的子数组递归进行此操作,直到最后只有一个元素为止,这个时候我们的整个数组就排序完毕了。
例子
利用快速排序对以下数组进行排序:
1 | $arr = [5, 1, 7, 8, 6]; |
选择最后一个数(6)作为中点,小于 6 的交换到
$arr
的左边,中间为 6,大于 6 的交换到$arr
的右边。经过上一步的交换后,
$arr
为[5, 1, 6, 8, 7]
,这个时候 6 其实就是最终排序完的位置了。再对 6 左边和右边的子数组
[5, 1]
、[8, 7]
执行第一步操作,如此直到子数组只有一个元素,结束排序。
我们可以发现,快速排序的过程,其实就是一次次地确定最终排序数组中间元素的过程。
PHP 实现
1 | function quick_sort(&$arr, $p, $r) |
算法分析
- 快速排序是否是稳定的排序算法?
由于快速排序的过程中,会发生元素前后的交换,所以不能保证稳定性,所以是不稳定的排序算法。
- 快速排序的时间复杂度是多少?
平均时间复杂度是 O(nlogn)
,但可能比这个大
- 快速排序的空间复杂度是多少?
快速排序是原地排序算法,排序过程不需要开辟新的空间来处理。
归并排序和快速排序对比
归并排序是稳定的排序算法,快速排序不是稳定的排序算法
归并排序的过程需要开辟临时的空间来进行合并操作,快速排序是原地排序算法,归并排序的空间复杂度太高
归并排序的时间复杂度是固定的,快速排序的时间复杂度会受实际数据有序度影响
实际使用中,快速排序用得更多,因为是原地排序算法,但是不能保证排序结果的稳定性