插入排序 Insertion Sort
一种简单直观的基础排序算法,如同整理手中的扑克牌。
基本思想
插入排序的工作方式是:构建最终排序好的序列,一次一个元素。它迭代地从未排序的部分取出一个元素,并将其插入到已排序部分的正确位置。
想象一下你正在玩扑克牌,拿到新牌时,你会从右到左(或从左到右)比较,找到合适的位置插入,使得手中的牌始终保持有序。
一种简单直观的基础排序算法,如同整理手中的扑克牌。
插入排序的工作方式是:构建最终排序好的序列,一次一个元素。它迭代地从未排序的部分取出一个元素,并将其插入到已排序部分的正确位置。
想象一下你正在玩扑克牌,拿到新牌时,你会从右到左(或从左到右)比较,找到合适的位置插入,使得手中的牌始终保持有序。
通过下面的动画,观察插入排序如何逐步将元素插入到已排序的部分。数组元素由柱状图表示,高度代表其值。
点击“下一步”或“自动播放”开始。初始数组已生成。
这是插入排序的核心 C++ 实现。代码简洁地体现了其“查找并插入”的逻辑。
void insertionSort(int a[], int n) {
// 从第二个元素开始 (索引为1)
for (int i = 1; i < n; i++) {
int key = a[i]; // 当前待插入的元素
int j = i - 1; // 指向已排序部分的末尾
// 在已排序部分从后往前找插入位置
// 将比 key 大的元素向后移动
while (j >= 0 && a[j] > key) {
a[j + 1] = a[j]; // 元素右移
j--; // 继续向前比较
}
// 找到合适位置 (a[j] <= key 或 j < 0)
a[j + 1] = key; // 插入 key
}
}
了解算法的效率对于选择合适的排序方法至关重要。插入排序在不同情况下的表现差异较大。
场景 | 时间复杂度 (比较次数) | 时间复杂度 (移动次数) | 空间复杂度 |
---|---|---|---|
最好情况 (数组已排序) | O(n) | O(1) - 无需移动 | O(1) |
最坏情况 (数组逆序) | O(n²) | O(n²) | O(1) |
平均情况 | O(n²) | O(n²) | O(1) |
稳定性指:如果数组中有两个相等的元素,排序后它们的相对位置保持不变。
插入排序是稳定的。因为在 `while (j >= 0 && a[j] > key)` 条件中,只有当 `a[j]` 严格大于 `key` 时才会移动。如果 `a[j] == key`,循环会停止,`key` 会被插入到 `a[j]` 的后面,保持了相等元素的原始顺序。
插入排序通常与选择排序、冒泡排序一起作为入门级排序算法进行比较。
插入排序像整理扑克牌,不断将新牌插入到手中已排好序的牌里。对于小数组或基本有序的数组,它的表现非常好,甚至优于 O(n log n) 的算法。原地排序且稳定是其重要特性。
选择排序每次从未排序部分找到最小(或最大)的元素,放到已排序部分的末尾。它的比较次数总是 O(n²),但交换次数是 O(n),这在元素交换成本很高时有优势。但它通常是不稳定的。
冒泡排序重复地遍历列表,比较相邻元素,如果顺序错误就交换它们。每一轮遍历至少将一个最大(或最小)元素“冒泡”到最终位置。可以优化,若一轮没有发生交换则表示已排序。是稳定的算法。
检验一下你对插入排序的理解程度吧!
实践是检验真理的唯一标准。尝试实现下面的插入排序相关问题。
编写一个 C++ 函数 `insertionSortInt(int arr[], int n)`,接收一个整型数组 `arr` 和其大小 `n`,使用插入排序将其升序排列。你可以在下面的编辑器中编写和测试你的代码(注意:此编辑器仅供练习,无实际编译运行功能)。
修改插入排序算法,编写一个 C++ 函数 `insertionSortFloat(float arr[], int n, long long& comparisonCount)`,使其能够对浮点数数组进行排序,并统计在排序过程中执行的比较次数(即 `a[j] > key` 的判断次数)。比较次数通过引用参数 `comparisonCount` 返回。
你已经系统学习了插入排序的核心知识!
相比选择排序和冒泡排序,插入排序在平均情况和最好情况下的性能通常更好(尤其对近乎有序数据)。但在最坏情况下,三者都是 O(n²)。
"就像把牌一张张插入手牌..."
插入排序是基础,了解它有助于理解更高级的排序算法。接下来可以探索: