版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_21918021/article/details/89095551
六、快速排序(Quick Sort)
快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
算法描述
快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:
- 从数列中挑出一个元素,称为 “基准”(pivot);
- 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
- 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
算法分析
最佳情况:T(n) = O(nlogn) 最差情况:T(n) = O(n2) 平均情况:T(n) = O(nlogn)
动图展示
代码实现
1、普通快速排序
缺点:
- 在近乎有序的数组下,该快排比归并要慢很多。
- 因为每次排序后,左右两个子递归规模相差悬殊,构成的二叉树平衡因子没有归并好。
- 在完全有序时,退化为O(n^2)
public static void quickSort(int[] arr, int n) {
__quickSort(arr,0,n-1);
System.out.println(Arrays.toString(arr));
}
private static void __quickSort(int[] arr, int left, int right) {
//首先处理递归到底的情况,左边大于右边
//可以优化,如果对于小数组时,可以用插入排序完成
if(left>=right)
return;
int p= partition(arr,left,right);
//对arr[l,,p-1]和arr[p+1,,,r]递归,每次比基准值小的在左,大的在右
__quickSort(arr, left, p-1);
__quickSort(arr, p+1, right);
}
//每次以第一个元素季即arr[l]为基准,比基准值小的在右,大的在左,
//返回基准值最终在数组中的位置
private static int partition(int[] arr, int left, int right) {
// 设定基准值(pivot)
int pivot=arr[left];
int j=left;
for(int i=left+1;i<=right;i++) {
//每次循环将小于v的往前换
if(arr[i]<pivot) {
swap(arr,i,j+1);
j++; //arr[j+1]始终是>pivot的,arr[j]是最后一个<=pivot的
}
}
//再将基准值移动到中间
swap(arr,left,j);
//最终j所指的位置就是中间值
return j;
}
//可以用数组来作为引用
private static void swap(int[] arr, int i, int j) {
if(i!=j) {
int temp =arr[j];
arr[j]=arr[i];
arr[i]=temp;
}
}
2、快排优化方法一【随机基准】
优化:
- 将第一个元素为基准值改为随机一个元素为基准值。
- 降低退化为O(n^2)的概率。
- 数学期望值为O(nlogn)
public static void quickSort2(int[]arr,int n) {
__quickSort2(arr,0,n-1);
System.out.println(Arrays.toString(arr));
}
private static void __quickSort2(int[] arr, int left, int right) {
if(left>=right)
return;
int p= partition(arr,left,right);
__quickSort2(arr, left, p-1);
__quickSort2(arr, p+1, right);
}
private static int partition(int[] arr, int left, int right) {
//double类型,产生一个l到r的随机数作为数组基准值下标
//将随机数与数组第一个数交换,即让随机数作为基准值
//减小近乎有序的概率
swap(arr, left, (int)Math.random()*(right-left+1)+left);
int pivot=arr[left];
int j=left;
for(int i=left+1;i<=right;i++) {
if(arr[i]<pivot) {
swap(arr,i,j+1);
j++;
}
}
swap(arr,left,j);
return j;
}
private static void swap(int[] arr, int i, int j) {
if(i!=j) {
int temp =arr[j];
arr[j]=arr[i];
arr[i]=temp;
}
}
3、快排优化方法二【两路快排】
对于方一方二中如果存在大量重复元素,当基准值为重复元素时。等于base的这些元素会聚集到右侧(或者稍微改改大小关系就会聚集到左侧)。 总之就会聚集到一边。这样在数组中重复数字很多的时候, 就又会导致两边子递归规模差距悬殊的情况,很有可能退化为O(n^2)
这时想把等于base的那些数分派到base两边,而不是让他们聚集到一起。
优化:
- 可以将swap去掉
public static void quickSort3(int[]arr,int n) {
__quickSort3(arr,0,n-1);
System.out.println(Arrays.toString(arr));
}
private static void __quickSort3(int[] arr, int l, int r) {
if(l>=r)
return;
int p= partition(arr,l,r);
__quickSort3(arr, l, p-1);
__quickSort3(arr, p+1, r);
}
private static int partition(int[] arr, int left, int right) {
swap(arr,left,(int)Math.random()*(right-left+1)+left);
//满足arr[l+1,i)<=v,arr(j,r]>=v
int pivot=arr[left];
int i=left+1,j=right;
while(true) {
//从左到右扫描,扫描出第一个比base大的元素,然后i停在那里
while(arr[i]<pivot && i<right)//arr[i]不能=pivot,会导致v聚集在一边
i++;
//从右到左扫描,扫描出第一个比base小的元素,然后j停在那里
while(arr[j]>pivot && j>=left)
j--;
if(i>=j)
break;
swap(arr, i, j);
i++;
j--;
}
//将基准值交换到合适位置
swap(arr, left, j);
return j;
}
private static void swap(int[] arr, int i, int j) {
if(i!=j) {
int temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
}
4、快排优化方法三【三路快排】
优化:
- 当大量数据,且重复数多时,用三路快排。将整个数组分为小于v,等于v,大于v三部分。
public static void quickSort4(int[]arr,int n) {
__quickSort4(arr,0,n-1);
System.out.println(Arrays.toString(arr));
}
private static void __quickSort4(int[] arr, int left, int right) {
if(left>=right)
return;
//因为==pivot的部分是一个数组而非单独的一个数,所以直接处理,不调用函数
swap(arr,left,(int)Math.random()*(right-left+1)+left);
int pivot=arr[left];
//变量定义要保证初始空间为空
int i=left+1;//arr[lt+1,i)==v
int lt=left;//arr[l+1,lt]<v
int gt=right+1;//arr[gt,r]>v
while (i<gt) {
if(arr[i]==pivot) {
i++;
}else if (arr[i]<pivot) {
swap(arr, i, lt+1);//画示意图,arr[lt+1]==pivot
i++;
lt++;
}else {
swap(arr, i, gt-1);//arr[gt-1]为未处理的数据
gt--;
}
}
swap(arr, left, lt);
__quickSort4(arr, left, lt-1);
__quickSort4(arr, gt, right);
}
private static void swap(int[] arr, int i, int j) {
if(i!=j) {
int temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
}