数据机构与算法学习(八)- 排序
一、如何分析一个“排序算法”
排序算法的执行效率
1. 最好情况、最坏情况、平均情况时间复杂度
第一,有些排序算法会区分,为了好对比做一下区分。第二,对于要排序的数据,有的接近有序有的接近无序。有序度不同的数据对于排序的执行时间肯定是有影响的,我们要知道在不同的数据下的性能表现。
2. 时间复杂度的系数、常数、低阶
时间复杂度是一个增长趋势,它在表示的时候会忽略系数、常数、低阶。但是在实际的开发中需要考虑。
3.比较次数和交换次数
基于比较的排序算法, 在执行的过程中,会涉及两种操作,一种比较元素大小,另一种交换或移动元素。在分析 执行效率时也要考虑。
排序算法的内存消耗
算法的内存消耗可以通过空间复杂度来衡量,排序算法也不例外。针对排序算法有一个原地排序的概念。特指空间复杂度为O(1)的排序算法
排序算法的稳定性
如果待排序的序列中存在值相等的元素,经过排序后相等的元素的先后顺序不变。比如一组数据括号内为原始序列2(1),9(2),3(3),4(4),8(5),3(6), 排序后为2(1),3(3),3(6),4(4),8(5),9(2),那么这种排序算法叫做稳定排序算法,否则叫做不稳定排序算法
二、排序算法
冒泡排序、插入排序、选择排序、归并排序、快速排序这几种常用的排序算法。已经有单独的文章介绍原理和代码(从百度百科摘录的),这里就不在说明了。将各种算法的分析结果列在这里。
原地排序 | 稳定排序 | 最好 | 最坏 | 平均 | |
冒泡排序 | √ | √ | O(n) | O(n2) | O(n2) |
插入排序 | √ | √ | O(n) | O(n2) | O(n2) |
选择排序 | √ | × | O(n2) | O(n2) | O(n2) |
归并排序 | × | √ | O(nlogn) | ||
快速排序 | √ | × | O(nlogn) |
桶排序
核心思想是将要排序的数据分到几个有序的桶里,每个桶里的数据再单独进行排序。桶内排序完成之后,再把每个桶里的数据按照顺序依次取出,组成的序列就是有序的了。
时间复杂度分析:如果要排序的数据有n个,我们要把它们均匀地划分到m个桶内,每个桶里就有k=n/m个元素。每个桶内使用快速排序,时间复杂度就是O(k*logk).m个桶排序的时间复杂度就是o(m*k*logk),因为k=n/m,所以整个桶排序的时间复杂度就是O(n*log(n/m)).当桶的个数m接近数据个数n的时,log(n/m)就是一个非常小的常量,这个时候桶排序的时间复杂度接近O(n).
对数据要求:首先排序的数据需要很容易就能划分成M个桶,并且桶与桶之间有着天然的大小顺序。这样每个桶内的数据排完序后,桶与桶之间的数据不需要在进行排序。其次,数据在桶之间的分布必须是均匀的。极端情况下,数据都被划分到一个桶里,那就退化为O(nlogn)的排序算法了。
应用场景:比较适合用在外部排序中。就是数据存储在外部磁盘中数据量比较大,内存有限,无法将数据全部加载到内存中。
计数排序
计数排序其实是桶排序的一种特殊情况。当要排序n个数据,所处的范围并不大的时候,比如最大值是k,我们就可以吧数据划分成K个桶。每个桶内数值相等,省掉了桶内排序的时间。
计数排序只能用在数据范围不大的场景中,如果数据范围K比要排序的数据n大的多,就不适合用计数排序了。而且,计数排序只能给非负整数排序,如果要排序的数据是其他类型,要将其在不改变相对大小的情况下,转化为非负整数。
基数排序
基数排序对要排序的数据是有要求的,需要可以分割出独立的“位”来比较,而且位之间有递进关系,如果a数据的高位比b数据大,那剩下的低位就不用比较了。除此之外,每一位的数据范围不能太大,要可以用线性排序算法来排序,否则,基数排序的时间复杂度就无法做到o(n)了。
三、排序算法优化
如何选择合适的排序算法
线性排序算法适用场景比较特殊。所以如果要写一个通用的排序算法,不能选择线性排序算法。对于小规模数据进行排序,可以选择时间复杂度是O(n2)的算法;如果对于大规模的数据进行排序,O(nlogn)的算法更加高效。所以一般首选O(nlogn)的排序算法来实现排序函数。
O(nlogn)的算法前面介绍过快速排序和归并排序还有堆排序。因为归并排序不是原地排序,那么只有快速排序比较适合实现排序函数。但是快速排序在最坏的情况下时间复杂度是O(n2),如何解决复杂度恶化的问题呢?
如何优化快速排序?
主要原因还是因为我们分区点选的不够合理。最理想的分区点:被分区点分开的两个分区中,数据的数量差不多。不能粗暴的选择第一个或最后一个。
常用的分区算法:
1. 三数取中法:从收尾中间分别取出一个数,然后对比大小,取中间值作为分区点。如果数据较大,可能要五数取中,十数取中。
2. 随机法:每次从要排序的区间中,随机选择一个元素作为分区点。
举例分析排序函数
用qsort()函数举例说明
qsort()会优先使用哦归并排序来排序输入数据,要排序的数据量比较大的时候,qsort()会改为用快速排序算法来排序。qsort()选择分区点的方法就是“三数取中法”。在元素小于等于4的时候qsort()就退化为插入排序。