1. 排序分类
-
一、比较排序 1. 插入排序 - 直接插入排序 - 希尔排序 2. 选择排序 - 直接选择排序 - 堆排序 3. 交换排序 - 冒泡排序 - 快速排序 4. 归并排序 二、非比较排序 1. 计数排序 2. 基数排序 3. 桶排序
2. 堆排序
是对直接选择排序的改进,不稳定,时间复杂度 O(nlogn),空间复杂度 O(1)。
将待排序记录看作完全二叉树,可以建立大根堆或小根堆,大根堆中每个节点的值都不小于它的子节点值,小根堆中每个节点的值都不大于它的子节点值。
过程:以大根堆为例,在建堆时首先将最后一个节点作为当前节点,如果当前节点存在父节点且值大于父节点,就将当前节点和父节点交换。在移除时首先暂存根节点的值,然后用最后一个节点代替根节点并作为当前节点,如果当前节点存在子节点且值小于子节点,就将其与值较大的子节点进行交换,调整完堆后返回暂存的值。
3. 快速排序
是对冒泡排序的一种改进,不稳定,平均/最好时间复杂度 O(nlogn),元素基本有序时最坏时间复杂度 O(n²),空间复杂度 O(logn)。
过程:首先选择一个基准元素,通过一趟排序将要排序的数据分割成独立的两部分,一部分全部小于等于基准元素,一部分全部大于等于基准元素,再按此方法递归对这两部分数据进行快速排序。
快速排序的一次划分从两头交替搜索,直到 low 和 high 指针重合,一趟时间复杂度 O(n),整个算法的时间复杂度与划分趟数有关。
**优化:**当规模足够小时,例如 end - start < 10
时,采用直接插入排序。
4. 归并排序
归并排序基于归并操作,是一种稳定的排序算法,任何情况时间复杂度都为 O(nlogn),空间复杂度为 O(n)。
**基本原理:**应用分治法将待排序序列分成两部分,然后对两部分分别递归排序,最后进行合并,使用一个辅助空间并设定两个指针分别指向两个有序序列的起始元素,将指针对应的较小元素添加到辅助空间,重复该步骤到某一序列到达末尾,然后将另一序列剩余元素合并到辅助空间末尾。
适用场景:数据量大且对稳定性有要求的情况。
5. 怎么选择排序算法?
数据量规模较小,考虑直接插入或直接选择。当元素分布有序时直接插入将大大减少比较和移动记录的次数,如果不要求稳定性,可以使用直接选择,效率略高于直接插入。
数据量规模中等,选择希尔排序。
数据量规模较大,考虑堆排序(元素分布接近正序或逆序)、快速排序(元素分布随机)和归并排序(稳定性)。
一般不使用冒泡。
6. 复杂度分析
- 直接插入(插入过程类似冒泡):将无序数组分成2组,a 组1个元素,b 组n-1个元素,每次从b组中取出第一个元素插入a组中,插入方式为从头到尾逐个比较(若更大则右移)
- 希尔排序:插入排序的一种,首次以数组长度的一半 len/2 为间隔,将相同间隔的列为一组,每组内进行插入排序,后续每次的排序间隔减半,直至间隔为1后则完成排序。
- 直接选择排序:类似冒泡法,每次逐位比较找出最小的数插在一个新列表的尾部。
- 堆排序:二分查找
- 快速排序:在每个递归段,选择最左元素作为基准key。
1、在左边界小于右边界时,重复以下两步:
- 1)首选从右往左找到第一次比key更小的值(假设位置为right),放入key的位置 left,即
arr[left] = arr[right]
- 2)再从左到右找到第一次比key更大的值,放入right处,即
arr[right] = arr[left]
- 每次的left和right恰好是已交换位置
2、放回key值:
arr[left] = key
3、递归左子段(origin_left,left-1)
和右子段(right+1,origin_right)
- 基数排序:分桶算法的一种从个位往前逐位分桶,合并。
7. 稳定性问题
- 稳定性:排序前后两个相等的数相对位置不变,则算法稳定
- 哪些稳定:归并、基数、冒泡、直接插入