如果要排序一个数组,我们先把数组从中间分成前后两部分,然后对前后两部分分别排序,再将排好序的两部分合并在一起,这样整个数组就都有序了。

归并排序使用的就是分治思想。分治,顾名思义,就是分而治之,将一个大问题分解成小的子问题来解决。小的子问题解决了,大问题也就解决了。
归并排序的递推公式。
- 递推公式:
- merge_sort(p…r) = merge(merge_sort(p…q), merge_sort(q+1…r))
-
- 终止条件:
- p >= r 不用再继续分解
- // 归并排序算法, A是数组,n表示数组大小
- merge_sort(A, n) {
- merge_sort_c(A, 0, n-1)
- }
-
- // 递归调用函数
- merge_sort_c(A, p, r) {
- // 递归终止条件
- if p >= r then return
-
- // 取p到r之间的中间位置q
- q = (p+r) / 2
- // 分治递归
- merge_sort_c(A, p, q)
- merge_sort_c(A, q+1, r)
- // 将A[p...q]和A[q+1...r]合并为A[p...r]
- merge(A[p...r], A[p...q], A[q+1...r])
- }
我们把merge()函数写成伪代码
- merge(A[p...r], A[p...q], A[q+1...r]) {
- var i := p,j := q+1,k := 0 // 初始化变量i, j, k
- var tmp := new array[0...r-p] // 申请一个大小跟A[p...r]一样的临时数组
- while i<=q AND j<=r do {
- if A[i] <= A[j] {
- tmp[k++] = A[i++] // i++等于i:=i+1
- } else {
- tmp[k++] = A[j++]
- }
- }
-
- // 判断哪个子数组中有剩余的数据
- var start := i,end := q
- if j<=r then start := j, end:=r
-
- // 将剩余的数据拷贝到临时数组tmp
- while start <= end do {
- tmp[k++] = A[start++]
- }
-
- // 将tmp中的数组拷贝回A[p...r]
- for i:=0 to r-p do {
- A[p+i] = tmp[i]
- }
- }
归并排序是一个稳定的排序算法。
归并排序的时间复杂度的计算公式就是:
- T(1) = C; n=1时,只需要常量级的执行时间,所以表示为C。
- T(n) = 2*T(n/2) + n; n>1
- T(n) = 2*T(n/2) + n
- = 2*(2*T(n/4) + n/2) + n = 4*T(n/4) + 2*n
- = 4*(2*T(n/8) + n/4) + 2*n = 8*T(n/8) + 3*n
- = 8*(2*T(n/16) + n/8) + 3*n = 16*T(n/16) + 4*n
- ......
- = 2^k * T(n/2^k) + k * n
- ......
通过这样一步一步分解推导,我们可以得到T(n) = 2^kT(n/2^k)+kn。当T(n/2^k)=T(1)时,也就是n/2^k=1,我们得到k=log2n 。我们将k值代入上面的公式,得到T(n)=Cn+nlog2n 。如果我们用大O标记法来表示的话,T(n)就等于O(nlogn)。所以归并排序的时间复杂度是O(nlogn)。
空间复杂度就是O(nlogn)。
递推公式
- 递推公式:
- quick_sort(p…r) = quick_sort(p…q-1) + quick_sort(q+1, r)
-
- 终止条件:
- p >= r
将递推公式转化成递归代码。
- // 快速排序,A是数组,n表示数组的大小
- quick_sort(A, n) {
- quick_sort_c(A, 0, n-1)
- }
- // 快速排序递归函数,p,r为下标
- quick_sort_c(A, p, r) {
- if p >= r then return
-
- q = partition(A, p, r) // 获取分区点
- quick_sort_c(A, p, q-1)
- quick_sort_c(A, q+1, r)
- }
最坏情况下的时间复杂度是O(n2),但是平均情况下时间复杂度都是O(nlogn