一 、插入排序类
直接插入排序
示例代码:
| |
关键说明
- 算法核心逻辑
- 外层循环 (
for)遍历数组中未排序的元素(从第二个元素开始) - 内层循环将当前元素
temp与已排序部分的元素从后往前比较 - 如果发现更大的元素,则将其向后移动,直到找到插入位置
- 外层循环 (
- 时间复杂度
- 最优情况(已经排序好的数组): O(n)
- 最差/平均情况(完全逆序):O($n^2$)
- 适合小规模数据或基本有序的数据
- 关键特点
- 原地排序:不需要额外的内存空间
- 稳定排序:相同值的元素相对位置不变
- 逐步构建有序序列:每次处理一个元素,逐步扩大有序空间
希尔排序
代码示例:
| |
关键说明
- 算法思想
- 希尔排序是 插入排序的改进版,通过将数组按间隔 (gap) 分组,对每组进行插入排序。
- 逐步缩小间隔(如从 n/2 到 1 ),最终对整个数组进行一次插入排序。
- 时间复杂度
- 最优情况:O(n log n) (取决于间隔序列)
- 最差情况:O($n^2$) (取决于间隔序列)
- 平均性能:优于插入排序,适合中等规模数据。
- 空间复杂度
- O(1) (原地排序,无需额外空间)
- 稳定性
- 不稳定排序 (分组插入可能导致相同值的元素相对位置改变)
二、交互排序类
冒泡排序
示例代码:
| |
关键特性说明
- 算法核心逻辑
- 双循环结构:外层循环控制排序轮次,内层循环执行相邻元素比较和交换
- 元素交换:通过临时变量
temp实现相邻元素的位置交换 - 优化标志:
swapped标记本轮是否发生交换,若无交换则提前终止排序
- 时间复杂度
- 最优情况,输入数组已经完全有序,O(n)
- 最差情况,输入数组完全逆序,O($n^2$)
- 平均情况,O($n^2$) ,适用于小型数据集
- 空间复杂度
- O(1) 原地排序,仅需常数级额外空间
- 稳定性
- 稳定排序:相等元素不会发生位置交换
快速排序
代码实现:
| |
关键步骤说明:
- 基准选择:选择数组中间位置的元素作为基准值(pivot)
- 双指针分区:
- 左指针(
i):从左边开始寻找大于等于基准值的元素。 - 右指针(
j): 从右边开始寻找小于等于基准值的元素。 - 当找到这样的元素时,交换他们并移动指针,直到指针交叉(
i>j).
- 左指针(
- 递归排序: 将数组分为左右两个子数组 (
start到j和i到end),分别递归调用快速排序。
时间复杂度:
- 平均情况: O(n log n)
- 最坏情况: (如数组已有序且基准选择不当) :O($n^2$),但通过合理选择基准(如三数取中法)可优化。
特点:
- **原地排序:**不需要额外空间。
- **不稳定排序:**相等元素的顺序可能被打乱。
- **高效性:**实际应用通常优于其他O(n log n) 算法。
三、选择排序类
简单选择排序
代码示例:
| |
算法特点说明:
工作原理:
- 每次从未排序区间中选择最小值,放到已排序区间的末尾
- 时间复杂度始终为 O($n^2$) (无论数据是否有序)
- 不稳定排序: 可能改变相等元素的原始顺序
核心步骤:
- **外层循环:**控制已排序区间的增长(从0到n-2)
- **内层循环:**遍历未排序区间,寻找最小值的位置
- **交换操作:**将最小值与当前未排序区间的第一个元素交换
性能对比:
特性 选择排序 快速排序 时间复杂度 $O(n^2)$ 平均$O(n log n)$ 空间复杂度 $O(1)$ $O(log n)到O(n)$ 稳定性 不稳定 不稳定 适用场景 小数据量/内存敏感 大规模数据
堆排序
示例代码:
| |
核心步骤解析:
- 堆的构建 (
heapify)- 时间复杂度:O(n)
- 从最后一个非叶子节点开始 (索引
n/2-1) - 自底向上调整每个子树为最大堆
- 排序过程
- 时间复杂度:O(n log n)
- 每次将堆顶最大值交换到数组末尾
- 重新调整剩余元素为最大堆
- 堆大小每次减少1
执行过程示例 (数组【5,3,8,4,2】)
| |
算法特性:
| 特性 | 堆排序 |
|---|---|
| 时间复杂度 | 始终 O(n log n) |
| 空间复杂度 | O(1) |
| 稳定性 | 不稳定 |
| 适用场景 | 大数据量、优先队列 |
关键优化点:
- 非叶子节点计算:
n/2-1保证从最后一个有子节点的元素开始调整 - **递归调整:**当发生节点交换时,需要递归调整被破坏的子堆结构
- **原地排序:**直接原数组上操作,无需额外空间
该实现通过构建最大堆实现升序排序,若需要降序排序只需改为构建最小堆。堆排序虽然时间复杂度优秀,但实际应用中由于频繁的堆调整操作,其常数因子较大,通常在小数据量时性能不如快速排序。
四、归并排序类
归并排序
代码示例:
| |
代码说明
- sort方法:入口方法,检查数组有效性后调用递归排序
- mergeSort方法:
- **递归分解:**将数组分成左右两部分,直到子数组长度为1
- **合并操作:**调用
merge方法合并两个已排序的子数组。
- merge方法:
- **创建临时数组:**存储左右子数组的数据。
- **合并逻辑:**通过双指针遍历临时数组,将顺序将比较小值放回原数组。
- **剩余元素处理:**将未遍历完的子数组剩余元素直接复制到原数组。
特点:
- **时间复杂度:**O(n log n),适用于大规模数据。
- **空间复杂度:**O(n),需要额外空间存储临时数组
- **稳定性:**相等元素的顺序在排序后保持不变。
五、线性时间排序类
计数排序
代码示例:
| |
代码说明:
核心思想
通过统计每个元素的出现次数,直接计算元素在有序数组中的位置
- 时间复杂度:O(n + k) (**
K**为数据范围) ,适合数据范围较小的整数排序。 - 空间复杂度:O(n + k) ,需要额外空间存储计数数组和临时结果。
- **稳定排序:**通过反向填充保证相同元素的元素顺序不变。
- 时间复杂度:O(n + k) (**
关键步骤
- **确定数值范围:**通过最大值和最小值计算数据范围,处理负数。
- **统计频率:**用计数数组记录每个元素出现的次数。
- **累加计数:**将计数数组转换为累加形式,表示元素的最后位置。
- **反向填充:**从原数组末尾开始遍历,确保稳定性。
注意事项
- **适用场景:**适用于整数且数据范围较小的情况(如年龄、分数等)
- **优化点:**若数据范围过大(例如超过 $10^7$),需考虑其他排序算法。
- **负数处理:**通过计算偏移量
num-min,将负数映射到计数数组的合法索引。
桶排序
代码示例:
| |
代码说明
- 核心思想
- 将元素分配到有限数量的桶中
- 对每个桶单独排序(可使用任意排序算法)
- 按桶顺序合并结果
- 关键步骤
- **确定桶数量:**取数组长度的平方根
- 元素分配公式: **
(num-min)*(n-1)/range**保证均匀分布 - 处理负数:通过
num-min将数据偏移到非负区间 - **桶内排序:使用
Collections.sort**保证代码简洁性
- 时间复杂度
- 平均:O(n + k) (k为桶数量)
- 最佳:O(n) (均匀分布时)
- 空间复杂度:O(n+k)
算法特点:
| 特性 | 说明 |
|---|---|
| 适用场景 | 数据均匀分布的浮点数/整数排序 |
| 稳定性 | 取决于桶内使用的排序算法(本例使用稳定排序Collections.sort) |
| 优化方向 | 动态调整桶数量、桶内改用插入排序优化小数据排序性能 |
扩展说明:
- 动态桶数量:可根据数据分布特征使用更智能的分配策略
- 混合排序:当桶内元素少于阈值时,使用插入排序提高性能
- 浮点数支持:通过调整映射公式可支持浮点数排序
基数排序
以下是使用Java实现基数排序的代码示例,包含对负数的处理:
| |
代码说明:
核心思想
- 按位数从低到高(LSD)依次进行稳定排序
- 负数处理:取绝对值排序 -> 反转结果 -> 恢复符号
- 时间复杂度:O(d(n + k))*(
d为最大位数,k为基数10)
关键步骤
- 分离正负数:将负数转换为正数处理
- 基数排序核心:
- 统计当前位频率 -> 计算前缀和 -> 反向填充保证稳定性
- 合并结果:处理后的负数在前,正数在后
执行结果:
| |
算法特点:
| 特性 | 说明 |
|---|---|
| 适用场景 | 整数排序(包括负数),尤其适合位数较少的场景 |
| 稳定性 | 稳定排序(每轮使用计数排序保证顺序) |
| 空间复杂度 | O(n + k)(k为基数10) |
| 优化方向 | 动态调整基数(如按字节处理)、并行化高位排序 |
扩展说明:
- 混合基数:使用256基数(按字节处理)可减少排序轮次
- MSD变体:最高位优先排序适合字符串排序,可提前终止分支
- 并行优化:高位排序完成后,可对低位进行并行排序