冒泡排序的核心逻辑怎么写才不越界
冒泡排序本质是相邻元素两两比较、交换,每轮把最大(或最小)值“浮”到一端。C# 中最易出错的是循环边界——
i和
j的取值范围稍有偏差,就会导致
IndexOutOfRangeException。
关键点:外层控制轮数,最多
n-1轮;内层控制比较范围,第
i轮只需比到
length - 1 - i位置。 数组长度为
n,下标范围是
0到
n-1,
j+1最大不能超过
n-1→ 所以
j最大只能是
n-2每轮结束后,末尾
i个元素已有序,内层循环上限应动态收缩:
j若用
for (j = 0; j ,则 <code>j+1在最后一轮会越界
升序和降序只差一个比较符,但别写反了
升序是“前面 > 后面 就交换”,降序是“前面 arr[j] 却声称要升序,结果排出来是逆序——这不是算法错,是语义理解反了。
建议统一用“目标顺序 + ‘应交换’的条件”来校验:
升序:大的该往后走 →arr[j] > arr[j + 1]时交换 降序:小的该往后走 →
arr[j] 时交换泛型版本中,用
comparer.Compare(arr[j], arr[j + 1]) > 0更安全,避免手动写错符号
用 Span 优化性能时要注意生命周期
在高频或大数据量场景下,直接操作
Span<t></t>可避免数组拷贝和 GC 压力,但必须确保
Span不逃逸出当前作用域。
错误示例:
Span<int> s = array.AsSpan(); return s;</int>—— 返回局部
Span会导致未定义行为。 正确做法:把排序逻辑封装为
void Sort(Span<int> span)</int>,只在栈上操作 对
ReadOnlySpan<t></t>无法排序(不可变),必须用可写的
Span<t></t>若原始数据是
List<t></t>,先调
list.AsSpan(),但注意
List<t></t>内部数组可能被扩容,需确保未发生结构变更
实际项目中为什么几乎不用手写冒泡
冒泡排序时间复杂度恒为 O(n²),即使加了“提前退出”优化(检测某轮无交换就终止),最坏和平均仍是 O(n²)。.NET 自带的
Array.Sort()底层是 introsort(快排 + 堆排 + 插入排序混合),实测百万整数排序比手写冒泡快 300 倍以上。
真正需要手写冒泡的场景极少,典型如:
教学演示算法思想(强调交换过程) 嵌入式或极简运行时环境(无System.Array排序 API) 调试时临时对小数组做可视化排序步骤跟踪
只要不是为了理解过程本身,优先用
Array.Sort(arr)或
arr.OrderBy(x => x).ToArray()——省心,且编译器和 JIT 会做大量优化。
