目录
快速排序
算法步骤
- 在给定的序列中选择一个数A[n]作为主元pivot
- 定义指针i,j(i:指针之前都是小于pivot的元素,j:遍历指针)
- j从序列的头部开始,一旦发现A[j]<=pivot,交换A[j]和A[++i]
- 直到j遍历到n-1结束,交换A[i+1]和A[n]
- 对于1..i和i+2..n递归调用上述过程
复杂度分析
取决于pivot的选取
最坏情况时间复杂度:O(n^2)
平均情况时间复杂度:O(nlogn)
稳定性分析
不稳定
为什么快速排序不稳定?
在算法中,我们将序列分为四个部分:
lower | higher | unvisited | pivot
在unvisited部分遍历到需要放到lower部分的元素时,需要和higher部分的第一个交换,在这个过程中,higher元素的相对位置发生了变化
归并排序
算法步骤
- 将序列分为较平均的两部分,分别对这两个部分继续调用步骤1
- 直到序列的元素无法进行分割
- 对排序好的序列两两进行合并:依次从左至右比较大小,将较小的存入新开的数组中
复杂度分析
最坏情况和平均情况时间复杂度:O(nlogn)
空间复杂度:O(n)
稳定性
稳定
本质上还是相邻元素之间的比较和交换
插入排序
复杂度分析
平均情况时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性
稳定
选择排序
将未排序部分当前最小的交换到未排序部分的最开始
复杂度分析
平均情况时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性
不稳定
为什么选择排序是不稳定的?
未排序部分的最小元素和未排序部分的第一个元素交换时,会产生相对位置变化
冒泡排序
复杂度分析
平均情况时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性
稳定
希尔排序
算法步骤
- 确定步长,相隔步长的元素直接采取插入排序
- 步长缩小一倍,继续进行步骤1
- 直到步长缩小为1,进行一次插入排序后完成排序
算法细节
怎么确定步长?
通常步长选择为元素个数的一半,第一次的插入排序的元素每组只有两个
复杂度分析
平均时间复杂度:O(nlogn)
最差时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性
不稳定
为什么希尔排序是不稳定的?
拥有相同的值的元素在不同的分组中进行排序的过程中,相对位置可能会发生变化
堆排序
算法步骤
- 堆数据结构用数组存储
- 维护最大堆
- 将堆顶的元素和最后一个元素交换,并除去该元素,维护最大堆
- 重复步骤3
算法细节
怎么通过数组存储数据结构堆?
下标为i的元素的子节点是2i+1和2i+2
下标为i的元素的父节点是(i-1)/2向下取整
最大堆的性质
父节点的值大于其子节点的值
怎么维护最大堆?
- 从最后一个父节点开始,比较其值和其子节点的值,并和较大的子节点交换
- 对于交换后的子节点递归的调用维护最大堆(max_heap函数传入参数是父节点的下标)
- 直到所有的父节点处理完
怎么确定最后一个父节点的下标?
通过求最后一个元素的父节点就可以得到
复杂度分析
时间复杂度O(nlogn)
稳定性
不稳定
为什么堆排序是不稳定的?
在堆顶的元素和最后一个元素交换的操作,会将相同的值的元素的相对位置靠后的那一个,变成相对靠前的那一个
计数排序
算法步骤
- 找出数组的最大值max,并定义一个max+1大小的数组
- 用该数组统计每个数字出现的个数
- 将数组进行累加,确定在整个序列中的位置
复杂度分析
时间复杂度:O(n+m) m:给出的最大值
在m=O(n)的情况下认为是O(n)的时间复杂度
空间复杂度:O(n+m)
为什么复杂度优于O(nlogn)?
因为不是基于比较的排序算法
在m=O(n)的情况下,我们一般采用计数排序
稳定性
稳定
基数排序
从最低位到最高位依次采用一个稳定的排序:计数排序
这个算法就是相对于计数排序适用于大数据
复杂度分析
总的时间复杂度为O(d*(n+r))
其中d为位数,n为最多元素个数,r为桶数
空间复杂度是O(m+n)
稳定性
稳定
桶排序
将待排序数据平均切分为几个区间(不同区间之间本身就是有序的),叫做桶,每个桶各自将元素排序好,再将桶内数据合并即可完成排序
复杂度分析
在桶内选择快排,平均时间复杂度为O(n+m)
最差情况O(n^2)
稳定性
稳定
总结计数排序、基数排序、桶排序
-
基数排序:根据键值的每位数字来分配桶
-
计数排序:每个桶只存储单一键值
-
桶排序:每个桶存储一定范围的数据