1. 快速排序的动态图
2. 快速排序的伪代码
// 快速排序,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 的下标。
3. 快速排序的java代码实现
public int[] sortArray(int[] nums) {
quickSort(nums, 0, nums.length-1, nums.length);
return nums;
}
public void quickSort(int[] nums,int low,int high,int n){
if(low<high) {
int p = partition(nums,low,high);
quickSort(nums,low,p-1,n);
quickSort(nums,p+1,high,n);
}
}
int partition(int[] nums,int low,int high){
int p = nums[low];
while(low<high) {
while(low<high && nums[high]>=p)--high;
nums[low] = nums[high];
while(low<high && nums[low]<=p) ++low;
nums[high] = nums[low];
}
nums[low] = p;
return low;
}
4. 快排的时间复杂度分析
T(1) = C; n=1时,只需要常量级的执行时间,所以表示为C。 T(n) = 2*T(n/2) + n; n>1 |
根据上述递推公式,可得快排的时间复杂度近似为O(nlogn)。
如果数组中的数据原来已经是有序的了,比如 1,3,5,6,8。如果我们每次选择最后一个元素作为 pivot,那每次分区得到的两个区间都是不均等的。我们需要进行大约 n 次分区操作,才能完成快排的整个过程。每次分区我们平均要扫描大约 n/2 个元素,这种情况下,快排的时间复杂度就从 O(nlogn) 退化成了 O(n2)。
5. 对于分区点选择的优化
(1) 三数取中法
从区间的首、尾、中间,分别取出一个数,然后对比大小,取这 3 个数的中间值作为分区点。这样每间隔某个固定的长度,取数据出来比较,将中间值作为分区点的分区算法,肯定要比单纯取某一个数据更好。但是,如果要排序的数组比较大,那“三数取中”可能就不够了,可能要“五数取中”或者“十数取中”。
(2) 随机法
随机法就是每次从要排序的区间中,随机选择一个元素作为分区点。这种方法并不能保证每次分区点都选的比较好,但是从概率的角度来看,也不大可能会出现每次分区点都选得很差的情况,所以平均情况下,这样选的分区点是比较好的。时间复杂度退化为最糟糕的 O(n2) 的情况,出现的可能性不大。