快速排序的思想:在数组中找到一个基准数(pivot),然后将数组分为2部分,左边的数比基准数小,右边的数比基准数大,然后继续对左右区间进行同样操作,直到各个区间剩下1个数,就能够产生有序的数组了。
性能分析:最差的情况下,每次选取的基准数都是区间最大或最小值,导致每次只划分一个区域,需要n-1次递归才能结束排序,这时候的时间复杂度为O(n^2);
最好的情况下,每次选取的基准数都是区间的中间值,每次都划分2个区间,那就需要进行log n次递归才能结束排序,这时候的时间复杂度为O(nlog n),平均情况下,时间复杂度为O(nlog n)。快速排序是不稳定的,需要的辅助空间为O(nlog n)
当数组基本有序的时候,快排就没有什么优势,基本退化为冒泡排序,可以对基准数的选取进行优化,尽量选取中间值。
第一种实现方案:
function quickSort(arr,left,right){
if(left = right) return;
let index = partition(pivot,left,right);//选取key下标
if(left<key){
quickSort(arr,left,index-1)//对左部分进行排序
}
if(key<right){
quickSort(arr,index+1,right)//对右部分进行排序
}
}
function partition(arr,left,right){
let key=arr[left];//让key等于左边第一个数
while(left<right){//遍历一遍
while(key<=arr[right] && left<right){//如果key比a[right]小,则右边递减,继续比较
right--;
}
[arr[left],arr[right]] = [arr[right],arr[left]]//交换
while(key>=arr[left] && left<right){//如果key比arr[left]大,则左边递增,继续比较
left++;
}
[arr[left],arr[right]] = [arr[right],arr[left]]//交换
}
return left;//返回key的下标
}
对基准数的切分一般有3种方法,固定切分,随机切分,三取样切分。固定切分的效率不好,常用的是随机切分,但是最坏的情况也会出现O(n^2),建议使用三取样切分。
function quickSort(arr,left,right){
if(left = right) return;
let index = partition(arr,left,right)
if(left<key){
quickSort(arr,left,index-1)
}
if(key<right){
quickSort(arr,index,right)
}
}
function partition(){
let key = getKey(arr,left,right);
while(left<right){
while(key<=arr[right] && left<right){
right--
}
exchange(arr,left,right)
while(key>=arr[left] && left<right){
left++
}
exchange(arr,left,right)
}
return left;
}
function getKey(arr,left,right){
let mid = arr[Math.floor((left+right)/2)];
if(a[mid]>a[right]){exchange(arr,mid,right)};
if(a[left]>a[right]){exchange(arr,left,right)};
if(a[mid]>a[left]){exchange(arr,left,right)};
let key = arr[left]//现在arr[mid]<arr[left]<arr[right]
return key;
}
function exchange(arr,a,b){
[arr[a],arr[b]]=[arr[b],arr[a]]
}