快速排序核心思想
核心:在于选主元pivot和partition过程 以及 二分的思想。
一般的快速排序是选取第一个元素作为主元,然后通过一趟排序将要排序的数据进行分组,比主元小的分到左边,比主元大的分到右边。
然后再分别对左边和右边的部分进行递归。
快速排序三个步骤
- 选择基准:在待排序列中,按照某种方式挑出一个元素,作为pivot
- 划分操作:将数组中比主元小的交换到左边,比主元大的交换到右边
- 递归对两个序列进行快速排序,直到序列为空或者只有一个元素
Partition
public int partition(int arr[],int start,int end){
int index=start;//这里不能随便写!
int pivot=arr[index];
//使用两个指针,i和j一个从开开始扫描,一个从尾部开始扫描
int i=start,j=end;
//partition整体的复杂度为O(N)
while(i!=j){// 此处仅仅是比较i是否等于j,不算时间复杂度
//比较一半
while(arr[j]>pivot&&i<j){
j--;
}
//比较另一半
while(arr[i]<=pivot&&i<j){
i++;
}
//交换当前的i和j
swap(arr,i,j);
}
if(i==j){ //i已经遇到j的
swap(arr,index,i);
}
return i;
}
选主元三种方法
第一种方法就是直接选第一个序列第一个数,begin下标的元素作为主元。
这种方法比较糟糕,因为如果数组已经有序,
每次划分只能让待排序列的序列减一。此时为最坏情况,沦为冒泡排序O(N^2)
第二种方法是随机基准元,加入随机性,这在大数据的排序中比较有效,
即我们的划分期望是比较好的,不会有特别差的情况出现。
所以随机快速排序对于大多数情况达到O(NlogN)的时间复杂度。
但是当数组全部元素都是相等的,
这时就是最坏情况。
第三种
选取中位数作为我们的主元,这是我们的最佳划分。
但是我们只能去估计,一般可以采用Median3
的方法,选择头元素和尾元素,以及一个位于中心的元素来估计中位数。
Median3消除了预排序输入的不好情况。
举例:待排序序列为:8 1 4 9 6 3 5 2 7 0
左边为:8,右边为0,中间为6
public int median3(int[] arr,int low,int high){
//获取序列的中间元素下标,比较三个元素的值,返回中间值的下标
int m=(low+high)/2;
//保证左边部分较小
//正常 1 2 3 不用比较
//考虑 2 1 3
// 3 2 1
// 2 3 1
// 3 1 2
/// 1 3 2
if(arr[low]>arr[m])
swap(arr,low,m);
//保证左边小,此时最左边的一定是最小的元素
if(arr[low]>arr[high]){
swap(arr,low,high);
}
//如果中间比左边小,保证左边部分较小
if(arr[m]>arr[high]){
swap(arr,m,high);
}
//将中位数换到high-1去
//我们其实不用考虑左右两端的数,因为它们肯定已经是有序的了
//只需要考虑A[low+1] ..A[hight-2]
swap(arr,m,high-1); //hide pivot
return arr[high-1]; //high-1;
}
本质就是一个 将3个无序的数排序的算法(非递减)。
现在我们的m是最右边的数的前一个,即我们现在只是对low+1到 high-2进行考虑,因为pivot是high-1,pivot会在patition过程中被
快排实现
public void Sort(int[] A,int p,int r){
int len=A.length;
int i,j,pivot;
pivot=median3(A,p,r);
if(p+3<=r){ //median
i=p;j=r-1;
while(A[++i]<pivot){}
while(A[--j]>pivot){}
for(;;){
if(i<j){
swap(A,i,j);
}else{
break;
}
}
//划分完之后,交换一下A[r-1]与A[i],因为A[i]位置的值由于median3的交换实际上是A[high-1]的值,也就是说它肯定是比pivot大的,而A[r-1]是pivot的值,所以它应该回归到中位位置!
swap(A,i,r-1);
Sort(A,p,i-1);
Sort(A,i+1,r);
}else{
//1~3长度的数组采用插入排序
InsertionSort(A);
}
}
性能分析
对比冒泡快排之所以快?
因为相比冒泡排序,每次交换都是跳跃式的,每次排序的时候设置一个基准点。每次交换的时候不会像冒泡一样每次只是在相邻数之间交换,它交换的距离大得多。
时间复杂度
最好情况: O(NlogN)
最坏情况: O(N^2)
空间复杂度
O(logN), 这时递归造成的空间复杂度,因为递归树的深度是logN。
稳定性:不稳定
因为关键字的比较和交换是跳跃进行的。