1.快速排序的实质
快速排序本质上是在数组的一个范围内,对未经划分的数据,通过计算,分为左右两区或者三区,从左到右判断一个数属于左边的小于等于区(或分开),还是右边的大于区,判断一次,让两边的区域扩展一次
2.快速排序1.0
以给定范围内最右边的数为排序的基准,比该数大的放在右边的大于区,反之则放在小于区,将大于区设置在R处(包含),小于等于区设置在L-1处,从范围内的左边到右边进行遍历
如果这个数属于左边的区域,那么将下标所指的数与小于区下一个数做交换,同时小于区扩大,判断数的下标+1
如果这个数大于基数,那么将下标所指的数与大于区的下一个也就是左边的数做交换,大于区扩大,但是判断数字的下标不+1
因为我们要判断的是没有经过计算的数,而刚才换过来的数,也没有经过判断,因此我们需要对该数也进行一个判断
到最后,将判断数的下标与大于区的下标重叠,我们再将最右边的数换到大于区的最左边,这时候呢大于区指针左边指的是小于等于区的最右边,而大于区指针++则是他区域的最左边
大于区指针所指的数肯定是基准数,我们返回 传入函数的L和小于区下标++ 以及大于区下标和传入函数的L即可(这里面小于区下标可能没有动,可能是-1,大于区没有动的话是R,怎么传取决于后面你想要在第二次更细节的快排时怎么用,自己不混淆即可)
java
//快排1.0 <=区 >区,然后i<大于区 让最右边的掉回来,
public static void partion1(int[] arr, int L, int R){
if (arr==null|| arr.length<2){
return;
}
if (L>=R) {
return;
}
int eqIndex=quickSort(arr,L,R);
partion1(arr,L,eqIndex-1);
partion1(arr,eqIndex+1,R);
}
public static int quickSort(int[]arr,int L,int R){
int num=arr[R];
//int Lp=L-1;
int Rp=R;
int i=L;
while (i<Rp){
if (arr[i]<=num)i++;
else {
int temp=arr[i];
arr[i]=arr[Rp-1];
arr[Rp-1]=temp;
Rp--;
}
}
arr[R]=arr[i];
arr[i]=num;
return i;
}
3.快速排序2.0
我们刚才呢,每次根据最右边的数进行分区时,只传回来了一个数,仔细想一下会发现,其实可能等于区不止一个,那怎么进行划分呢?
1.传入要判断数组的最左边L 和最右边R
2.设置小于区下标为L-1 大于区下标为R+1
3.将R处的数字用一个变量提出来,以便后续判断
4.判断下标从L开始直到判断下表与大于区下标重叠
5.当所判断数与基准数相等时,小于区和大于区的指针均不移动,判断下标++
6.当所判断的数小于基准数时,我们就应该交换小于区指针下一个单位的数与当前所判断数 然后让小于区的指针右移,判断下表++
7.当判断书大于基准数时,交换大于区指针--的数,与当当前数做交换,大于区指针--,判断下表不动,因为换过来的数没有进行判断
java
//2.0 等于区
//以最右边为界限去选一个数来进行分区
// 之后返回等于区间的第一个,和最后一个
// 因为可能大于小于区的指针是-1或者超length
//特殊情况呢是L>R 和L=R
//相等时,返回的是L R 大于时返回的是-1 -1
public static void possess2(int []arr,int L,int R){
if (L>=R)return;
int []equalnums=equalflag(arr,L,R);
possess2(arr,L,equalnums[0]-1);
possess2(arr,equalnums[1]+1,R);
}
public static int[] equalflag(int[] arr,int L,int R){
if (L>R){
return new int[]{-1,-1};
}
if (L==R){
return new int[]{L,R};
}
int less=L-1;
//以右边的数为交换的基准
int more=R;
int i=L;
while (i<more){
//如果是=那就直接右移指针
//如果是小于那就变化less指针,交换less下一个与i i位置肯定是小的
// less下一个可能是等于区,也可能是i 同时i++这样就肯定正确了
//如果是大于,那就变换与more指针前面数的位置,同时i不++因为传过来的数
if (arr[i]==arr[R])i++;
else if (arr[i]<arr[R]){
int temp=arr[i];
arr[i]=arr[less+1];
arr[less+1]=temp;
less++;
i++;
}
else {
int temp=arr[i];
arr[i]=arr[more-1];
arr[more-1]=temp;
more--;
}
}
//more区有一个是=的
int temp=arr[R];
arr[R]=arr[i];
arr[i]=temp;
//more肯定不会越界,less可能会越界
return new int[]{less+1,more};
}
3.快速排序3.0
刚才呢我们一直都是用最右边的数作为基准,如果传入的数组是随机的,那还好,但是如果判断的数组不是随机的,那么该算法的时间复杂度就会提升,当数组每次划分区域刚好为原来的一半时,时间复杂度最低,那怎么能够确保呢?
我们随机挑选L-R上的数为基准数,之后呢通过复杂的数学计算得到期望就等于NlogN,接近于每次划分为两半的递归然后用master公式进行计算
每次划分两半,每次规模减半,logb a=1 而每次递归前进行的数组运算的时间复杂度为O(N)
master公式计算得到结果,二者相等,时间复杂度的结果为O(N的logb a次方 *logN)=O(N的logb a次方 *logN)
java
//3.0 随机选一个数
public static void possess3(int arr[],int L,int R){
if (L>=R)return;
int []equationNum=randomEquateSession(arr,L,R);
possess3(arr,L,equationNum[0]-1);
possess3(arr,equationNum[0]+1,R);
}
public static int [] randomEquateSession(int arr[],int L,int R){
//随机选一个是为了制造随机,给的数组可能不是随机的,给的数组如果是最差的
//每一次分的两半极不均匀,那么复杂度就不能是NlogN了
//那么我们如果固定选R,时间复杂度就不能是NlogN了,但是如果是随机的那么数学最后算的就是NlogN
if (L>R){return new int[]{-1,-1};}
if (L==R){return new int[]{L,R};}
int randomIndex= L+(int) (Math.random()*(R-L+1));
int compareNum=arr[randomIndex];
int less=L-1;
int more=R+1;
int i=L;
while (i<more){
if (arr[i]==compareNum)i++;
else if (arr[i]<compareNum){
int temp=arr[i];
arr[i]=arr[less+1];
arr[less+1]=temp;
i++;
less++;
}else {
int temp=arr[i];
arr[i]=arr[more-1];
arr[more-1]=temp;
more--;
}
}
return new int[]{less+1,more-1};
}
4.快速排序4.0(非递归)
我们前三种均为计算出下一次两个分区的左右边界,之后调用函数进行递归调用,这样很明显,太过于耗费内存,那怎么转为非递归的呢?
我们把步骤拆到最小的会发现我们是,先在数组的L R范围做以基数为标准的划分区域,之后对每个区域再进行细化分,ok这就是最原始的步骤
我们利用自定义栈,对最原始的步骤进行模拟,运算,实现即可,以减少内存的损耗,减少内存溢出的可能性以及时间的损耗
java
//用模拟栈实现 非递归形式
//我们刚才的缺点是什么?一直在递归函数,会导致内存溢出
//如果我们手动模拟,然后每次是选择循环判断进行操作
//那么内存耗费就会大大减少
//我们只需要用栈模拟每次返回的R L即可
//不需要开辟新的空间去耗费内存
public static void partionSort(int arr[]){
if (arr==null||arr.length<2)return;
Stack<int[]> stack=new Stack();
stack.push(new int[]{0,arr.length-1});
while (!stack.empty()){
int[] pop = stack.pop();
if (pop[0]>=pop[1])continue;
//左右边界是pop范围的+1 -1
int randomIndex=pop[0]+(int) (Math.random()*(pop[1]-pop[0]+1));
int less=pop[0]-1;
int more=pop[1]+1;
int i=less+1;
int compareNum=arr[randomIndex];
while (i<more){
if (arr[i]==compareNum)i++;
else if (arr[i]<compareNum){
int temp=arr[i];
arr[i]=arr[less+1];
arr[less+1]=temp;
less++;
i++;
}else {
int temp=arr[i];
arr[i]=arr[more-1];
arr[more-1]=temp;
more--;
}
}
if (less-pop[0]>pop[1]-more){
stack.push(new int[]{pop[0],less});
stack.push(new int[]{more,pop[1]});
}else {
stack.push(new int[]{more,pop[1]});
stack.push(new int[]{pop[0],less});
}
}
}