基本思想
每一步将待排序的元素,按照其排序码的大小,插入到前面已经排好序的一组元素的合适位置上去,直到元素全部插完为止(可以想象打牌的场景,每拿到一张牌,就要找到合适的位置,然后把牌插进去)。
具体算法实现
假设现有 n 个待排序的元素,对应的关键字分别为 a1、a2、……..an,因为第一个元素是有序的,所以从第二个元素开始,将 a2 与 a1 进行比较。若 a2 < a1,则将 a2 插入到 a1 之前;否则,说明已经有序,不需要移动 a2。
现在有序的元素变为2个,再从第三个元素 a3 开始,先和 a2 进行比较,若 a3小于a2,再将 a3 和 a1 进行比较,若 a3小于a1,则将 a3 插入到 a1 的前面;否则,说明已经有序,继续插入下一个元素。
但是,这个算法还存在一点bug,如果 a3大于a1 呢?这时我们就会想到搬移元素了,即先将要进行插入的元素附给变量 key ,用 key 和已经排好序的元素依次进行比较;像上面的问题,就可以先让 a2 搬移到 key 的位置,再将 key 插入。
(图画的比较丑,多多包涵啦)按照上述方法,直到最后一个元素插入完成。(整个过程可以想象成打牌,每拿到一张新牌,找到它要放的位置后,肯定要将原有的往后移,才有位置插入)。
代码实现
void InsertSort(int array[], int size)
{
int i = 1;
for (; i < size; ++i)
{
int key = array[i];
int end = i - 1;
while (end >= 0 && key < array[end])
{
//搬移元素
array[end + 1] = array[end];
--end;
}
array[end + 1] = key;
}
}
根据以上算法及代码可以发现一个问题,每次都要一个一个去比较,效率太低,有没有什么方法可以减少比较的次数呢?
还记得二分查找吧,这里,我们就可以使用二分查找的算法思想寻找插入位置,我们将这种插入排序称为折半插入排序。
折半插入排序
void InsertSort_OP(int array[], int size)
{
int i = 1;
for (; i < size; ++i)
{
int key = array[i];
int begin = 0;
int end = i - 1;
int mid = 0;
while (begin <= end)
{
int mid = (begin + end) / 2;
if (array[mid] < key)
{
begin = mid + 1;
}
else
{
end = mid - 1;
}
}
mid = i - 1;
while (mid >= end && key < array[mid])
{
//搬移元素
array[mid + 1] = array[mid];
--mid;
}
array[mid + 1] = key;
}
}
总结
- 稳定度
不论是直接插入排序还是折半插入排序,都是稳定的。
2.时间复杂度
直接插入排序中,最好的情况,即所有元素的关键字都已经有序,此时外层的for循环执行 n-1 次,而内层的 while 循环不执行,时间复杂度为 O(n);在最坏的情况下,内外层循环都执行 n 次,时间复杂度为 O(n^2)。
折半插入排序中,平均时间复杂度为 O(nlog2(n))。
3.空间复杂度
因为直接插入排序和折半插入排序,都没有借助辅助空间,所以空间复杂度都为 O(1)。