一、直接插入排序
直接插入排序是一种简单的插入排序法,其基本思想是:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列。
在讲解直接插入排序之前,先让我们脑补一下我们打牌的过程。
先拿一张5在手里,
摸到一张4,比5小,插到5前面,
摸到一张6,比5大,插到5后面,
摸到一张8,比6大,插到6后面,
这就是一个关于直接插入排序的经典例子了,相信这样更容易理解插入排序的算法思想了吧。
二、直接插入排序算法实现
1、算法实现步骤
假设有一组无序序列 R0, R1, … , RN-1。
(1) 我们先将这个序列中下标为 0 的元素视为元素个数为 1 的有序序列。
(2) 然后,我们要依次把 R1, R2, … , RN-1 插入到这个有序序列中。所以,我们需要一个外部循环,从下标 1 扫描到 N-1 。
(3) 接下来描述插入过程。假设这是要将 Ri 插入到前面有序的序列中。由前面所述,我们可知,插入Ri时,前 i-1 个数肯定已经是有序了。所以我们需要将Ri 和R0 ~ Ri-1 进行比较,确定要插入的合适位置。这就需要一个内部循环,我们一般是从后往前比较,即从下标 i-1 开始向 0 进行扫描。
2、代码实现模块
void InsertSort(int*array, int size)
{
for (int i = 1; i < size; i++)
{
int key = array[i];
int end = i - 1;
//找到插入位置
while (end >= 0 && key < array[end])
{
array[end + 1] = array[end];
--end;
}
//插入元素key
array[end + 1] = key;
}
}
算法性能分析:
(1)时间复杂度:当元素越接近有序,直接插入效率越高,时间复杂度在O(1)~O(N^2)之间
(2)空间复杂度:O(1);
(3)稳定性:是一种稳定的排序算法
3、算法改进
我们可以知道,当数据接近反序时,直接插入排序需要进行多次比较和搬移数据才能完成,这时就可以采用二分法实现,减少比较操作,提高插入效率:
代码实现:
void BinaryInsertSort(int *array, int size)
{
for (int i = 0; i < size; i++)
{
int key = array[i];
int left = 0;
int right = i - 1;
int mid = 0;
int end = i - 1;
//二分法找待插入元素的位置
while (left <= right)
{
mid = left + ((right - left) >> 1);
if (key < array[mid])
right = mid - 1;
else
left = mid + 1;
}
//搬移元素
while (end >= left)
{
array[end + 1] = array[end];
--end;
}
//插入元素
array[end+1] = key;
}
}
三、希尔排序
希尔排序(Shell’s Sort)是插入排序的一种,又称“缩小增量排序”,是直接插入排序算法的一种更高效的改进版本;
其排序过程如下所示:
算法实现步骤:
假设增量序列为 h(1),h(2)…..h(k),其中h(1)必须为1,
且h(1)<(2)…
(1)第一趟排序时在增量为h(k)的各个元素上进行比较;
(2)第二趟排序在增量为h(k-1)的各个元素上进行比较;
……….
(3)最后一趟在增量h(1)上进行比较。由此可以看出,每进行一趟排序,增量是一个不断减少的过程,因此称之为缩小增量。
代码实现部分:
void ShellSort(int*array, int size)
{
int gap = size;
while (gap>1)
{
gap = size / 3 + 1;
for (int i = gap; i < size; i++)
{
int key = array[i];
int end = i - gap;
//找带插入元素的位置
while (key < array[end] && end >= 0)
{
array[end + gap] = array[end];
end = end - gap;
}
//插入元素
array[end + gap] = key;
}
}
}
算法性能分析:
(1)时间复杂度:O(n^1.25 ~ 1.6n^1.25)
(2)空间复杂度:O(1)
(3)稳定性:稳定
(4)适用场景:数据量大