下图是对常见的几种排序的简单分类:
时间复杂度、空间复杂度以及稳定性
插入排序:
1、直接插入排序:
是将一个数根据它的大小插入到一个已经有序的数组中,从而得到一个个数加一的新的有序数据,该算法适用于少量数据的排序,时间复杂度为O(n^2),是一种稳定的排序方法。
思想(配合示意图食用更佳):
从第二项开始遍历整个数组,当a[i]<a[i-1]时,把a[i]的值保存到tmp;
从a[i]开始,逐个往前遍历,当tmp的值小于a[i-1]时,记下此时的位置为a[j],把a[j]赋给a[i],然后 j-- 继续向前;当tmp的值大于a[j]时,跳出循环;
把之前tmp保存的值赋到最后跳出循环的位置。
示意图:
时间复杂度分析:首先要遍历数组找到tmp,这是一个O(n);找到tmp之后,向前遍历找到插入的位置,也是O(n);所以插入排序的时间复杂度为O(n^2)。
2、希尔排序
希尔排序是进阶版的插入排序,它是将整个数组分为几个小数组,每个小数组先进行排序,再对这些有序的小数组排序,最后原始数组就会成为一个有序的数组。
思想:
- 大数组划分为小数组是希尔排序与插入排序最大的区别,正因为如此,希尔排序的效率才有所提高。
- 划分数组的依据是gap的大小,gap的初始值我们将它设置为数组的大小,后续gap的取值为gap/3+1(大牛们总结的,这样的效率是最高的),当gap的值为1时,就变成了普通的插入排序。
- 分组完成后的排序方法和插入排序是一致的。
选择排序
1、选择排序
选择排序
遍历数组,选择出最大(小)的数据,将这个最大(小)的数据放在整组数据的最右(左)边,循环整个数组,当循环结束时,得到的数组就是有序的
假设数组最左边的数据
i
是最小的,把这个数据定义为min
:
- 遍历整个数组,当遇到小于
min
的数据时,把j
的位置赋给min
所在的位置,并跳出当前循环- 交换
min
和i
位置的值- 再次遍历数组,从
i + 1
开始
下图是第一次循环的具体过程,剩余的循环类似:
选择排序优化
遍历数组,同时选择出最大和最小的数据,将最大的数据放在整组数据的最右边,最小的数据放在整组数据的最左边,循环整个数组,当循环结束时,得到的数组就是有序的,同时选择之后,效率明显有所提升
定义数组的最左端为
left
,最右端为right
,初始时将min
和max
设置为最左端的值,在这个范围内对数组进行排序:
- 从数组第二项开始循环,找到最大值赋给
max
,找到最小值赋给min
- 把最左端的值与
min
位置的值进行交换,最右端的值与max
位置的值进行交换left++,right--
,将数组变小- 循环上述过程
注:每层内循环中,找到的最值都是当前数组中的最值,完成交换后的值的位置就是最终的位置
2、堆排序
交换排序
1、冒泡排序
重复地遍历要排序的序列,依次比较两个相邻的元素,按照想要的顺序(如从大到小、首字母从A到Z)进行交换,当这组序列中不在含有元素,表示排序完成。
基本思路:
- 从头开始遍历数组,当a[j]>a[j+1]时,交换这两个数,并循环执行,此时最后的元素就是这组序列中最大的那个,它的位置不会再发生改变了;
- 重复上述步骤
优化一:
假设我们现在排序{1, 2, 3, 4, 5, 6, 7, 8, 10, 9}这组数据,按照上面的排序方式,第一趟排序后将10和9交换已经有序,接下来的8趟排序就是多余的,什么也没做。所以我们可以在交换的地方加一个标记flag,如果那一趟排序没有交换元素,说明这组数据已经有序,不用再继续下去,可提高一定的效率。
优化二:
优化一仅仅适用于连续有序而整体无序的数据(例如:1, 2,3 ,4 ,7,6,5),但是对于前面大部分是无序而后边小半部分有序的数据(例如:1,2,5,7,4,3,6,8,9,10), 它的排序效率也不是很可观,对于这种类型数据,可以再次进行优化。
在循环的过程中可以记下最后一次交换的位置,后边没有交换,必然是有序的,然后下一次排序截止最后一次交换的位置即可,减少了排序的数据,也会提高效率。
2、快速排序
归并排序
它是建立在归并操作上的一种采用分治法的排序算法,将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
简单来说,就是把要排序的无序序列从中间一分为二,分割后的序列再次进行分割,循环分割直至序列中只有一个元素,此时序列中唯一元素是有序的;然后按照分割的顺序反向进行合并,使每个子序列都有序,最终得到的序列就是有序的。
基本思路:
归并排序实际上是一个先分割再合并的算法,分割采用递归的方法是比较容易得到的,难点在于如何合并,而且要使得合并完的序列有序,私以为这才是归并排序的重点。下面给出了相关图解,并有动图展示。
动图展示:
提问:归并排序和快速排序都是采用的“分而治之”的思想,那么两者有何不同呢?
归并排序的“分”是拿到一组序列之后,直接从中间一分为二,再递归分割;
快速排序的“分”是拿到一组序列之后,先选取一个基准值,以基准值与其余元素比较大小,大的放在基准值的右侧,小的放在左侧,如此递归分割。
这就意味着,归并排序每次分割完之后,两边的元素数量是基本一致的(若序列内的元素的数量为奇数,则一边比另一边多一);而快速排序在每次分割完之后,两边的元素数量大部分情况下都是不相等的;所以等量数据时,归并排序的效率更高一筹。
总结上述几种排序的性能及时间、空间复杂度如下表:
排序方法 | 时间复杂度(平均) | 时间复杂度(最坏) | 时间复杂度(最好) | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
直接插入排序 | O(n2) | O(n2) | O(n) | O(1) | 稳定 |
希尔排序 | O(nlogn) | O(n2) | O(n) | O(1) | 不稳定 |
选择排序 | O(n2) | O(n2) | O(n2) | O(1) | 不稳定 |
堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(1) | 不稳定 |
冒泡排序 | O(n2) | O(n2) | O(n) | O(1) | 稳定 |
快速排序 | O(nlogn) | O(n2) | O(nlogn) | O(logn) | 不稳定 |
归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 稳定 |