快速排序的基本原理
任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两⼦序列,左⼦序列中所有元素均⼩于基准值,右⼦序列中所有元素均⼤于基准值,然后最左右⼦序列重复该过程,直到所有元素都排列 在相应位置上为⽌。
代码详解
代码思路的关键是:找到基准值,并进行排序将基准值的左边是比基准值小的,而右边是比基准值大的
并将新的基准值作为新的序列的尾部或头部,并在新的序列中进行上面的操作.类似于二叉树的遍历,左树和右树的递归遍历
如图: 将6作为基准值,left从左向右找比key大的值, right从右向左找比key小的值. 找到后left和right进行交换. 当right和left相遇时就是都找完了. 所以将key和left或right的位置进行交换.此时就可以让左⼦序列中所有元素均⼩于基准值,右⼦序列中所有元素均⼤于基准值
hoare法
java
public static void quickSort(int[] array){
quick(array,0,array.length-1);
}
//将基准值的两边进行排序,并一直递归下去
public static void quick(int[] array ,int start, int end){
//当start>end时候则证明,此时没有要排序的数了,递归结束
if(start>end)return;
//寻找基准值
int pivot = partition(array,start,end);
//递归排序基准值的左边
quick(array,start,pivot-1);
//递归排序基准值的右边
quick(array,pivot+1,end);
}
//寻找基准值并返回,并将数组进行排序.
public static int partition(int[] array,int left,int right){
//先将第一个数作为基准进行判断
int key = array[left];
//将基准值的下标记录,后面交换要用到
int pivot = left;
//left从左边开始找比基准值大的数
//right从右边开始找比基准值小的数
while(left<right){
//加上left<right的条件,防止数组越界
while (left<right&&array[left]<=key){
left++;
}
while (left<right&&array[right]>=key){
right--;
}
//找到比基准值大的和小的值后,进行交换
//基准值的左边是小于基准值的,右边是大于基准值的
swap(array,right,left);
}
//将left和基准值进行交换,同理right也行
swap(array,left,pivot);
//此时返回的left就是交换后的基准值的下标
return left;
}
挖坑法
代码原理:
先将序列的第一个值记录下来. 此时第一个位置的值就已经不需要了, 主要的逻辑不是"交换"而是"覆盖", 先right从后往前找比基准值小的数,找到后将right的值直接覆盖到left的位置上.
同理left也是如此. 最后将记录下来的值覆盖到left所指的位置上.
代码:
java
public static int partitionDigHole(int[] array,int left,int right){
int key = array[left];
while(left<right){
//先从右边一直找到比基准小的数.
while (array[right]>=key && left<right){
right--;
}
//将右边的值覆盖到左边值
array[left]=array[right];
//再从左边开始一直找到比基准值大的数
while (array[left]<=key && left<right){
left++;
}
//将左边的值覆盖到右边
array[right]= array[left];
}
array[left]=key;
return left;
}
主要的代码和hoare法类似只是将逻辑从"交换"改为了"覆盖"
前后指针法
代码原理:
以升序排序为例, 序列的第一个数作为基准值. 利用两个指针:prev和cur, 初始时prev是cur的前一个数. cur一直寻找比基准值小的值,此时分为两种情况:
1.如果当前数比基准值大则cur++.
2.当cur所指的数比基准值小,则先让prev++接着判断两个指针所指的值 是否 相等.
相等时则进行prev和cur的值进行交换
不相等时则说明prev和cur指的是同一个值,没必要交换

代码部分:
java
public static int partitionPointer(int[] array,int left,int right){
int prev= left;
int cur = prev+1;
//当cur超过了right时,这个序列排序结束
while (cur<=right){
/*利用++prev先将prev+1再进行比较*/
if(array[cur]<array[left] && array[++prev]!=array[cur])
{
//交换时,prev所指的值是目前最后一个比基准值大的数.
swap(array,cur,prev);
}
cur++;
}
//最后时,将基准值与prev交换
swap(array,prev,left);
return prev;
}
总结:
这三种方法的最终结果虽然一样, 但中途产生的结果却不一定一样. 选择题考到时需要一个一个方法尝试.
时间复杂度:最坏情况为O(N^2), 平均情况下是O(NlogN);
空间复杂度:最坏情况下为O(N), 平均情况下是O(logN);
不稳定排序
额外部分
栈溢出问题的优化
当排序数据过多时,会报出''栈溢出''错误

优化思路:
栈溢出的原因主要是因为,左右树的递归次数太多, 开辟的栈空间太多. 导致栈溢出. 例如:当一个很大的有序的序列进行排序, 那么它的递归快速排序的树状结构是一个单叉树, 它的空间复杂度是O(N). 因为无法保证当前的基准值有无左子树或右子树(不知道基准值有没有比它大或比它小的值).
要解决这样的问题我们需要在一开始选择基准值时就要选一个肯定有左右子树的值.
三数取中
三数取中法是快速排序中优化基准值选择的一种方法. 其核心思想是从待排序数组的首(left)、尾(right)、中间三个位置选取第二大的数作为基准值,减少分区不平衡的概率。
代码:
java
public static void midOfThree(int[] array,int start,int end){
//start是后续排序的left,end是后续排序的right
//排序时是将left作为基准值判断的
int mid = (start+end)/2;
//判断3个数中谁是第二大的数,将其与start交换
if(array[start]>array[end]){
//mid>start>end的情况
if(array[mid]>array[start]){
//start是第二大的数无需交换
}
//start>end>mid的情况
else if(array[mid]<array[end]){
//end是第二大的数,将其与start交换
swap(array,start,end);
}
//start>mid>end的情况
else {
//mid是第二大的数,将其与start交换
swap(array,mid,start);
}
}
else{
//mid>end>start的情况
if(array[mid]>array[end]){
//end是第二大的数,将其与start交换
swap(array,start,end);
}
//mid>start>end的情况
else if(array[mid]<array[start]){
//此时不需要换.start就是第二大的数
}
//end>mid>start的情况
else {
//mid是第二大的数,将其与start交换
swap(array,mid,start);
}
}
}
利用栈方式排序
利用栈的"先进后出"的性质, 实现以非递归方式实现快速排序
代码:
java
public static void quickSortByStack(int[] array){
Stack<Integer> stack = new Stack<>();
int left = 0;
int right = array.length-1;
int pivot=partitionDigHole(array,left,right);
//判断为true则基准值的左边还有值,将基准值左边的序列放入栈中
if(pivot-1>left){
//将新的序列的右边存入栈中
stack.push(pivot-1);
//将新的序列的左边存入栈中
stack.push(left);
}
if(pivot+1<right){
//同理将基准值右边的新序列放进栈中
stack.push(right);
stack.push(pivot+1);
}
//经过一次之后,栈中至少会有两个元素.之后进行出栈操作
//只要栈中还有元素就要一直排序
while(!stack.isEmpty()){
//从栈中获取新的左右边界的值
left=stack.pop();
right=stack.pop();
//并进行新的排序
pivot=partitionDigHole(array,left,right);
//重复之前的步骤
if(pivot-1>left){
//将新的序列的右边存入栈中
stack.push(pivot-1);
//将新的序列的左边存入栈中
stack.push(left);
}
if(pivot+1<right){
stack.push(right);
stack.push(pivot+1);
}
}
}