最好 | 最坏 | 平均 | 是否原地算法? | 是否稳定? | |
---|---|---|---|---|---|
归并排序 | \( nlogn \) |
\( nlogn \) |
\( nlogn \) |
× | √ |
快速排序 | \( nlogn \) |
\( n^2 \) |
\( nlogn \) |
√ | × |
归并排序
归并排序是一种基于分治思想的排序算法,通过将数列划分为若干个子数列,然后将子数列按顺序“收集”起来,以到达最终有序的目的。是一种自底向上的算法(子数列先有序,整体最后有序)
实现
可以通过递归实现,其递归表达式为:
r = p + (q - p) / 2
MergeSort(a[p, q]) = merge(MergeSort(a[p, r]), MergeSort(a[r+1, q]))
退出条件:
p >= q
时间复杂度
假设对n个元素进行归并排序需要时间T(n),那分解成两个子数组排序的时间为T(n/2)。
merge()函数合并两个有序子数组的时间复杂度为O(n)。
则有:
T(1) = C
T(n) = 2 * T(n/2) + n
继续分解得:
T(n) = 2 * T(n/2) + n
= 2 * (2 * T(n/4) + n/2) + n
= 2 * (2 * (2 * T(n/8) + n/4) + n/2) + n
……
=$ 2^k $
* T(n/$ 2^k $
) + k * n
……
当T(n/$ 2^k $
)=T(1)时,
n/
$ 2^k $
= 1
=> k =$ log_{2}n $
将k值代入上面公式,得到:
T(n) = C * n + n *
$ log_{2}n $
= O(nlogn)
快速排序
快速排序也是基于分治思想实现的。与归并算法不同的是,它是一种自顶向下的算法,会挑选出一个“枢轴变量”,然后保证枢轴变量左侧的元素都小于或等于它,右侧的元素都大于它。
实现
用递归实现时,递归表达式为:
pivot = partition(p, q)
quickSort(0, pivot - 1)
quickSort(pivot + 1, q)
退出条件:
p >= q
partition()为分区函数,将原数列以枢轴变量为中心分为两部分。
为了方便处理,一般直接选择最后一个元素为枢轴变量。比起取中间元素为枢轴变量的方法,这种方法不但逻辑清晰些,在细节方面也不需要耗费过多脑力去思考边界值的处理。
以最后一个元素为枢轴变量
func partition(arr []int, p int, q int) int {
i := p
j := p
for ; i < q; i++ {
if arr[i] < arr[q] {
tmp := arr[i]
arr[i] = arr[j]
arr[j] = tmp
j++
}
}
tmp := arr[q]
arr[q] = arr[j]
arr[j] = tmp
return j
}
以中间元素为枢轴变量
func partition(arr []int, p int, q int) int {
mid := (p + q) / 2
i := p
j := q
for ; i < j; {
for ; i <= j; i++ {
if arr[i] >= arr[mid] {
break
}
}
for ; j > i; j-- {
if arr[j] < arr[mid] {
break
}
}
if i <= j {
tmp := arr[i]
arr[i] = arr[j]
arr[j] = tmp
if i == mid {
mid = j
} else if j == mid {
mid = i
}
} else {
i--
}
}
tmp := arr[i]
arr[i] = arr[mid]
arr[mid] = tmp
return j
}