文章目录
- 一、排序的概念
- 二、直接插入排序
- 2.1 基本思想
- 2.2 适用说明
- 2.3 过程图示
- 2.4 代码实现
- 2.5 直接插入排序特性总结
- 三、希尔排序(缩小增量排序)
- 3.1 算法步骤
- 3.2 代码实现
- 3.3 希尔排序的特性总结
一、排序的概念
- 排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
- 稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,
r[i]=r[j]
,且r[i]
在r[j]
之前,而在排序后的序列中,r[i]
仍在r[j]
之前,则称这种排序算法是稳定的;否则称为不稳定的。 - 内部排序:数据元素全部放在内存中的排序。
- 外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。
二、直接插入排序
2.1 基本思想
插入排序,一般也被称为直接插入排序。
对于少量元素的排序,它是一个有效的算法。
直接插入排序是一种简单的插入排序法,其基本思想是:
把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。
实际中我们玩扑克牌时,就用了插入排序的思想
2.2 适用说明
插入排序的平均时间复杂度是 O(n^2)
,空间复杂度为常数阶 O(1)
,具体时间复杂度和数组的有序性是有关联的。
插入排序中,当待排序数组是有序时,是最优的情况,只需当前数跟前一个数比较一下就可以了,这时一共需要比较 N-1
次,时间复杂度为 O(N)
。最坏的情况是待排序数组是逆序的,此时需要比较次数最多,最坏的情况是 O(n^2)
。
2.3 过程图示
假设前面 n-1
(其中 n>=2
)个数已经是排好顺序的,现将第 n
个数插到前面已经排好的序列中,然后找到合适自己的位置,使得插入第 n
个数的这个序列也是排好顺序的。
按照此法对所有元素进行插入,直到整个序列排为有序的过程,称为插入排序。
从小到大的插入排序整个过程如图示:
第一轮:从第二位置的 6 开始比较,比前面 7 小,交换位置。
第二轮:第三位置的 9 比前一位置的 7 大,无需交换位置。
第三轮:第四位置的 3 比前一位置的 9 小交换位置,依次往前比较。
第四轮:第五位置的 1 比前一位置的 9 小,交换位置,再依次往前比较。
…
就这样依次比较到最后一个元素。
最终效果:
2.4 代码实现
// 直接插入排序
// 时间复杂度:O(N^2) 逆序
// 最好的情况:O(N) 顺序有序
void InsertSort(int* a, int n)
{
// [0, end] end+1
for (int i = 0; i < n - 1; i++)
{
int end = i;
int temp = a[end + 1];
while (end >= 0)
{
if (temp < a[end])
{
a[end + 1] = a[end];
--end;
}
else
{
break;
}
}
a[end + 1] = temp;
}
}
void PrintArray(int* a, int n)
{
for (int i = 0; i < n; i++)
{
printf("%d ", a[i]);
}
printf("\n");
}
void TestInsertSort()
{
int a[] = { 3, 2, 6, 8, 4, 6, 0, 9, 5, 7, 1 };
InsertSort(a, sizeof(a) / sizeof(int));
PrintArray(a, sizeof(a) / sizeof(int));
}
int main()
{
TestInsertSort();
return 0;
}
2.5 直接插入排序特性总结
- 元素集合越接近有序,直接插入排序算法的时间效率越高
- 时间复杂度:
O(N^2)
- 空间复杂度:
O(1)
,它是一种稳定的排序算法 - 稳定性:稳定
三、希尔排序(缩小增量排序)
希尔排序,也称缩小增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。
希尔排序是基于插入排序的以下两点性质而提出改进方法的:
- 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率;
- 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位;
希尔排序的基本思想是:首先选择一个整数作为增量(gap),将待排序序列分成若干个子序列,每个子序列的元素之间距离为增量。然后对每个子序列进行插入排序。接下来,逐渐减小增量,重复上述分组和排序的过程。当增量减小到 1
时(就是直接插入排序),所有记录都在同一组内排好序。
3.1 算法步骤
希尔排序的算法步骤可以分为以下两步:
- 预排序:这一步的目的是通过分组和子序列排序,使整个数组接近有序。
- 选择一个初始的增量
gap
。 - 将数组分成若干个子数组,每个子数组的元素之间的距离为
gap
。 - 对每个子数组进行直接插入排序。随着增量的减小,子数组之间的距离会逐渐缩短。
- 选择一个初始的增量
- 直接插入排序:当增量减至
1
时,对整个数组进行直接插入排序,使数组完全有序。
希尔排序的时间复杂度与初始增量gap
的选择和减小策略有关。选择合适的增量序列可以使得希尔排序的性能接近于最佳可能的线性时间复杂度。当前gap的减小策略以gap = gap/3 + 1
较好,其它减小策略如:gap = gap/2
也可行,但唯一一点就是要保证最后gap
的值最终能够减小到 1
。
3.2 代码实现
希尔排序
//void ShellSort(int* a, int n)
//{
// int gap = n;
//
// // gap > 1时是预排序,目的让数组接近有序
// // gap == 1是直接插入排序,目的是让数组有序
// while (gap > 1)
// {
// //gap = gap / 2;
// gap = gap / 3 + 1;
// // 一组一组排
// for (int j = 0; j < gap; j++)
// {
// for (int i = j; i < n - gap; i += gap)
// {
// int end = i;
// int temp = a[end + gap];
// while (end >= 0)
// {
// if (temp < a[end])
// {
// a[end + gap] = a[end];
// end -= gap;
// }
// else
// {
// break;
// }
// }
// a[end + gap] = temp;
// }
// }
// }
//}
// 希尔排序(优化为多组并排,减少一层循环,关键在于i的变化)
// O(N^1.3)
void ShellSort(int* a, int n)
{
int gap = n;
// gap > 1时是预排序,目的让数组接近有序
// gap == 1是直接插入排序,目的是让数组有序
while (gap > 1)
{
// 保证最后gap的结果为1
//gap = gap / 2;
gap = gap / 3 + 1;
// 多组并排
for (int i = 0; i < n - gap; i++)
{
int end = i;
int temp = a[end + gap];
while (end >= 0)
{
if (temp < a[end])
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
a[end + gap] = temp;
}
}
}
void PrintArray(int* a, int n)
{
for (int i = 0; i < n; i++)
{
printf("%d ", a[i]);
}
printf("\n");
}
void TestShellSort()
{
int a[] = { 3, 2, 6, 8, 4, 6, 0, 9, 5, 7, 1 };
ShellSort(a, sizeof(a) / sizeof(int));
PrintArray(a, sizeof(a) / sizeof(int));
}
int main()
{
TestShellSort();
return 0;
}
3.3 希尔排序的特性总结
- 希尔排序是对直接插入排序的优化。
- 当
gap > 1
时都是预排序,目的是让数组更接近于有序。当gap == 1
时,数组已经接近有序的了,这样进行直接插入排序就会很快。这样整体而言,可以达到优化的效果。 - 希尔排序的时间复杂度不好计算,因为
gap
的取值方法很多,导致很难去计算,因此在许多书中给出的希尔排序的时间复杂度都不固定:-
《数据结构(C语言版)》— 严蔚敏
-
《数据结构-用面相对象方法与C++描述》— 殷人昆
-
- 稳定性:不稳定,因为在分组时无法保证相同的值被分在同一组,所以在与排序时有可能发生相同的值对应位置的变化。
【八大排序】系列正在火热跟新中,大家敬请期待!!💖