首页 > 代码库 > 快速排序思路整理
快速排序思路整理
引言:
快速排序和归并排序是面试当中常常被问到的两种排序算法,在研究过数据结构所有的排序算法后,个人认为最复杂的当属快速排序。从代码量上来看,快速排序并不多,我之所以认为快排难以掌握是因为快排是一种递归算法,同时终止条件较多。如果你刚刚把快排的思路整理过一遍可能觉得不难,然而一个月之后呢?
面试要求的是临场发挥,如果不是烂熟于心,基本就卡壳了。在面试官眼里,你和那些完全不懂快速排序算法的菜逼是一样的,也许实际上你可能私底下已经理解很多遍了,然而并没卵。所以当下问题就是,如何将快排烂熟于心?我觉得记忆一个算法应当在理解的基础上,当你心里面把所有终止和判断条件都一清二楚之后,手写快排就不是件难事了。
源代码:
1 package test; 2 /** 3 * 快速排序 4 * @author xuanxufeng 5 * 6 */ 7 public class QuickSort { 8 9 public void quickSort(int[] source,int low,int high){ 10 if(low<high){ 11 int pos = Partition(source,low,high); 12 quickSort(source,low,pos-1); 13 quickSort(source,pos+1,high); 14 } 15 } 16 17 private int Partition(int[] source, int low, int high) { 18 int temp = source[low]; 19 while (low < high) { 20 while (low < high && source[high] >= temp) high--; //一定要是大于等于,否则死循环!!! 21 source[low] = source[high]; 22 while (low < high && source[low] <= temp) low++; 23 source[high] = source[low]; 24 } 25 source[low] = temp; 26 return low; 27 } 28 public static void main(String[] args) { 29 // TODO Auto-generated method stub 30 int[] a = { 4, 2, 1, 6, 3, 6, 0, -5, 1, 1 }; 31 QuickSort qsort = new QuickSort(); 32 qsort.quickSort(a, 0, a.length - 1); 33 for (int i = 0; i < a.length; i++) { 34 System.out.print(a[i] + " "); 35 } 36 37 } 38 39 }
思路整理:
我想,对于大多数学过数据结构的人来说,上面代码的大体思路应该是没有什么问题的,如果有问题那你是不是要好好考虑下重学一遍数据结构了。
这里我从几个判定条件入手来捋一捋算法思路:
判定条件1 while (low < high && source[high] >= temp) 中的low < high
来看这个例子,假设我们要对 4 2 1 6 进行排序,即待排序数组 source = {4,2,1,6}; 快速排序的步骤大体是这样的:
4 2 1 6 | 4 ----> 4 2 1 6 | 4 ----> 1 2 1 6 | 4 ----> 1 2 1 6 | 4 ----> 1 2 1 6 | 4 ----> 1 2 4 6 | 4(跳出循环, source[low] = temp; ), 在这里,我们用蓝色代表low指向的元素,红色代表high指向的元素,品红色代码low和high相遇了,即 low = high,而|号后边的数字4代码选取的基准元素(默认是数组第一个数字)。所以,在移动low和high的过程中,终止条件就是low==high,即他们相遇了。
判定条件2
while (low < high) { ..... }
条件1中说了,low和high不能相遇。但是为什么外面还要设置一层循环呢?咱们再看一个例子:
4 2 5 7 2 3 1 6 | 4 ----> 4 2 5 7 2 3 1 6 | 4 ----> 1 2 5 7 2 3 1 6 | 4 ----> 1 2 5 7 2 3 1 6 | 4 ----> 1 2 5 7 2 3 1 6 | 4 ----> 1 2 5 7 2 3 5 6 | 4 , OK。到这里,读者可以考虑一下如果没有外面这层while循环,是Partition函数下一步干嘛?没错,会这样执行1 2 4 7 2 3 5 6 | 4,但是这个数组还没有遍历完啊! 并且说好的4左边的全部比4小,4右边的全部比4大的呢?
因此,通如果low和high没有相遇,就应该让它们一直遍历下去!
判定条件3 while (low < high && source[high] >= temp) 中的 source[high] >= temp
有没有想过为什么是>=呢? 如果换成>会是什么结果? 我可以明确的告诉你,把等号去掉就会死循环了!是不是倒吸一口凉气?我想,任何一个程序员应该都不敢轻视任意一个边界条件,所以即便是一个等号也要认真考半天,为什么要等号,可以不可以去掉?我们继续上个例子:
1 2 1 1 3 -5 0 | 1 ----> 0 2 1 1 3 -5 0 | 1 ----> 0 2 1 1 3 -5 2 | 1 ----> 0 2 1 1 3 -5 2 | 1 ----> 0 -5 1 1 3 -5 2 | 1 ----> 0 -5 1 1 3 -5 2 | 1 ----> 0 -5 1 1 3 1 2 | 1,好了,到了这里读者再往后推算几步,看看有什么发现?是不是一直在做反复赋值的死循环?因为从 low<high一直满足, 而source[high] > temp和source[low] < temp一直都不满足,所以low和high一直都不会移动了,所以自然就死循环了!这个时候,只有把>和<修改成>=和<=,low和high才会继续移动下去!其实,Partition函数返回位置的左边的数应当是小于或等于基准元素,返回位置右边的数应当是大于等于基准元素的值才对!!!!
判定条件4
if(low<high){ .... }
这个是快排作为递归的终止条件,为什么终止条件是这样呢?
我们先看第一种情况,假设Partition函数调用后的结果是这样的,..... 5 7,其中5对应于返回的位置pos. 那么在调用 quickSort(source,pos+1,high); 时对应的实参是多少?这是一个递归调用,这个情况下pos+1显然就等于high了,那么回到这个函数的判定条件,是不是这个Partition函数其实什么都没做?所以这个判定条件就是递归调用的出口!
我们再看第二种情况,假设Partition函数调用后的结果是这样的,.... 2 3 4 ,其中2对应于返回的pos,那么在调用quickSort(source,pos+1,high);后返回的位置应当就是3对应的位置了。这时候还要再往深处调用 quickSort(source,low,pos-1); 和 quickSort(source,pos+1,high); ,但是这个时候low对应于3,pos-1对应于2 ,不满足low<high;同时,pos+1对应于4,high也对应于4,也不满足!所以整个递归调用到这一层次就算是结束了!
好了,整个快排梳理就到这儿了,通过反复梳理记忆,快排一定会烂熟于心!
快速排序思路整理