快速排序
原理:
- 从区间中取一个数据做基准值,按照该基准值将区间分割左右两部分
左部分 基准值 右部分 - 按照快排的思想排左半部分
- 按照快排的思想排右半部分
时间复杂度:O(N)
最优情况下:O(NlogN)
场景:每次都均分
最差情况下:O(N^2),每次划分都拿到极值
场景:数据有序或者接近有序
应用场景:数据量大,比较随机的(数据杂乱)
数据量大:将来递归胜读可能比较深–>每次递归都需要在栈中压入一个栈帧
栈帧:函数在运行期间要保存的中间结果–>比如:函数中的局部变量、参数、返回值信息
栈是有大小的–>可能导致栈溢出
public static void quickSort(int[] arr,int left ,int right){
// if(right-left<16){
// 数据量小的时候可以调用别的排序方法,避免栈溢出,
// 降低栈溢出的概率,但是不能杜绝
// 如果想要绝对的杜绝递归过深导致栈溢出的情况:
// 不要使用递归,用循环
递归转循环,有些时候可以直接转,有些情况不能直接转,需要借助栈(二叉树前、中、后序非递归遍历)
为什么将递归递归转化为循环需要借助栈的数据结构?
栈的特性:后进先出
递归:先调用的后退出,后调用的先退出
// }
// else
if(right-left > 1){
//说明区间中至少有两个元素
//按照基准值对【left,right)
int dir=partion(arr,left,right);
//递归排基准值的左半侧
quickSort(arr,left,dir);
//递归排基准值的右半侧
quickSort(arr,dir+1,right);
}
}
标准值优化:使用三数取中发对每次取到的最值防止出现极端情况,加大出现均分的概率,拿到极值的概率就降低了,认为:快排看的是平均复杂度O(NlogN)
public static int getIndex(int[] arr,int left,int right ){
int mid=left +((right-left)>>1);
if(arr[left]<arr[right-1]){
if(arr[mid]<arr[left]){
return left;
}else if(arr[mid]>arr[right-1]){
return right-1;
}else {
return mid;
}
}
else{
if(arr[mid]>arr[left]){
return left;
}else if(arr[mid]<arr[right-1]){
return right-1;
}else{
return mid;
}
}
}
方式一:Hoare
1. 让begin从前往后找,找比基准值大的元素,找到之后停止
2. 让end从后往前找,找比基准值小的元素,找到之后停止
3. 将begin和end标记的元素进行交换
4. 将基准值与begin位置的数据进行交换
public static int partion(int[] arr,int left,int right){
int begin=left;
int end=right-1;
int key=arr[end];
while (begin<end){
//1.begin从前往后找,找比基准值大的元素
while (begin<end && arr[begin]<=key){
begin++;
}
//2.end从后往前,找比基准值小的元素
while (begin<end && arr[end]>=key){
end--;
}
if(begin<end){
swap(arr,begin,end);
}
}
if (begin!=right-1){
swap(arr,begin,right-1);
}
return begin;
}
方式二:挖坑法
基本思路和Hoare 法一致,只是不再进行交换,而是进行赋值(填坑+挖坑)
public static int partion2(int[] arr,int left,int right){
int begin=left;
int end =right-1;
int mid=getIndex(arr,left,right);
swap(arr,mid,right-1);
int key=arr[end];
while (begin<end) {
//1.begin从前往后找,找比基准值大的元素
while (begin < end && arr[begin] <= key) {
begin++;
}
//找到了一个比基准值大的元素,用该元素填end位置的元素
if(begin<end){
arr[end--]=arr[begin];
}
//2.end从后往前,找比基准值小的元素
while (begin<end && arr[end]>=key){
end--;
}
//end从后往前找到了一个比基准值小的元素,用该元素填begin 位置的坑
if(begin<end){
arr[begin++]=arr[end];
}
}
//用Key填最后一个坑
arr[begin]=key;
return begin;
}
方法三:前后索引法一前一后
- 刚开始时:prev和cur是一前一后的关系
- 一段时间后:prev和cur之间有距离
prev+1,cur之间的元素都比基准值大
public static int partion3(int[] arr,int left,int right){
int cur=left;
int prev=cur-1;
int mid=getIndex(arr,left,right);
swap(arr,mid,right-1);
int key=arr[right-1];
while(cur<right){
if(arr[cur]<key && ++prev != cur){
swap(arr,cur,prev);
}
++cur;
}
if(++prev!=right-1){
swap(arr,prev,right-1);
}
return prev;
}
循环方式:
数据量小的时候可以调用别的排序方法,避免栈溢出,
降低栈溢出的概率,但是不能杜绝
如果想要绝对的杜绝递归过深导致栈溢出的情况:
不要使用递归,用循环
递归转循环,有些时候可以直接转,有些情况不能直接转,需要借助栈(二叉树前、中、后序非递归遍历)
为什么将递归递归转化为循环需要借助栈的数据结构?
栈的特性:后进先出
递归:先调用的后退出,后调用的先退出
public static int partion(int[] arr,int left,int right){
int begin=left;
int end=right-1;
int key=arr[end];
while (begin<end){
//1.begin从前往后找,找比基准值大的元素
while (begin<end && arr[begin]<=key){
begin++;
}
//2.end从后往前,找比基准值小的元素
while (begin<end && arr[end]>=key){
end--;
}
if(begin<end){
swap(arr,begin,end);
}
}
if (begin!=right-1){
swap(arr,begin,right-1);
}
return begin;
}
public static void swap(int[] arr,int left,int right){
int tmp=arr[left];
arr[left]=arr[right];
arr[right]=tmp;
}
void quickSort(int[] arr){
Stack<Integer> s=new Stack<>();
s.push(arr.length);
s.push(0);
while (!s.empty()){
int left=s.pop();
int right=s.pop();
if(right-left>1){
int dir=partion(arr,left,right);
s.push(right);
s.push(dir+1);
s.push(dir);
s.push(left);
}
}
}