一 插入排序
插入排序就像是我们打牌时整理我们的牌面一样,第一张视为有序,然后不断拿后面的牌插入到排好的牌中,这样牌的有效区域会不断增加,待排序的牌会越来越少,我们也就完成了牌的插入排序。
//插入排序
void ShellSort(int* a, int n)
{
for (int i = 0; i < n - 1; i++)
{
int end = i;//第一个数字视为已排完序
int tmp = a[end + 1];//下一个数字为待插入数据
while (end >= 0)
{
if (tmp < a[end])
{
a[end + 1] = a[end];//a[end+1]已被保存在tmp中
end -= 1;//插入数字与有序区间中的下一个数字相比
}
else
{
break;//直到a[end]>=tmp
}
}
a[end + 1] = tmp;
}
}
二插入排序时间复杂度分析
还是按算时间复杂度的一般情况,那假设总共有n个元素,插入元素从第二个元素到第n个元素,
最坏情况讨论,比如降序排升序,第二个元素要往前比较插入1次,第三个元素插入两次.....,第n个元素插入n-1次,等差数列的结果我们都知道肯定是个n^2,但是这并不意味着该排序是无用的,在数组接近有序的情况下,比如只有两三个数字无序,插入排序只需要O(n),这种情况下只有冒泡的效率能接近插入排序,冒泡虽然也是O(n),但差不多是个4n,比插入排序稍微慢一点,该特殊场景下归并,堆排序,快排都比不过,所以即使快排,归并,堆排序是O(nlogn),但不意味着其它排序就要退出历史舞台。还有就是在小区间下,插入比堆排,归并,快排要优秀的多,所以插入排序还应用于归并和快排的小区间排序,可以减少递归次数。
三 希尔排序
//希尔排序
void ShellSort(int* a, int n)
{
int gap = n;
while (gap > 1)
{
gap /= 2;//如果模三要加一,因为/3最后结果不一定为一,
那样就不会变成插入排序,会使得排序结果接近有序,但不是完全有序。
先分为两组,每组的个数不是均分的,会出现一组多一个,一组少一个的情况
for(int j=0;j<gap;j++)该层for循环是控制排序的组,j++表示去下一组进行排序
{
for (int i = j; i <n-gap; i+=gap)//该层for循环一次循环是一组内的比较排序
{
int end = i;//第一个数字视为已排完序
int tmp = a[end + gap];//与同组中的下一个数字比较
while (end >= 0)
{
if (tmp < a[end])
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
a[end + gap] = tmp;
}
}
}
}
四 希尔排序代码简化(但有些不便于理解)
下面代码和上面的代码几乎是一样的,但区别在于少了外层的for循环,这层本来是控制到下一组去排序的,先如今简化为一层for循环,内层for循环也就是把i+=gap改为i++,由于待插入数据下标最大为n-1,下标小于n,则有效区间的边界end小于n-gap。
//希尔排序
void ShellSort(int* a, int n)
{
int gap = n;
while (gap > 1)
{
gap /= 2;//模三要加一,因为/3最后结果不一定为一
先分为两组,每组的个数不是均分的,会出现一组多一个,一组少一个的情况
for (int i = 0; i <n-gap; i++)//i++表示去下一组的数字去进行希尔排序
{
int end = i;//第一个数字视为已排完序
int tmp = a[end + gap];//与同组中的下一个数字比较
while (end >= 0)
{
if (tmp < a[end])
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
a[end + gap] = tmp;
}
}
}
希尔排序的复杂度超出本人的数学范围,不好解释,大家就记住是接近n^(1.4)就好了。