一、快速排序
1.什么是快速排序?
快速排序是交换排序的一种,本质上快速排序就是采用“分而治之”的策略(分治法),将问题规模减小,再而对问题分别进行处理的排序算法。
2.快速排序的基本原理。
快速排序的原理:在已有元素中,任选一个元素作为“基准”,根据“基准”,将未排序元素划分为两个子序列,一个子序列的元素均小于基准元素,而另一个子序列的元素均大于基准元素,然后递归地对这两个子序列进行排序。
快速排序示意图(44为基准):
3.实现快速排序的具体过程。
①选取一个元素作为基准,与末尾元素交换位置。 ②设置两个指针(Low和High),分别指向首元素与倒数第二个元素。 ③Low指针由左向右扫描,其位置左侧放置的都是遍历过的或交换过的小于基准的元素; —High指针从右向左扫描,其位置右侧放置的都是遍历过的或交换过的大于基准的元素。 —首先是Low指针向后扫描,遇到大于基准的元素停止; —然后是High指针向前扫描,遇到小于基准的元素停止。 ④在指针还未错位时(在 Low < High 时),将 High 和 Low 指向的元素交换位置。 ⑤重复上述操作,直至 High指针 和 Low指针 错位 ⑥错位后停止,将基准元素与指针Low指向元素交换位置,至此,我们成功将小于基准的元素放在其左,大于基准的元素放于其右! ——这代表我们成功完成了一次划分,以基准为边界分别划分成小于和大于基准的两个子序列。 ⑦递归地对两个子序列,用同样的方法进行快速排序即可。
二、算法优化
需注意: —在特殊情况下,比如在序列基本有序的情况下,若每次划分得到的两个子序列都是1比(N-1)的情况时,快速排序执行时间复杂度接近于冒泡排序的O(N2)。 —为了避免最坏结果,我们需要在下标为Low,High,(Low+High)/ 2的三个元素中取得中间值元素作为序列的基准,这样有可能避免最坏情况。
三、快速排序代码实现(优化后)。
import java.util.Arrays;
public class QuickSort {
public static void main(String[] args) {
int[] arr = {85,12,59,36,62,44,43,94,7,35,52};
String str1 = Arrays.toString(arr);
Qsort(arr,0,arr.length - 1);
String str2 = Arrays.toString(arr);
System.out.println(str1);
System.out.println(str2);
}
public static void Qsort(int[] arr,int left,int right){
if(left < right){
int Low;
int High;
int Pivote;
Pivote = Median3(arr,left,right);
Low = left;
High = right - 1;
while(true){
while(arr[++Low] < Pivote);
while((Low<High) && arr[--High] > Pivote);
if(Low < High) swap(arr,Low,High);
else break;
}
if(Low < right)
swap(arr,right-1,Low);
Qsort(arr,left,Low-1);
Qsort(arr,Low+1,right);
}
}
public static void swap(int[] arr,int i,int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public static int Median3(int[] arr,int left,int right){
int center = left + ((right-left) >> 1);
if(arr[left] > arr[center])
swap(arr,left,center);
if(arr[left] > arr[right])
swap(arr,left,right);
if(arr[center] > arr[right])
swap(arr,center,right);
swap(arr,center,right - 1);
return arr[right-1];
}
控制台输出:
四、算法分析
时间复杂度
快速排序时间复杂度分析较为复杂。 最好情况下,每次划分都得到等长的子序列,每一层递归比较的时间复杂度为O(N),而递归层次深度为O(log2N),即最好情况是O(Nlog2N)。 最差时间复杂度上文有解释过,为O(N2)。
五、快排思想在实际题目中的运用
(更新于:2022.9.10)
题目一、剑指Offer 40.最小的k个数
LeetCode原题链接:剑指Offer 40.最小的k个数 题目描述:
输入整数数组 arr ,找出其中最小的 k 个数。
例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
示例 1:
输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]
示例 2:
输入:arr = [0,1,2,1], k = 1
输出:[0]
解题思路: 经过上文的讲解,我们知道了,快速排序就是选定一个基准,通过一定操作让小于基准的元素放在基准左侧子序列,将大于基准的元素放在基准右侧子序列的排序算法。
题目的要求是:找出数组中最小的k个数,是不是觉得有什么地方十分相似?当我们的第k个数是快速排序的基准时,加上基准左侧的子序列,正是要求的最小的k个数…
所以解决问题的关键点就在于: 我们把基准及其左侧子序列的元素个数记作num 当num等于预期需要的数量k,输出划分好的序列即可。 当num大于预期需要的数量k,我们递归地对左序列进行同样的快排操作。 当num小于预期需要的数量k,我们递归地对基准之后与第k个元素之前的序列进行操作。(此时预期的数量就变为k-num了,因为num个数已经划分好,只需要划分剩下的元素,直至达到预期)
建议在理解代码时,画图辅助理解,特别是快排划分的那部分,有助于清晰地理解快排划分左子序列的具体过程。
实现代码:
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
Qselect(arr,0,arr.length-1,k);
int[] Oput = new int[k];
for(int i = 0;i < k; ++i){
Oput[i] = arr[i];
}
return Oput;
}
public void Qselect(int[] arr,int L,int R,int k){
if(L >= R)
return;
int p = getPivote(arr,L,R);
int num = p-L+1;
if(num == k ){
return;
}else if(num > k){
Qselect(arr,L,p-1,k);
}else{
Qselect(arr,p+1,R,k-num);
}
}
public int getPivote(int[] arr,int L,int R){
int pivote = (int)(Math.random()*(R-L+1)+L);
swap(arr,pivote,R);
return Qsort(arr,L,R);
}
public int Qsort(int[] arr,int L,int R){
int Low = L-1;
int High = R;
for(int l = L;l < High; l++){
if(arr[l] <= arr[High]){
Low++;
swap(arr,Low,l);
}
}
swap(arr,R,Low+1);
return Low+1;
}
public void swap(int[] arr,int i,int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
提交结果: 总结,只要理解了基本的解题思路,问题就不算难,重要的还是要记住快速排序的基本模式是如何的。
|