排序算法很多,其中最经典,最常用的:冒泡排序 ,插入排序,选择排序,归并排序,计数排序,基数排序,桶排序。大致可以分为三类,如图:
带着思考学习:插入排序和冒泡排序的时间复杂度相同,都是,在实际的软件开发 里,为什么我们更倾向于使用插入排序算法而不是冒泡排序算法呢?
一,如何分析一个“排序算法”
学习排序算法,除了学习它的算法原理,代码实现外,还需要分析一个排序算法,要分析一个排序算法,需要从下几个方面入手:
A:: 排序算法的执行效率:对于排序算法执行效率的分析,从如下几个方面衡量:
1,最好情况,最坏情况,平均情况时间复杂度
因为有些排序算法会区分不同情况,同时对需要排序的数据,有的接近有序,有的完全无序。有序度的不同,对算法的执行时间也是不同的。所以需要实在算法在不同数量下的性能表现。
2,时间复杂度的系数,常数,低阶
时间复杂度反应的是数据规模n很大的时候的一种增长趋势,但在同一阶时间复杂度的排序算法性能对比时,就需要将系数,常数,低阶考虑进去。
3,比较次数和交换(或移动)次数
B::排序算法的内存消耗
算法的内存消耗可以通过空间复杂度来衡量,包括排序算法。另外,针对排序算法,还引入了新的概论:原地排序。 原地排序是特指空间复杂度是O(1)的排序算法。
C::排序算法的稳定性
排序算法的稳定性,是指如果要排序的序列中存在值相等的元素,经过排序之后,相等元素之间原有的先后顺序不变。
例如待排序的一组数据:2,9,3,4,8,3,按照大小排序之后是2,3,3,4,8,9。这组数据里有两个3,排序后,两个3的前后顺序没有改变的话,这种排序算法叫做稳定的排序算法,不然叫不稳定的排序算法。
算法的稳定性的影响:在实际软件开发中,我们需要排序的往往不是单纯的整数,而是一组对象,按照对象的某个key来排序。
例如:在电商交易系统中的"订单"排序,订单有两个属性:下单时间和订单金额。若有10万条数据。希望按照金额从小到大对订单排序,对金额相同的订单,按下单时间从早到晚有序。
思路:最先想到的方法是:先对金额进行排序,然后再对每个金额相同的小区间按下单时间进行排序。这事思路不难但麻烦。借助稳定排序算法,可以非常简洁地解决,先对下单时间进行排序,排序完后按照订单金额重新排序。两遍排序之后,得到的就是我们的需求。
二,排序算法
(一)冒泡排序(Bubble Sort)
冒泡排序,只会操作相邻的两个数据。每次冒泡操作都会对相邻两个元素进行比较,看是否满足大小关系要求。如果不满足就让它互换。一次冒泡会至少一个元素移动到它对应的位置,n次冒泡,就完成了n个数据的排序工作。如图一次冒泡操作:
冒泡排序算法还有很多种优化版本,例如:当某次冒泡没有数据交换时,说明已经达到完全有序。
关于评估冒泡算法的三个方面:
1,冒泡排序是原地排序算法?
因只涉及相邻数据交换,所以是常量级的临时空间,所以其空间复杂度是O(1),是原地排序。
2,冒泡排序是稳定的排序算法?
冒泡排序,为了保证其算法稳定性,当两个元素相等时,可以不交换,所以其是稳定的。
3,冒泡排序的时间复杂度是多少?
a,最好情况 是刚好有序,只要一次冒泡,并且没有数据交换发生。其时间复杂度是O(n)。
b,最坏情况 是完全倒序,需要n次冒泡,其时间复杂度是。
c,平均时间复杂度:
引入“有序度”和“逆序度”的两个概论来分析, 有序度:数组中具有有序的元素对的个数。数学表达式:a[i]<=a[j],如果i<j。如图:
对于一个倒序的数组,例如6,5,4,3,2,1,有序度是0,对于一个完全有序的数组,有序度是n(n-1)/2,即满有序度。
逆有序度与有序度刚好相反。可以得出一个公式:逆序度=满有序度-有序度。排序的过程就是一种增加有序度,减少逆有序度的过程,最后达到满有序度。
对于冒泡算法,两个操作原子,比较和交换。每交换一次,有序度就加1,所以,不管算法如何改进,交换次数总是确定的,即逆序度,也就是满有序度-初始有序度。如图:
根据最好情况和最坏情况,我们取中间值作为平均时间复杂度n(n-1)/4,所以平均时间复杂度是。
(二)插入排序(Insertion Sort)
通过动态地往有序集合中添加数据的方法,对一组动态数据进行排序,就是插入排序算法。具体的思想如下:将数组中的数据分为两个区间,已排序区间和未排序区间。初始已排序区间只要一个元素(数组第一个元素)。插入算法核心思想是取未排序区间的元素,在已排序区间找到合适插入位置将其插入,后面的元素需要顺序向后移动一位。
对于不同的插入算法(从头到尾,从尾到头),元素的比较次数不同,但是移动的操作固定等于逆序度。例如如下:
插入算法的也有很大优化空间,例如希尔排序。上述只是最基础的一种。
关于评估插入算法的三个方面:
1,插入排序是原地排序算法?
因只涉及相邻数据交换,不需要额外的存储空间,所以其空间复杂度是O(1),是原地排序。
2,插入排序是稳定的排序算法?
为了保证其算法稳定性,元素相等时,可以将后面出现的元素插入到前面出现元素的后面,所以其是稳定的。
3,插入排序的时间复杂度是多少?
最坏情况:数组已是有序的,时间复杂度是O(n)。
最好情况:数组已是倒序的,时间复杂度是。
平均情况:对于数组插入一个数据的平均时间复杂度是O(n),对于插入排序,每次插入数据操作都相当于插入一个数据,循环执行n次插入操作。所以时间复杂度是。
(三)选择排序(Selection Sort)
选择排序算法的实现思路有点类似插入排序,也分已排序区间和未排序区间。但选择排序每次会从未排序区间中找到最小元素,将其放到已排序区间的末尾。
关于评估选择算法的三个方面:
1,选择排序是原地排序算法?
因只涉及相邻数据交换,不需要额外的存储空间,所以其空间复杂度是O(1),是原地排序。
2,选择排序是稳定的排序算法?
选择排序算法是从未排序区间选择最小值,并与前面的元素交换位置,这破坏了稳定性。例如5,8,5,2,9,这样一组数据,使用选择排序算法来排序的话,第一次找到最小元素2,与第一个5交换位置,那第一个5和中间的5顺序就变了,所以就不稳定 了。正是因此,相对于冒泡排序和插入排序,选择排序就稍微逊色了。
3,选择排序的时间复杂度是多少?
最坏情况,最好情况和平均情况时间复杂度都是。
三,冒泡,插入,选择排序小结
1,冒泡和插入排序都是稳定排序,为何插入排序更受欢迎?
冒泡排序不管怎么优化,元素交换的次数是一个固定值,是原始数据的逆序度。插入排序是同样的,不管怎么优 化,元素移动的次数也等于原始数据的逆序度。 但是,从代码实现上来看,冒泡排序的数据交换要比插入排序的数据移动要复杂,冒泡排序需要3个赋值操作,而插入排序只需要1个。
2,小结
冒泡,插入,选择排序,在实际的开发应用中并不多,插入相对挺有用的,小规模数据排序时,用起来相对高效。大规模数据时时间复杂度相对高些。我们更倾向于用时间复杂度为O(NlogN)的排序算法。