一.算法描述
- 每一轮排序选择一个基准点(pivot)进行分区
- 让小于基准点的元素的进入一个分区,大于基准点的元素的进入另一个分区
- 当分区完成时,基准点元素的位置就是其最终位置
- 在子分区内重复以上过程,直至子分区元素个数少于等于 1,体现的是分而治之的思想从以上描述可以看出,一个关键在于分区算法,常见的有洛穆托分区方案、双边循环分区方案、霍尔分区方案
二. 单边循环快排(lomuto 洛穆托分区方案)
- 选择最右元素作为基准点元素
- j 指针负责找到比基准点小的元素,一旦找到则与 i 进行交换
- i 指针维护小于基准点元素的边界,也是每次交换的目标索引
- 最后基准点与 i 交换,i 即为分区位置
三. 双边循环快排(不完全等价于 hoare 霍尔分区方案)
- 选择最左元素作为基准点元素
- j 指针负责从右向左找比基准点小的元素,i 指针负责从左向右找比基准点大的元素,一旦找到二者交换,直至 i,j 相交
- 最后基准点与 i(此时 i 与 j 相等)交换,i 即为分区位置要点
- 基准点在左边,并且要先 j 后 i
- while( i< j && a[j] > pv ) j–
- while ( i< j && a[i] <=pv ) i++
四. 快排特点
- 平均时间复杂度是 O(NlogN),最坏时间复杂度 O(N^2)
- 数据量较大时,优势非常明显
- 属于不稳定排序
五. 代码实现
class Solution {
public int[] sortArray(int[] nums) {
quickSort(nums, 0, nums.length - 1);
return nums;
}
private void quickSort(int[] nums, int l, int r){
if(l >= r){
return;
}
int mid = partition(nums, l , r);
quickSort(nums, l, mid - 1);
quickSort(nums, mid + 1, r);
}
private int partition1(int[] nums, int l, int r){
int pv = nums[r];
int i = l;
for(int j = l; j < r; j++){
if(nums[j] < pv){
swap(nums, i, j);
i++;
}
}
swap(nums, i, r);
return i;
}
private int partition2(int[] nums, int l, int r){
int pv = nums[l];
int i = l;
int j = r;
while(i < j){
while(i < j && nums[j] > pv){
j--;
}
while(i < j && nums[i] <= pv){
i++;
}
swap(nums, i, j);
}
swap(nums, l, i);
return i;
}
private void swap(int[] nums, int i, int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}