细致分析快速排序!

快速排序是当下面试最容易考的题之一。初学算法基础的伙伴一定饱受它的折磨,因为快速排序的思想让初学者有一些发懵。
我写下这篇文章谈一谈自己对快速排序的理解,希望能够帮助初学者理解一下快速排序算法的流程。

快速排序的思想:
我要探讨的思想呢,不是教科书上的,而是我自己按容易理解的方法进行了改编。
请大家跟着我的思路:

宏观的来看:
1 我们拿出第一个数当作基准数,把所有比他小的数放在他左边,所有比他大的数放在右边
2 对mid左侧所有的数当成一个新的数列 进行上述排序
3 对mid右侧所有的数当成一个新的数列 进行上述排序

一个大数列,一点一点被我们分的越来越小,最后找到一个数做mid排完之后,左侧就剩下一个数,右侧就剩下一个数,就排完了

具体怎么实现找到基准数位置的呢??
我们拿到一个数列有n个数, 我们假设面前摆了n个水桶,每个水桶里面有一个数,现在数是乱序的,我们要不移动水桶,而把数取出来排序。
我们把n个水桶从左到右编号分别为0到n-1
ok!!!请大家跟着我的步骤哦~不要掉队!!
1 把0号水桶的数拿在手里, 这个数,我们把它当作基准数mid
2 拿一个名字叫做high的水瓶子当作标记,从最右侧n-1号桶开始往左找,找到第一个比基准数mid小的数的时候,我们把这个数取出来,放在0号位置,把high放进这个水桶里,标记一下现在这个水桶空了。
3 拿另一个名字为low的水瓶子当作标记,从刚才把high所在桶取出的数扔进的那个桶开始向右找,找到第一个比mid大的数,我们把low水瓶扔进去把数取出来,把这个数扔进high水瓶所在的桶里。
4 从刚刚取出high的水桶开始向左找,找到第一个比基准数mid小的数,把high瓶子扔进去把数取出来,然后把数扔进low瓶子所在的桶里,把low瓶子取出来
5 从刚取出low的桶开始向右找,找到第一个比基准数mid小的数,把low扔进桶,把数取出来放进high瓶子所在桶里,把high瓶子取出来
6 从刚取出high的桶向左找,找到第一个比基准数mid大的数,把high扔进去,把数取出来放到low所在的桶里,把low拿出来
7 从刚取出low的桶向右找,找到第一个比基准数mid小的数,把low扔进去,把数取出来放进high瓶子所在桶里,把high取出来
.......
我们发现从2到7 一直在做相同的工作,右侧的high水瓶子在一点一点向左推进,左侧的low水瓶子在不断向右推进
最后 low和high两个水平碰面了,我们把mid基准数放到这个碰面的水桶里
此时,mid左侧的数全都比mid小
mid右侧的数全都比mid大

然后,对mid 左侧所有的数当作一个新的数列,进行刚刚的排序
再对mid右侧所有的数当作一个新的数列,进行刚刚的操作

这样一直重复下去,数列就被排好了。

我们简化一下刚刚的步骤:
1 把第一个数当作基准数mid拿出来,0号位置空了
2 从右侧向左找第一个比基准数小的数记录位置high,把数拿出来放到mid所在位置上,high位置空了
3 mid右侧第一个向右找第一个比基准数大的数记录位置low,把数拿出来放到high空位上, 这时候左边low位置空了
4 从high开始向左找,找到第一个比mid小的数high重新记录位置,把数取出来放到low位置上,high位置空了
5 从low开始向右找,找到第一个比mid大的数low重新记录位置,把数取出来放到high位置上,low位置空了
....之后我们一直重复
low一点点向右,high一点点向左
当low和high碰头的时候,我们把mid放在碰头位置上
这时候形成的局面就是,mid左侧的数全都比mid小,mid右侧的数全都比mid大
然后我们把mid左侧所有数当成新的数列进行上述排序
把mid右侧所有数当成新的数列进行上述排序

这样一直重复下去,每次数列都被裁剪成更小的数列,一直到我们拿出第一个数做基准数,我发现右侧没有数左侧也没有数,这个数列排序结束了


但是我们刚刚忽略了一个严重的问题!!!!我们把比mid小的数放在左侧,把比mid大的数放在右侧了!那和mid相等的呢????
如果我们在两侧遇到mid相等的数,都移动到另一侧,实际上在程序中会陷入死循环,
每次都拿出数放入对面的空位,对面从空位开始比较又扔了回来
所以怎么办!!??
其实,当两边碰到相等都不管,或者只有一侧进行移动到另一侧,另一侧不管,这样最终排序就能正确。
如果我们都不管相等的数,再排子数列的时候,会判断出他是最大或者最小,再被移动到相应位置。

时间复杂度:
最坏时间复杂度: O(n^2) 我们假设拿到一个正序,每次拿到的基准数比较之后都没移动,所有的数都参与了比较,那就相当于每个数都跟其他所有数比了一次
最优时间复杂度: O(n log n)
我们粗略理解成每次把数列分成两半的话,一共会分多少次呢?就看长度n除以2 要除x次后n为1,x=log n
每次分开的数列n个数每个都跟基准数比了一次 所以粗略看成 是 n log n
稳定性: 不稳定
从我们刚才探讨的,碰到和基准数相等的问题上就能看出快速排序不稳定



我们上代码!!!
在python中这样实现



我们上代码!!!
在python中这样实现
 1 #快速排序 传入列表、开始位置和结束位置
 2 def quick_sort( li , start , end ):
 3     # 如果start和end碰头了,说明要我排的这个子数列就剩下一个数了,就不用排序了
 4     if not start < end :
 5         return
 6 
 7     mid = li[start] #拿出第一个数当作基准数mid
 8     low = start   #low来标记左侧从基准数始找比mid大的数的位置
 9     high = end  #high来标记右侧end向左找比mid小的数的位置
10 
11     # 我们要进行循环,只要low和high没有碰头就一直进行,当low和high相等说明碰头了
12     while low < high :
13         #从high开始向左,找到第一个比mid小或者等于mid的数,标记位置,(如果high的数比mid大,我们就左移high)
14         # 并且我们要确定找到之前,如果low和high碰头了,也不找了
15         while low < high and li[high] > mid :
16             high -= 1
17         #跳出while后,high所在的下标就是找到的右侧比mid小的数
18         #把找到的数放到左侧的空位 low 标记了这个空位
19         li[low] = li[high]
20         # 从low开始向右,找到第一个比mid大的数,标记位置,(如果low的数小于等于mid,我们就右移low)
21         # 并且我们要确定找到之前,如果low和high碰头了,也不找了
22         while low < high and li[low] <= mid :
23             low += 1
24         #跳出while循环后low所在的下标就是左侧比mid大的数所在位置
25         # 我们把找到的数放在右侧空位上,high标记了这个空位
26         li[high] = li[low]
27         #以上我们完成了一次 从右侧找到一个小数移到左侧,从左侧找到一个大数移动到右侧
28     #当这个while跳出来之后相当于low和high碰头了,我们把mid所在位置放在这个空位
29     li[low] = mid
30     #这个时候mid左侧看的数都比mid小,mid右侧的数都比mid大
31 
32     #然后我们对mid左侧所有数进行上述的排序
33     quick_sort( li , start, low-1 )
34     #我们mid右侧所有数进行上述排序
35     quick_sort( li , low +1 , end )
36 
37 
38 
39 #ok我们实践一下
40 if __name__ == '__main__':
41     li = [5,4,3,2,1]
42     quick_sort(li , 0 , len(li) -1 )
43     print(li)

 

如果后续有时间,我会再补一个c语言实现的快排上来!毕竟会python的伙伴太少了

ok!!一大早上我就写了c语言版本的。因为c语言中如果不是全局数组,函数就不能修改他,为了让排序通用,我们利用指针!

上代码!思想是一样的!要静下心来哦~

 1 #include "stdio.h"
 2 //c语言实现的快速排序算法
 3 //在c语言当中,如果不是全局数组,就不能在函数中直接改变数组里面的值,所以我们使用指针,传入待排序数列的开始和结尾元素指针
 4 void quickSort(int * start , int * end ){
 5     //每次都找到中间元素把两边子序列传进来再排序,如果发现传进来序列是一个元素,就不用执行了
 6     //当发现,一个子序列就一个元素,start和end就碰头了,就不用再排序了
 7     if( start>= end ){
 8         return ;
 9     }
10 
11     int * low = start;//用low标记待排序列开始位置,它向右寻找比基准数大的数的位置
12     int * high = end;//用high来标记待排序列结束位置,它向左寻找比基准数小的数的位置
13     int mid = *low;
14 
15 
16     //low从左向右,找到大的数扔到右边
17     //high从右向左,找到小的数扔到左边
18     //当low和high碰头了,这个位置就是中间mid的位置,此时跳出排序循环
19     while( low < high ){
20         //low和high没碰头 并且high的数大于等于mid,high就左移
21         //当high碰到第一个比mid小的数,就跳出循环,记录下这个数的位置
22         while( low < high && mid <= *high ){
23             high--;
24         }
25         //把high标记的比mid小的数扔到左边low的位置去
26         *low = *high;
27         //low和high没碰头 并且low的数小于等于mid,mid就右移
28         //当low碰到第一个比mid大的数,就跳出循环,记录这个数的位置
29         while( low < high && mid>= *low ){
30             low++;
31         }
32         //把low标记的这个数扔到右边high的位置上
33         *high = *low;
34 
35         //上述的过程我们一直做,在while循环当中,一直到low和high碰头了
36     }
37     //跳出循环说明low和high碰头了,这时候把mid扔到这个位置
38     //此时mid左侧的数都比mid小 mid右侧的数都比mid大
39     *low = mid;
40     //把mid左侧的子序列扔进去递归继续排序, 从start到mid之前一个
41     quickSort( start , low-1 );
42     //把mid右侧的子序列扔进去递归继续排序  从mid之后一个到end
43     quickSort( low+1 , end );
44 }
45 
46 void main(){
47     int i;
48     int a[10]={10,9,8,7,6,5,7,4,3,1};
49     quickSort(a,a+9);
50     for(i = 0; i<10 ;i++){
51         printf("%d ",a[i]);
52     }
53 }

 

 

希望对朋友们有所帮助!欢迎大家发现错误与我交流

原文地址:https://www.cnblogs.com/Lin-Yi/p/7308897.html