直接插入排序
直接插入排序是一种简单的插入排序法,其基本思想是:
把待排序的数据按顺序逐个插入到一个已经排好序的有序序列中,直到所有的数据插入完为止,得到一个新的有序序列。
当我们要插入第i个数据a[i]时,前面的a[0],a[1],…,a[i-1]已经排好序,此时用a[i]与前面的数据比较,找到一个合适的位置把a[i]插入,原来位置上的数据顺序后移。
例如,我们要对下面的一组数据进行插入排序(以升序为例)
第一趟排序:把第一个数据9看成是有序的,将6和9进行比较,9比6大,说明不符合升序,此时要把9向后移,然后把6插入到9原来的位置。
第二趟排序:经过第一趟排序之后,6和9已经有序了,此时要把8插入到合适的位置。先将8与9比较,9比8大,不符合升序,此时9向后移一个位置,再把8和6进行比较,符合升序,将8插入到原来9的位置。
第三趟排序:此时6,8,9已经有序,要把5插入到合适的位置,把5依次与6,8,9比较,6,8,9都比5大,不符合升序,6,8,9依次后移一个位置,把5插入到原来6的位置。
第四趟排序:此时7前面的数据已经有序,要把7插入到合适的位置。先将7与9比较,不符合升序,9后移一个位置;再把7与8比较,不符合升序,8后移一个位置;然后把7和6比较,符合升序,接着把7插入到原来8的位置。
此时数组已全部有序,插入排序结束。
插入排序的代码实现:
void InsertSort(int* a, int n)
{
for (int i = 1; i < n; i++)
{
int tmp = a[i];//tmp保存要插入的数据
int end = i - 1;//end是有序区间的结束位置,[0,end]为有序区间
while (end >= 0)
{
if (a[end] > tmp)
{
a[end + 1] = a[end];//数据后移
end--;
}
else
{
break;
}
}
a[end + 1] = tmp;//插入数据
}
}
希尔排序(缩小增量排序)
希尔排序法又称缩小增量法,希尔排序是对直接插入排序的优化。
希尔排序的基本思想:
每一趟排序都选定一个gap,对间隔为gap的每一组分别进行直接插入排序。每一趟排序完成后,数组都更接近有序。当进行到最后一趟排序时,gap=1,当最后一趟排序完成后,数组已经有序。
当gap > 1时都是预排序,预排序的目的是让数组更接近有序,当gap = 1时,就是直接插入排序。
例如,我们现在要用希尔排序把下面的数组排成有序(以升序为例)。
我们先假设gap=3,现在对上面的数据进行分组,间隔为gap的为一组。
分组如上图所示,同颜色指向的为一组。
现在我们先对红色这组进行直接插入排序,代码如下:
void ShellSort(int* a, int n)
{
int gap = 3;
for (int i = 0; i < n - gap; i += gap)
{
int tmp = a[i + gap];
int end = i;
while (end >= 0)
{
if (a[end] > tmp)
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
a[end + gap] = tmp;
}
}
红色这组数据的第一趟插入排序:此时要把5插入到有序序列9之中。根据代码,此时i
和end
是9的下标位置,指向9,tmp
是5。end
等于0,满足while
循环条件,进入循环,a[end]
是9,tmp
是5,if
条件满足,把9移动到5的位置,然后end
减去gap。再判断while
循环条件,此时end
小于0,不满足,while
循环结束,来到代码a[end + gap] = tmp
,这句代码将5放到原来9的位置。
红色这组数据的第二趟插入排序:此时要把8插入到有序序列5,9之中,根据代码,此时i
和end
是9的下标位置,指向9,tmp
是8。end
等于3,满足while
循环条件,进入循环。a[end]
是9,tmp
是8,if
条件满足,把9移动到8的位置,然后end
减去gap。再判断while
循环条件,此时end
等于0,满足while
循环条件,进入循环。此时end
指向5,5小于8,不进入if
语句,break
跳出循环,来到代码a[end + gap] = tmp
,这句代码将8放到原来9的位置。
此时红色这组数据已经排序完成。接下来,我们还要继续对其他组进行直接插入排序。对其他组进行直接插入排序,只要再加一层for
循环就可以了,就像下面这样:
void ShellSort(int* a, int n)
{
int gap = 3;
for (int j = 0; j < gap; j++)
{
for (int i = 0; i < n - gap; i += gap)
{
int tmp = a[i + gap];
int end = i;
while (end >= 0)
{
if (a[end] > tmp)
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
a[end + gap] = tmp;
}
}
}
通过j
来控制对哪一组进行直接插入排序,j = 0
是红色这组,j = 1
绿色这组,j = 2
是蓝色这组。
接下来我们还要解决一个问题,当只有10个左右数据的时候,gap取3或者取其他相近的值是可以的。但当数据量非常大的时候,比如有一亿个数据,gap还取3就不合理了,那么gap如何取值呢?
当gap越大,跳得越快,越不接近有序;当gap越小,跳得越慢,越接近有序。
如何理解上面这句话呢?比方说我们有一个10个数的数组,最大的数在最前面,如果gap取3,最大的数跳3次就可以跳到最后一个位置,而如果gap=1,即是直接插入排序的时候,最大的数要跳10次才能跳到最后。因此,gap取大一点,可以让较大的数更快跳到后面,让较小的数更快跳到前面。但是gap一直取大,就越不接近有序,因此在实际情况中,gap是变化的。gap的取值一般有两种,一种是gap = n/2,一种是gap = n/3 + 1。通过gap由大变小的方式,可以让数据跳得更快,并且慢慢接近有序,最后gap=1,就是直接插入排序,保证数组是有序的。
再加上一层循环,我们的希尔排序就完成了。希尔排序的最终代码:
void ShellSort(int* a, int n)
{
int gap = n;
while (gap >= 1)
{
gap = gap / 2;
for (int j = 0; j < gap; j++)
{
for (int i = j; i < n - gap; i += gap)
{
int tmp = a[i + gap];
int end = i;
while (end >= 0)
{
if (a[end] > tmp)
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
a[end + gap] = tmp;
}
}
}
}
示例数组的完整希尔排序过程: