一.直接插入排序
1.基本思想
把待排序的记录按照其码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列。
2.实现
(1)实现单次交换的思路
给定数组{1,3,5,2},想将这个数组变为升序排列
这里将5的位置记为end,2为end+1并将其记录在变量tmp里
int end;
int tmp = a[end + 1];
while (end >= 0)
{
if (tmp < a[end]) //如果tmp的值小于a[end]就用大的数值覆盖在a[end+1]
{
a[end + 1] = a[end];
--end; //end往前继续比较
}
else
{
break; //不用交换则跳出循环
}
}
a[end + 1] = tmp;
循环条件 end>=0 是为了处理end超出数组的情况,比如给定数组{1,3,5,0},在循环时会出现如图情况
此时end=-1,表示0已经处在头部,直接结束循环,将tmp赋给a[end+1]。
(2)实现多次
实现多次和实现单词思想大致相同,区别在于需要用另一层循环和改变end的初始位置
代码
void InsertSort(int* a, int n)
{
for (int i = 0; i < n - 1; i++)
{
int end = i; //控制end从头部开始
int tmp = a[end + 1];
while (end >= 0)
{
if (tmp < a[end])
{
a[end + 1] = a[end];
--end;
}
else
{
break;
}
}
a[end + 1] = tmp;
}
}
让end从数组中下标位置为0的地方开始,向后比较
for循环的结束条件
如果我们以i<4(n)作为结束条件,此时end+1会超出数组
--end的作用
以数组{2,3,1,5}为例,在排序过程中会出现{2,1,3,5}的情况,这里2和1是需要换位置的,此时end指向1的位置,end+1指向3的位置,想要交换2和1需要让end往回挪动一次(即--end)。因为while循环有两个出口,第一个是end>=0,第二个就是相邻两数不需要交换。样例中结束条件就是从break结束循环。
这里可以画图帮助理解。
3.总结
直接插入排序难度不大,重点是弄清楚单次循环中的开始位置和结束位置以及弄清楚多次循环和单次循环的联系。
二.希尔排序
1.基本思想
希尔排序是插入排序的一种。也称缩小增量排序,可以看作是直接插入排序更高效的改进版。将一组数按某个间距分成若干组,分别排序(预排序)再进行插入排序。
2.实现
(1)思路
首先我们给定一个数gap作为分组,gap为几就会把数组分成几组。如图gap=3,数组{3,4,2,7,5,6,9,8,1}被分成3组。
我们要做的就是分别对这三组进行排序,先以一组为例,再过渡到多组
(2)单次
以红色为例,改写上文中直接插入排序的代码就能解决
int gap = 3;
int end;
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;
和直接插入排序的代码比较不难发现,直接插入排序就相当于gap=1的希尔排序。
(3)多次
void ShellSort(int* a, int n)
{
int gap = 3;
for (int j = 0; j < gap; j++)
{
for (int i = j; i < n - gap; i += gap)
{
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;
}
}
//插入排序...
}
第一个for循环控制进行几次,因为分成了三组,所有控制三次大循环就行;第二个for循环控制一组数交换。
这里我们探讨一下第二个循环的结束条件i<n-gap。
如果以i<n作为结束条件,在最后一次循环时,a[end+gap]会指向数组外,总体分析过程和直接插入排序类似。
3.优化
void ShellSort(int* a, int n)
{
int gap = 3;
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;
}
}
这样我们就可以将三层循环优化成两层循环,我们还可以通过改变gap的大小来优化,具体留到下文。
4.关于gap
gap的取值会改变代码效率。
gap的取值越大,排序越快,但整体越趋近无序;gap越小,结果更趋近有序。
5.总结
希尔排序作为一种经典的排序算法,它更像是直接插入排序的更高级,在处理庞大数据时颇有成效。当我们理解了直接插入排序再来学习希尔排序简直如虎添翼。