快速排序的两种写法(站在巨人的肩膀上加深理解)
2017-09-11 15:42
651 查看
前言
这是面试总结,好多要求手写快排,堆排序,思路简单,但是手写还是有一定难度的,所以总结下。由于本人太渣,不足之处请各位不吝赐教,在下方留言。在网上搜索了下,发现有两种写法很受欢迎,且两种方法实现思路还是有点区别的,了解过区别之后,对快排应该可以更加熟悉。无论哪种写法,本质没变:
分治
将所有比temp大的移动到右边,比temp小的移动到左边。
分治,分而治之,在递归中用的尤其多,这篇博文就不会讲解这个问题了,不然文章就太长了。
首先提出一个问题,快排一定要从右往左排吗?难道不能从左往右排吗?这个问题可以自己先想想,后面会给出答案。
这里快排的讲解按照默认的从小往大排,选取当前治理的数组序列中第一个位置。
两种写法中,最经典的,就是下面这种
1 挖坑填数
经典讲解在这里这是我自己临时手撸的,没有验证,主要是把自己的思想记录下来,真正代码可以看上面这篇博客(一下讲解中,temp代表选取的比较标准)
public void quick_sort(int l,int r){ if(l>=r) return ; //算法核心:以temp为基础,比temp小的全部移动到左边,比temp大的全部移动到右边 int i = l,j=r,temp=a[l]; while(i<j){ while(i<j&&a[j]>=temp){//从右往左找 j--; } if(i<j){//说明找到了比temp小的值,把这个值填到i的坑 a[i++] = a[j]; } //a[j]给a[i]了,i的坑j来填,那么j的坑也要有人填,所以从i往右找到第一个比temp大的值,把这个值填到j的坑 while(i<j&&a[i]<=temp){ i++; } if(i<j){ a[j--] = a[i]; } } //i==j,所有坑填完了,该考虑temp这个数填哪个坑了。 a[i]=temp; quick_sort(l,i-1); quick_sort(i+1,r); }
很多算法代码的思路过程可能现在记得很清楚,但是长时间不使用,我们很快便忘记了。所以记住这个算法的这个写法的一些特性,是回忆代码,加深理解,以及运用的关键。因此我们敲完后需要多想想。
这个排序法,每次排完序后,数组中都有两个位置的数是相同的,在一次循环完成之前,看不到temp的值,当一次循环全部完了之后,才把i == j这个坑填上temp。注意,一次循环后,所有元素相对于temp的位置就不会再变了,也就是说,此时比temp小的,最终结果仍然在temp的左边,比temp大的仍然在temp右边(因为这里递归左右两边不会相互影响。)
这里提出个问题,如果从左往右开始找,会怎么样?
如果从左开始找,找到第一个比temp大的值得时候,填谁的坑?temp的坑吗?i的坑吗?j的坑吗?都不行。
这里我们可以记住,从左找,还是从右找,是以你的选取标准(这里我们默认的选取标准是l,也就是数组的第一个元素)来的,你选取第一个位置作为标准,那么不管你从大到小排序,还是从小到大排序,都需要从右开始往左找。原因很简单:从左开始找,找到的第一个比temp大(小)的数会先和0位置交换,再和后面交换,即数组两边都会出现比比temp大(小)的数。
例如
4,2,6,3,1,10,12,0
如果从左开始找,在6的位置找到了比temp大的,占了4的位置,变成
6,2,6,3,1,10,12,0
那么从右往左找的时候,找到比temp小的放到6位置,变为
6,2,0,3,1,10,12,0
明显看出不对。
如果你选取的标准是最后一个位置,那么就要从左边开始找。
理解了
while(i < j && a[j] > temp) j--;
中a[j] > temp的作用了吧?保证了j右边都数都比temp大,这样从左边找到比temp大的数时,才能放心放到j这里来,同理,保证了i左边的数都比temp小………)
这个a[j]>temp就是快排中的数能够交换的保证。
参考这个博客:快排为什么从右往左排
2 直接替换 不占坑
这个算法的详解见连接戳这里
void quicksort(int left,int right) { int i,j,t,temp; if(left>right) return; temp=a[left]; //temp中存的就是基准数 i=left; j=right; while(i!=j) { //顺序很重要,要先从右边开始找 while(a[j]>=temp && i<j) j--; //再找左边的 while(a[i]<=temp && i<j) i++; //交换两个数在数组中的位置 if(i<j) { t=a[i]; a[i]=a[j]; a[j]=t; } } //最终将基准数归位 a[left]=a[i]; a[i]=temp; quicksort(left,i-1);//继续处理左边的,这里是一个递归的过程 quicksort(i+1,right);//继续处理右边的 ,这里是一个递归的过程 }
这个算法就没有占坑,而是直接一次性找到比他大和比他小的两个位置,两个位置直接替换。这是另一种写法,这里主要说一下刚开始看这个
4000
形式的快排算法会出现的疑问。
如上面代码,最后一定是把temp和i==j这个位置替换。如果i==j的位置的数比temp大呢?那么也会替换吗?如果替换,那就不满足所有左边的数都比temp小了。
仔细思考后发现,不存在这种情况: 在i==j的位置的数大于temp。看看作者在文中说的:
//顺序很重要,要先从右边开始找
如果从左边开始找会怎么样?
首先需要明白,找坑的时候(交换之前),让i 停留的位置,一定是比temp大的,让j 停留的位置,一定是比temp小的。
如果从左边开始找,i最后会停在比temp大的值的位置,这时候再找j,会发现i==j停留在比temp大的位置,最总会把会将temp和a[i]替换,也就是比temp大的放在了temp左边。
如果从右边开始找,j会停在比temp小的值的位置,这时候在i==j的时候,仍然可以保证temp和比他小的值交换。
综上,从右往左找很重要(这个从右开始的原因就和上面不一样了)。
以上这两种算法均可,个人认为第二种更好理解和实现。
应用
这里说一下快速排序算法的应用(排序就不说了),他的核心思想是把比temp小的数都往左移动,比temp大的数都往右移动,也就是说,假设temp的位置是i,那么temp一定是第(i+1)小的数,第(n-i)个大的数(数组从0位置开始存储),这个就是快排的一个经典应用,面试中经常出现——求出数组中第k个最大的数,或者求出数组的前k个大的数。int k; public int quick_sort(int l,int r){ if(l>=r) return -1; //算法核心:以temp为基础,比temp小的全部移动到左边,比temp大的全部移动到右边 int i = l,j=r,temp=a[l]; while(i<j){ while(i<j&&a[j]>=temp){//从右往左找 j--; } if(i<j){//说明找到了比temp小的值,把这个值填到i的坑 a[i++] = a[j]; } //a[j]给a[i]了,i的坑j来填,那么j的坑也要有人填,所以从i往右找到第一个比temp大的值,把这个值填到j的坑 while(i<j&&a[i]<=temp){ i++; } if(i<j){ a[j--] = a[i]; } } //i==j,所有坑填完了,该考虑temp这个数填哪个坑了。 a[i]=temp; int ans=-1; if(i==k) return a[i]; else if(i>k)//这个数一定在左边 ans = quick_sort(l,i-1); else//这个数一定在右边 ans = quick_sort(i+1,r); return ans; }
应用的应用(为了面试方便,放在一起)
再来说一下这个求出数组的前k个大的数,这个题还有一个解法,就是堆排,堆排序我个人认为比快排难一点,因为涉及到一种类似递归的思想,建树,删树,插入,不断替换等,完全理解起来比快排难度大点。思路:针对此题来说,可以维护一个大小为k的最小堆,然后拿后面的点与最小堆的根节点来进行对比,如果大于根节点,就把根节点的值替换,并调整为最小堆。
记住,前k个大的数,维护最小堆,每次比较如果比根节点大,那就替换,再调整为最小堆(原因:我们的目标是找到前k个最大的值,如果维护最大堆,你怎么知道子节点能不能替换?最小堆的根结点root一定是堆中最小的,如果新数比root大,说明当前最小堆不是前k个最大值组成的最小堆)。同理,前k个小的数,维护最大堆。这个最小堆的根节点一定是第k个最大的数。
最后
再来谈一下辅助空间和稳定性的问题。这里留个空间吧,后面有时间再补上。相关文章推荐
- 快速排序partition过程常见的两种写法+快速排序非递归实现
- 快速排序partition过程常见的两种写法+快速排序非递归实现
- 快速排序partition过程常见的两种写法+快速排序非递归实现
- 快速排序的几种写法
- 快速排序用js、java和C的写法
- 发现一个快速理解排序方法的小网站,小视频清晰易懂
- 快速排序的两种实现
- 快速排序 小讲 - (一)【 理解 + 例题 】
- 快速排序两种实现方式
- 快速排序 两种实现
- 快速简单理解——直接插入排序
- C++实现快速排序,递归写法
- 快速排序的简单应用及回调函数的初步理解
- 快速排序的理解
- 快速排序的两种实现方式(Java)
- 快速排序理解(挖坑填坑比喻理解)
- 快速排序精简版总容易理解版
- 详谈排序算法之交换类排序(两种方法实现快速排序【思路一致】)
- 快速排序的理解
- 快速排序lua实现 递归和栈两种实现