算法一——排序

文章出处:极客时间《数据结构和算法之美》-作者:王争。该系列文章是本人的学习笔记。

分析排序算法的角度

算法的执行效率

算法的执行效率一般从时间复杂度以及比较、交换次数来考虑。

时间复杂度

时间复杂度需要考虑最好情况、最坏情况、平均情况时间复杂度。同时我们需要考虑复杂度的系数、常数和低阶。这是因为时间复杂度反应的是数据规模n很大时候的一个增长趋势,所以会忽略系数、常数、低阶。但是实际软件开发中,n可能是10、100、1000。这个时候就不能忽略系数、常数、低阶。

比较、交换次数

我们常见的排序算法大多数是基于比较的。基于比较的排序算法会涉及比较和元素交换两种操作。所以需要考虑。

内存消耗

有些排序算法的空间消耗是O(1)的,有些是O(n)。原地排序是空间复杂度是O(1)的排序算法。

算法的稳定性

排序算法的稳定性是指如果数组中有两个值相同的元素,经过排序算法排序之后,这两个元素的前后顺序不发生变化。这种排序算法叫做稳定的排序算法。如果前后顺序发生变化了,则称为不稳定性排序算法。
用途是:一般排序的是一个对象。例如希望按照订单金额从小到大排序订单,金额相同的时候按照订单时间从小到大排序。我们可以先按照订单时间从小到大排序,然后按照订单金额从小到大排序。如果是稳定的则可以实现这样的要求。

总结:考核的指标有:最好时间复杂度、最坏时间复杂度、平均时间复杂度、比较次数、交换次数、空间复杂度、算法稳定性。

基于比较的排序算法比较

有序度

数组中具有有序关系的元素对的个数。有序元素对的表示是这样的:

a [ i ] < = a [ j ] , 如 果 i < j a[i]<=a[j],如果i<j a[i]<=a[j]i<j
一个数组中最大的有序度是 n ∗ ( n − 1 ) 2 \dfrac{n*(n-1)}{2} 2n(n1),称为满有序度。
逆序度=满有序度-有序度

冒泡排序

特点:比较相邻元素,交换的也是相邻元素
空间复杂度:O(1),原地排序
稳定性:稳定(只要相邻元素相等不做交换)
最好时间复杂度:要排序的数组已经有序,需要冒泡一遍,O(n)。
最坏时间复杂度:顺序刚好相反,需要n次冒泡,O( n 2 n^2 n2)。
平均时间复杂度:平均时间复杂度本来是一个加权平均值。但这里太过复杂,使用有序度来解决。
排序算法的时间复杂度主要由比较次数和交换次数决定。
因为每交换一次,数组的有序度加1。所以最少的交换次数是0,当输入完全有序。最多 n ∗ ( n − 1 ) 2 \dfrac{n*(n-1)}{2} 2n(n1),当输入完全无序。取个平均值表示初始有序度既不高也不低: n ∗ ( n − 1 ) 4 \dfrac{n*(n-1)}{4} 4n(n1)。也就是说平均情况下需要 n ∗ ( n − 1 ) 4 \dfrac{n*(n-1)}{4} 4n(n1)次交换。
比较次数肯定多于交换次数,这个值的上限是O( n 2 n^2 n2)。
所以平均时间复杂度是O( n 2 n^2 n2)。

插入排序

特点:往一个有序的数组中插入一个元素。将数组分为有序区间和无序区间。
空间复杂度:O(1),原地排序
稳定性:稳定
最好时间复杂度:如果输入是已经排序好的,我们每次只在有序区间的末尾比较一次数据,所以是O(n)。注意:这里是从尾到头遍历已经排序好的数据。
最坏时间复杂度:如果是输入是逆袭的,相当于每次在数组的第一个位置插入元素,需要移动大量的数据。复杂度O( n 2 n^2 n2)。
平均时间复杂度:在数组中插入一个数据的平均时间复杂度是O(n)(以前课程里面讨论过)。循环插入n次。所以复杂度O( n 2 n^2 n2)。
一般项目中选择插入排序。因为插入排序的数据交换工作比较简单。随机生成 10000 个数组,每个数组中包含 200 个数据。在我电脑上,冒泡排序394ms,插入排序6ms。

选择排序

特点:将数组分为有序区间和无序区间。每次在无序区间选择最小元素插入在有序区间末尾。
空间复杂度:O(1),原地排序
稳定性:不稳定。找到的最小元素需要与前面的元素互换位置,可能改变相同元素的前后顺序。
最好时间复杂度:因为选择排序每次都需要在无序区间选择最小元素,所以每次都需要n次比较。需要重复n次。所以复杂度O( n 2 n^2 n2)。
最坏时间复杂度:O( n 2 n^2 n2)
平均时间复杂度:O( n 2 n^2 n2)

归并排序

特点:使用分治、分区的思想。例如解决下标从p到q数组的排序问题,可以先解决从p到r,从r+1到q两个子数组的排序问题,然后将两个子数组合并,就解决了。
空间复杂度:因为合并部分需要单独申请空间,而最多申请O(n)。不是原地排序。
稳定性:只需要保证合并过程中相同元素前后位置不变,就可以保证稳定性。
时间复杂度:O(nlogn)。

快速排序

特点:使用分治、分区的思想。例如解决下标从p到q数组的排序问题。我们先选择一个pivot元素,可以是a[q]。小于pivot的元素放在左边,大于pivot的元素放在右边,pivot放在中间,位置假如是i。然后再递归的解决从p到i-1的排序问题,从i+1到q的排序问题。
递推公式:quick_sort(p…q) = quick_sort(p,i-1)+ quick_sort(i+1,q)
退出条件:p>=q
快速排序找到pivot元素的合适位置的函数是partition。这个函数的实现有一些技巧。
空间复杂度:O(1),原地排序
最好时间复杂度:最好的情况是pivot每次可以将数组一分为二,时间复杂度O( n l o g n nlogn nlogn)。
最坏时间复杂度:如果每次pivot都将数组分成两部分(自己和其他部分),时间复杂度就是O( n 2 n^2 n2)
平均时间复杂度:O( n l o g n nlogn nlogn),以后的课程中会介绍。使用递归树来理解。
分治、分区思想的拓展:用来解决无序问题。例如查找一个无序数组的第k大元素。我们选择数组a[0…n-1]的最后一个元素a[n-1]作为pivot。对数组做原地拆分,变为a[0…p-1]、a[p]、a[p+1…n-1]。如果k=p+1,那么a[p]就是答案。如果k<p+1,那么答案在a[0…p-1]中,继续做分区。如果k>p+1,则答案在a[p+1…n-1],继续做分区。

快速排序vs归并排序

在这里插入图片描述
归并排序是由下到上,先处理子问题(的排序问题),然后再合并。
快速排序是由上到下,先分区,再处理子问题(的排序问题)。

如何优化快速排序

快排在某些情况下时间复杂度会退化为 O ( n 2 ) O(n^2) O(n2)。这种时候是因为选取的pivot每次都将数组分成了自己和其他两个部分,如果选择的pivot合适就不会出现这样的情况。
1 三数取中法。可以选择头、尾、中间三个元素的中间值作为pivot。如果数组比较长,可以使用“五数取中法”、“十数取中法”。
2 随机法。从要排序的区间中每次随机选择一个数作为pivot。从概率角度讲,有选择不合适的时候,但不会每次都不合适。

快排可以优化的还有空间。快排使用的是递归,会因为栈大小的限制,引起栈溢出。可以在堆上模拟实现一个函数调用栈,手动模拟递归出栈、压栈的过程,这样就没有栈空间大小限制了。

线性排序算法

桶排序

特点:将要排序的数据放入几个桶,每个桶内的数据再单独排序。按照桶依次取出就实现了整体数据排序。
应用范围:1 要排序的数据能够很容易划分到m个桶。并且桶之间有天然的大小关系。2 各个桶的数据分布要均匀,如果集中在一个桶,那就没有意义了。3 比较适合外部排序,数据量太大,不能在内存中完成排序。
时间复杂度:在桶的个数m非常接近数组大小n的时候,接近O(n),实际是O(nlog(n/m))。
空间复杂度:O(n)
稳定性: 取决于每个桶的排序方式,快排就不稳定,归并就稳定。

计数排序

特点:是桶排序的特殊化。每个桶内放的值是相同的。例如考生排名。
时间复杂度:O(n)。
计数排序的计数方法是需要了解的。(图片来自极客时间在这里插入图片描述

例如有8个考生,成绩分别是A[8]={2,5,3,0,2,3,0,3}。计算得到每个考生的排名。
1 用C[6]表示桶数组,遍历一遍得到C[6]={2,0,2,3,0,1}。
2 我们对C数组顺序求和得到C[6]={2,2,4,7,7,8}。C[k]存储小雨等于分数k的考生个数。
3 从后向前一次扫描数组A,k=A[i],idx=C[k]-1,R[idx]=k,C[k]=C[k]-1。这样就知道考试得分是k的考生,排名是idx。
代码

考生排名一般用JDK自带的排序算法即可实现。这样的时间复杂度一般为O(nlogn)。当成绩是一个整数的时候可以使用计数排序在O(n)时间内完成。代码

应用范围:计数排序只能用在数据范围不大的场景中。如果数据范围k比数组长度n大很多,就不适合了。计数排序只能给出非负整数的排序。如果要排序的数据是基于其他类型的,需要调整到非负整数的范围。
稳定性: 稳定,只要整理最后结果时从后开始遍历即可。

基数排序

特点:如果有10万个手机号码,从小到大排序。因为数据范围大,桶排序和计数排序都不适合。可以先按照最后一位排序,接着按照倒数第二位排序…一直到按照第一位排序。使用稳定排序算法。经过11次排序之后,手机号码就有序了。
应用范围:可以分割出单独的每一位,而且位之间有递进关系(a的高位大于b的高位=>a大于b)。每一位的数据范围不能太大,要可以使用线性排序。
时间复杂度:O(k*n)。每一位按照桶排序或者计数排序实现,需要排序k次。
稳定性:稳定,而且必须稳定。

业务思考题

1 为什么插入排序比冒泡排序更受欢迎?
2 用快排思想在O(n)内找第k大元素?
3 现在你有10个接口访问日志文件,每个文件大小约300MB,每个文件里的日志都是按照时间戳从小到大排序的。你希望将这10个较小的日志文件合并为1个日志文件,合并之后的日志仍然按照时间戳从小到大排序。如果处理上述排序任务的机器内存只有1GB,怎么做?
4 如何根据年龄给100万用户数据排序?
5 假设我们现在需要对D,a,F,B,c,A,z这个字符串进行排序,要求将其中所有小写字母都排在大写字母前面,但小写字母内部和大写字母内部不要求排序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值