快速排序思路整理

引言:

快速排序和归并排序是面试当中常常被问到的两种排序算法,在研究过数据结构所有的排序算法后,个人认为最复杂的当属快速排序。从代码量上来看,快速排序并不多,我之所以认为快排难以掌握是因为快排是一种递归算法,同时终止条件较多。如果你刚刚把快排的思路整理过一遍可能觉得不难,然而一个月之后呢?

面试要求的是临场发挥,如果不是烂熟于心,基本就卡壳了。在面试官眼里,你和那些完全不懂快速排序算法的菜逼是一样的,也许实际上你可能私底下已经理解很多遍了,然而并没卵。所以当下问题就是,如何将快排烂熟于心?我觉得记忆一个算法应当在理解的基础上,当你心里面把所有终止和判断条件都一清二楚之后,手写快排就不是件难事了。

源代码:

 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,也不满足!所以整个递归调用到这一层次就算是结束了!

 好了,整个快排梳理就到这儿了,通过反复梳理记忆,快排一定会烂熟于心!

 

posted on 2017-05-06 17:57  一生不可自决  阅读(2196)  评论(0编辑  收藏  举报

导航