快速排序
快速排序的思想:通过一趟排序将数据分割成独立的两部分,其中一部分数据都比另一部分的所有数据都要小,然后再按照此方法对这两部分数据分别进行快速排序,可以递归的进行。
时间复杂度:
- 好的情况(无序的):O(nlog2n)
- 坏的情况(有序的):O(n^2)
让我们先通过一个例子来了解一下:
12 5 4 25 10 15
首先:我们选择 12 为基准,比 12 小的放左边。
5 4 10 12 25 15
同样,用快速排序对 12 的前半部分进行排序:
4 5 10 12 25 15
此时,前半部分排序完毕,后半部分一样用快速排序:
4 5 10 12 15 25
我们可以用递归的方法来求解。
下面我们换个例子来深入的理解快速排序算法。
23 4 34 54 65 76 22 3 56 13 35 28 32 43 5
如下图所示,为第一趟的快速排序。
此时,23 的前半部分都比 23 小,23 的后半部分都比 23 大。
我们可以对它的前半部分和后半部分分别进行快速排序,直到排序结束。
下面我们用 java 来实现快速排序算法。
public static void quickSort(int[] array){ Quick(array,0,array.length -1); }
public static void Quick(int[] array,int start,int end){ int par = partion(array,start,end); if(par > start + 1){ Quick(array,start,par-1);//对前半段进行快速排序 } if(par < end-1){ Quick(array,par+1,end); //对后半段进行快速排序。 } }
下面这个函数是返回基准位置的。即上面的例子中第一趟快速排序后 23 的位置。
public static int partion(int[] array,int start,int end){ int tmp = array[start]; while(start < end){ while(start < end && array[end] >= tmp){ --end; } if(start >= end){ break; }else{ array[start] = array[end]; } while(start < end && array[start] <= tmp){ ++start; } if(start >= end){ break; }else{ array[end] = array[start]; } } array[start] = tmp; return start; }
这是我们用递归来解决它,其实还有另外一种办法,我们可以利用栈来解决这个问题。
还是上面的例子,让我们来分析一下。
23 4 34 54 65 76 22 3 56 13 35 28 32 43 5
首先:我们来给来个快速排序,将它分成两部分。
5 4 13 23 22 23 76 65 56 54 35 28 32 43 34
然后:和前面一样,我们分别对左边和右边进行快速排序,但不同的是,这次我们要把下标 start 和 end 存入栈中,然后将右边的下标 start 和 end 也存入栈中。
如图
接着我们从栈里取出,使 low 和 high 分别指向 start 和 end ,然后进行快速排序。
依次类推,我们一边向栈里取出元素,一边向栈里存储元素,直到栈为空,表示我们的排序结束。
下面我们用 java 实现一下这个非递归的。
public static void QuickSort(int[] array){ int[] stack = new int[array.length]; int top = 0; int low = 0; int high = array.length -1; int par = partion(array,low,high); //入栈 if(par > low+1){ stack[top++] = low; stack[top++] = par-1; } if(par < high-1){ stack[top++] = par+1; stack[top++] = high; } //出栈 while(top > 0){ high = stack[--top]; low = stack[--top]; par = partion(array,low,high); if(par > low+1){ stack[top++] = low; stack[top++] = par-1; } if(par < high-1){ stack[top++] = par+1; stack[top++] = high; } }
快速排序的优化
快速排序有以下优化方案
- 当待排序数组当中数据比较少的时候,用直接插入法。
- 聚集相同元素法。
- 随机选取基准法。
当数据比较少时,采用直接插入法排序
当数据比较少时,采用直接插入法排序比较高效。
所以我们只需要在快速排序的方法上增加一个判断语句即可
以下为用 java 实现
public static void Quick1(int[] array,int start,int end){ //当数据少于 100 时用直接插入法排序即可 if((end - start + 1) < 100){ insertSort(array,start,end); return; } int par = partion(array,start,end); if(par > start + 1){ Quick1(array,start,par-1); } if(par < end-1){ Quick1(array,par+1,end); } }
聚集相同元素法
简单来说就是找和基准相同的元素,这样子可以减少递归的次数。
下面是把和基准相同的元素聚集到一起。
这里附上代码
public static int[] focus(int[] array,int start,int end,int par,int left,int right){ //聚集和基准相同的元素 //左边找 int parleft = par-1; int parright = par + 1; for(int i = par-1;i >= start;i--){ if(array[i] == array[par]){ if(i != parleft){ swap(array,i,parleft); parleft--; }else{ parleft--; } } } left = parleft; //右边找 for(int j = par+1;j <= end;j++){ if(array[j] == array[par]){ if(j != parright){ swap(array,j,parright); parright++; }else{ parright++; } } } right = parright; int[] brray = new int[2]; brray[0] =left; brray[1] = right; return brray; }
然后我们对于基准左边和基准右边分别进行排序,和优化前不同的是我们不用再比较和基准相同的元素了。
对于左边,我们令 low = 0,high = pl-1。
对于右边,我们令 low = pr-1, high = 长度-1。
public static void Quick2(int[] array,int start,int end){ int par = partion(array,start,end); int left = par-1; int right = par+1; int[] b = focus(array,start,end,par,left,right); left = brrar[0]; right = brrar[1]; if(par > start + 1){ Quick2(array,start,left); } if(par < end-1){ Quick2(array,right,end); } }
随机选取基准法。
在数据已经有效的情况下,快速排序时间复杂度太高,为了解决这个问题,课一随机选取基准来提高效率。
public static void Quick3(int[] array,int start,int end){ int par = partion(array,start,end); //假设已经有序,就会提高效率 swap(array, start, (int)(Math.random()*10%(end-start))+start); if(par > start + 1){ Quick(array,start,par-1); } if(par < end-1){ Quick(array,par+1,end); } }