13-快速排序

快速排序(QuickSort)

如果要排序数组中下标从 p 到 r 之间的一组数据,我们选择 p 到 r 之间的任意一个数据作为 pivot(分区点)

遍历 p 到 r 之间的数据,将小于 pivot 的放到左边,将大于 pivot 的放到右边,将 pivot 放到中间,如下图:以5作为分区点

数组 p 到 r 之间的数据就被分成了三个部分:

前面 p 到 q-1 之间都是小于 pivot 的,

中间是 pivot,

后面的 q+1 到 r 之间是大于 pivot 的

根据分治、递归的处理思想,用递归排序下标从 p 到 q-1 之间的数据和下标从 q+1 到 r 之间的数据,直到区间缩小为 1,就说明所有的数据都有序了

递推公式:

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)

}

partition() 分区函数,就是随机选择一个元素作为 pivot(一般情况下,可以选择 p 到 r 区间的最后一个元素),然后对 A[p…r] 分区,函数返回 pivot 的下标。

partition() 分区函数:

我们申请两个临时数组 X 和 Y,遍历 A[p…r],

将小于 pivot 的元素都拷贝到临时数组 X,

将大于 pivot 的元素都拷贝到临时数组 Y,

最后再将数组 X 和数组 Y 中数据顺序拷贝到 A[p…r]。

如果按照这种思路实现的话,partition() 函数就需要很多额外的内存空间,就不是原地排序了

原地排序伪代码:

partition(A, p, r) { 

pivot := A[r] 

i := p for

j := p to r-1 do { 

if A[j] < pivot {

swap A[i] with A[j] i := i+1 

}

} 

swap A[i] with A[r]

return i

通过游标 i 把 A[p…r-1] 分成两部分。

A[p…i-1] 的元素都是小于 pivot 的,我们暂且叫它“已处理区间”,A[i…r-1] 是“未处理区间”。

我们每次都从未处理的区间 A[i…r-1] 中取一个元素 A[j],与 pivot 对比,如果小于 pivot,则将其加入到已处理区间的尾部,也就是 A[i] 的位置。

因为分区的过程涉及交换操作,如果数组中有两个相同的元素,比如序列 6,8,7,6,3,5,9,4,在经过第一次分区操作之后,两个 6 的相对先后顺序就会改变。所以,快速排序并不是一个稳定的排序算法。

归并排序的处理过程是由下到上的,先处理子问题,然后再合并。

快速排序的处理过程是由上到下的,先分区,然后再处理子问题。

归并排序虽然是稳定的、时间复杂度为 O(nlogn) 的排序算法,但是它是非原地排序算法。归并之所以是非原地排序算法,主要原因是合并函数无法在原地执行。

快速排序通过设计巧妙的原地分区函数,可以实现原地排序,解决了归并排序占用太多内存的问题

快排的时间复杂度:

快排的时间复杂度也是 O(nlogn)。

T(1) = C; n=1时,只需要常量级的执行时间,所以表示为C。

T(n) = 2*T(n/2) + n; n>1

发布了127 篇原创文章 · 获赞 24 · 访问量 13万+

猜你喜欢

转载自blog.csdn.net/Linzhongyilisha/article/details/102712142