1.直接插入排序
1.思想
把待排序的记录,按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列。在现实生活中,在我们打牌时,每当我们拿了一张牌,然后我们就用会用插入排序将其放到正确的位置。
2.实现思路
现在有一个数组:“4,3,2,1,6,5,9,8,7,0”,要求使用插入排序使数组有序,首先假设这个数组第一个元素有序,然后分别拿后面的元素进行插入排序,如果后面的元素比首元素大,那么就放在首元素的后面,如果后面的元素小于首元素,我们就将首元素向后挪,然后再插入。
假设首元素'4'有序,用‘3’插入如下:
'3'插入完后,那么'3, 4'就有序了,我们再拿’2‘进行插入,’2‘比’4‘小,’4‘往后挪,‘2’比‘3’小,‘3’往后挪,然后插入‘2’
一次类推,直到最后最后一个元素插入新序列。
3.代码
#include <stdio.h>
void InsertSort(int arr[], int n)
{
for (int i = 0; i < n - 1; i++)
{
int end = i;
int tmp = arr[i + 1];
while (end >= 0)
{
if (arr[end] > tmp)
{
arr[end + 1] = arr[end];
end--;
}
else
{
break;
}
}
arr[end + 1] = tmp;
}
}
int main()
{
int arr[] = { 4, 3, 2, 1, 6, 5, 9, 8, 7, 0 };
int n = sizeof(arr) / sizeof(arr[0]);
for (int i = 0; i < n; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
InsertSort(arr, n);
for (int i = 0; i < n; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
4.代码分析
在插入排序中,首先我们假设第一个元素有序,然后定义tmp变量,用于存放有序序列的下一个元素,end变量表示有序数列的末尾位置,然后用tmp和arr[end]比较,如果tmp<arr[end],我们就把arr[end]往后挪一位,也就是挪到了tmp开始的位置,即使挪完后tmp的值被覆盖,但是我们在一开始就把tmp位置的值保存到了tmp。挪完后,end--,因为tmp还要继续和找比自己小的数据,如果arr[end]还是大于tmp,那么我们就继续将arr[end]往后挪一位,end--,直到tmp找到比自己小的元素,然后插入到它的后面,或者没有找到比它小的数据,那么tmp就插入到arr[0]位置。当一个元素插入完成后,再插入下一个元素,直到最后的元素插入到数组中。
2.希尔排序
1.思想
希尔排序,也称为递减增量排序算法,是插入排序的一种高效率的改进版本。它通过将待排序的序列分割成若干子序列,分别进行直接插入排序,从而达到整个序列有序的目的。希尔排序的核心在于间隔序列的选择,间隔序列通常是按某种规则递减至1的。
2.实现思路
假设现在有序列:“9,1,2,5,7,4,8,6,3,5”,我们不直接对整个数组进行插入排序,我们定义变量gap = 5,然后将这个序列分为5组,相同颜色为一组,然后对每组进行插入排序。
当gap = 5,对这样的分组进行插入排序,只要有一个元素要调整位置,它在分组序列每移动一次,都相当于在初始序列移动5次,这样可以使较小的元素很快的移动到数组前面部分,使较大的元素较快的移动到数组靠后的位置。对每组插入排序后,gap变为2,再分组,再排序。
gap=2排好后,gap最终回归到1,对数组进行整体插入排序,此时的数据基本趋于有序,直接插入排序非常擅长趋于有序的序列排序。
gap=1,排完后,序列就有序了。
3.代码
#include <stdio.h>
void ShellSort(int arr[], int n)
{
int gap = n;
while (gap > 1)
{
gap /= 2;
for (int j = 0; j < gap; j++)
{
for (int i = j; i < n - gap; i += gap)
{
int end = i;
int tmp = arr[i + gap];
while (end >= 0)
{
if (arr[end] > tmp)
{
arr[end + gap] = arr[end];
end -= gap;
}
else
{
break;
}
}
arr[end + gap] = tmp;
}
}
}
}
int main()
{
int arr[] = { 9, 1, 2, 5, 7, 4, 8, 6, 3, 5};
int n = sizeof(arr) / sizeof(arr[0]);
for (int i = 0; i < n; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
ShellSort(arr, n);
for (int i = 0; i < n; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
4.代码分析
1.gap变化分析
gap的取值再分组插入排序中,比较关键,gap第一次可以去n/2,对数组元素进行跨度较大的插入排序,然后每一循环,只要gap大于1都将gap整除2,gap最后一次一定是1,当gap=1时,对数组进行插入排序后,数组就有序了。
2.循环嵌套分析
序号①的for循环:这个for循环是对gap分好的每一个组进行插入排序
序号①循环:
- i = j,每次对i初始化为j,循环最开始的i值,表示分好的组中每组的首元素位置。
- i < n-gap是循环的终止条件,i<n-gap说明,i最大为n-gap-1,这是为了防止下面tmp=arr[i+gap]赋值时越界
- i+=gap,是因为分好的组里面每个元素位置相差gap,当对一个元素插入排序后,对第二个元素进行插入排序,就要让i+gap
序号②循环两种理解方式:
- 我们发现,当gap不仅表示,每组中每个元素的间隔,gap还表示,一共会分出多少组,如果gap=3,那么数组会被分为三组。所以一种理解就是:假设gap=3,那么这个循环的意思是,我们要对三个组进行插入排序,这里的j可以控制循环次数。
- 第二种理解是,这里的j也是每组首元素的位置,为i的初始话做准备。