希尔排序和直接插入排序都属于插入排序,希尔排序是对直接插入排序的优化,如果想要理解希尔排序首先要理解直接插入排序是如何进行排序的,下面我们先实现直接插入排序。
直接插入排序思想
直接插入排序思想是将待排序元素插入一段有序数列,在生活中这种思想比较常见,也许你没有注意,当你在玩扑克牌抓牌时,每有一张牌拿到手里都要插入到已经有序的牌里,这便是直接插入牌序的思想。从第一张扑克牌开始(一张牌时是有序的),逐个插入到手牌里。
算法描述
1.取出第一个元素,只有一个元素当然被认为是有序的。
2.取出下一个元素,插入到序列中,如果待插入元素小于序列中的被插入元素,该被插入元素移动至下一位置。直到该待插入元素大于被插入的元素或者前面已经没有元素时停止,该待插入元素插入到该位置后,此时完成一个元素的插入。
3.重复步骤二,直至所有元素插入完成。
C语言代码实现直接插入排序
void InsertSort(int* a, int size) {
assert(a);
int end;
int x;
for (int i = 0; i < size-1; i++) {
end = i;
x = a[end + 1];
while (end >= 0) {
if (a[end] > x) {
a[end + 1] = a[end];
end--;
}
else {
break;
}
}
a[end + 1] = x;
}
}
算法分析
我们来分析一下该算法的时间复杂度,最坏情况下当然是每次插入时插入到该序列的第一个位置,一趟排序的时间复杂度为O(n),完成排序的时间复杂度为O(n^2);那么最好的情况下的时间复杂度是多少呢?当待排序序列有序时或接近有序时,我们只需要将待插入元素插入到该序列之后,而不必遍历前面的序列,此时排序的时间复杂度为O(n)。总结:直接插入排序的时间复杂度为O(n^2),但在排序有序或接近有序的序列时时间复杂度会更小。
直接插入排序如何优化
希尔排序是对直接插入排序的优化,那么希尔是怎样对直接插入排序进行优化的呢?前面我们已经总结了直接插入排序的算法分析,知道了直接插入排序在对有序或接近有序的序列进行排序时效率十分高。那么有没有方法现将序列进行处理使得其接近有序呢?这便是希尔排序中预排序的处理。
希尔排序算法思想
将序列以gap为间隔进行分组,所有间隔相同的为一组,分为N/gap组。然后分别对每组进行直接插入排序。
当gap等于3时 ,序列被分成9/3=3组,然后对每组元素进行插入排序,得到的序列为{2,1,3,5,4,6,8,9,7},这样就得到了一个相较原序列更有序的序列。我们会发现当gap==1时每组间隔为1,序列被分为了N组元素(N为待排序序列元素个数),此时就是直接插入排序。如果我们一开始选择gap=length/2,然后缩小gap=gap/2,我们对序列进行多次预排序,最后当gap==1时进行直接插入排序,完成最后的排序。
C语言代码实现希尔排序
void ShellSort(int* a, int size) {
//设置排序间隔
int grap = size ;
while (grap > 1) {
//每次预排序间隔,当garp==1时进行直接插入排序
grap /= 2;
for (int i = 0; i < size - grap; i++) {
int end = i;
int x = a[end + grap];
while (end >= 0) {
if (a[end] > x) {
a[end + grap] = a[end];
end -= grap;
}
else {
break;
}
}
a[end + grap] = x;
}
}
}
算法分析
我们可以看到希尔排序与直接插入排序的代码是十分相似的,希尔排序在直接插入排序的思想上增加了预排序,通过gap/=2控制每次循环的增量gap对序列进行预排(这是希尔的划分,业界一般通过gap=gap/3+1来划分),当gap==1时进行对接近有序序列进行排序。希尔排序时被分为log2n个增量gap,每个增量的排序的时间复杂度大致为O(n)。大致算出希尔排序的时间复杂度为T(n)=O(n*log2n)。