快速排序(JAVA详细讲解快速排序的四种方式)
1.快速排序的实质
快速排序本质上是在数组的一个范围内,对未经划分的数据,通过计算,分为左右两区或者三区,从左到右判断一个数属于左边的小于等于区(或分开),还是右边的大于区,判断一次,让两边的区域扩展一次
2.快速排序1.0
以给定范围内最右边的数为排序的基准,比该数大的放在右边的大于区,反之则放在小于区,将大于区设置在R处(包含),小于等于区设置在L-1处,从范围内的左边到右边进行遍历
如果这个数属于左边的区域,那么将下标所指的数与小于区下一个数做交换,同时小于区扩大,判断数的下标+1
如果这个数大于基数,那么将下标所指的数与大于区的下一个也就是左边的数做交换,大于区扩大,但是判断数字的下标不+1
因为我们要判断的是没有经过计算的数,而刚才换过来的数,也没有经过判断,因此我们需要对该数也进行一个判断
到最后,将判断数的下标与大于区的下标重叠,我们再将最右边的数换到大于区的最左边,这时候呢大于区指针左边指的是小于等于区的最右边,而大于区指针++则是他区域的最左边
大于区指针所指的数肯定是基准数,我们返回 传入函数的L和小于区下标++ 以及大于区下标和传入函数的L即可(这里面小于区下标可能没有动,可能是-1,大于区没有动的话是R,怎么传取决于后面你想要在第二次更细节的快排时怎么用,自己不混淆即可)
//快排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.当判断书大于基准数时,交换大于区指针--的数,与当当前数做交换,大于区指针--,判断下表不动,因为换过来的数没有进行判断
//2.0 等于区//以最右边为界限去选一个数来进行分区// 之后返回等于区间的第一个,和最后一个// 因为可能大于小于区的指针是-1或者超length//特殊情况呢是L>R 和L=R//相等时,返回的是L R 大于时返回的是-1 -1public 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)
//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了,但是如果是随机的那么数学最后算的就是NlogNif (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这就是最原始的步骤
我们利用自定义栈,对最原始的步骤进行模拟,运算,实现即可,以减少内存的损耗,减少内存溢出的可能性以及时间的损耗
//用模拟栈实现 非递归形式//我们刚才的缺点是什么?一直在递归函数,会导致内存溢出//如果我们手动模拟,然后每次是选择循环判断进行操作//那么内存耗费就会大大减少//我们只需要用栈模拟每次返回的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 -1int 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});}}}