一、直接插入排序
1、 核心思想
插入排序通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入 ,如此重复,直至完成序列排序。
2、 算法分析
- 从序列第一个元素开始,该元素可以认为已经被排序
- 取出下一个元素,设为待插入元素,在已经排序的元素序列中从后向前扫描,如果该元素(已排序)大于待插入元素,将该元素移到下一位置。
- 重复步骤2,直到找到已排序的元素小于或者等于待排序元素的位置,插入元素
- 重复2,3步骤,完成排序。
3、代码实现
void InsertSort(int* a, int n)
{
for (int i = 0; i < n - 1; i++)
{
int end = i;
int tmp = a[end + 1];
while (end >= 0)
{
if (a[end] > tmp)
{
a[end + 1] = a[end];
end--;
}
else break;
}
a[end + 1] = tmp;
}
}
在这段代码中,最精髓也最难理解的便是a[end + 1] = tmp;
当a[end] > tmp
为真时,可以参考冒泡排序的思想把大于tmp
的数往后挪,一直挪到a[end] <= tmp
时或者end < 0
(end = -1)时,无论是哪种情况end + 1
都可以表示一个合法的且适合于tmp
的位置。
二、希尔排序(缩小增量法)
1、 核心思想
希尔排序是插入排序的一种,是对直接插入排序的优化。其特点在于分组排序。
希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数(gap),把待排所有数据分成gap个组,所有距离为差相同的数据分在同一组内,并对每一组内的记录进行排序。然后缩小gap,重复上述分组和排序的工作。当到达gap=1时,所有数据在统一组内排好序。
2、 为什么会出现希尔排序
直接插入排序有缺陷,当数据接近逆序或逆序的时候,排的非常慢,效率很低
1 .提高效率:直接插入排序的时间复杂度在最坏的情况下是O(n^2),当数据量较大时,排序效率会非常低。希尔排序通过先对相隔一定距离的元素进行排序,可以使数组变得部分有序,从而减少后续排序的移动次数,提高排序效率。
2.减少比较和移动次数:通过选择合适的增量序列,希尔排序可以有效地减少元素之间的比较和移动次数,尤其是在处理大量数据时,这种优势更为明显。
3、算法分析
首先gap取5,此时相隔距离为5的元素分到了一组(一共五组,每组两个元素),然后对每一组分别进行插入排序
gap折半为2,此时相隔距离为2的元素被分到了一组(一共两组,每组五个元素),然后对每一组分别进行插入排序
gap再次折半为1,此时所有元素被分到了一组,对它进行插入排序,至此插入排序完成
4、gap值的影响
当gap初始值较大时:
优点:较大的初始 gap 可以使数据更快地变得部分有序,减少后续较小 gap 排序时的移动次数。
缺点:如果 gap 过大,可能会导致子序列中的元素数量较少,排序效果不明
显,甚至可能导致某些元素没有被充分排序。
当gap初始值较小时:
优点:较小的初始 gap 可以更精细地调整元素的位置,使数据更接近最终的有序状态。
缺点:如果 gap 过小,排序过程可能会变得类似于直接插入排序,效率较低。
gap 序列选择是多样化的,不同的 gap 序列对排序性能有显著影响。其中线性递减序列(n / 2, n / 4, n / 8, …1)即每次 gap 减半到最终为1是实现相对简单,容易理解但性能通常不如一些专门设计的序列。
5、代码实现
void ShellSort(int* a, int n)
{
int gap = n;
while (gap > 1)
{
gap = gap / 2;
for (int i = 0; i < n - gap; i++)
{
int end = i;
int tmp = a[end + gap];
while (end >= 0)
{
if (a[end] > tmp)
{
a[end + gap] = a[end];
end -= gap;
}
else break;
}
a[end + gap] = tmp;
}
}
}
在代码实现上,需要注意while (gap > 1)
也可以为gap >= 1
循环次数多一次(n),在数据量较大时效率会有细微的差别。
总结
直接插入排序和希尔排序都是基于插入排序的思想,但它们在处理大规模数据时的效率和适用性有所不同。
- 对于已经部分有序或完全有序的数据,直接插入排序通常表现更好,希尔排序虽然在处理大规模数据时效率较高,但在完全有序的数据上并没有明显的优势。
- 对于小规模数据,直接插入排序通常足够。而对于大规模数据,希尔排序通常更高效。