目录
一、插入排序
1.思想
思想:将数组分为已排序区间和未排序区间两部分,初始时,已排序区间为空,从数组的第二个元素开始,将其与已排序区间的元素进行比较,如果已排序区间的元素大于(假定要排升序)当前元素,则将已排序区间的元素后移一位,直到找到已排序区间中小于或等于当前元素的位置,将当前元素插入到该位置。然后,对数组的下一个元素进行同样的操作,直到整个数组都变为已排序区间。
接下来以一个例子来解释:
以上面这个例子来说,假如要排升序,对于44来说,44>3,就不用交换,然后38比44小,就将44往后挪动,而3比38小,所以就不用交换了,38就在下标为1的位置,然后对于5的话,同理,应该插入到3的后面.....
2.代码实现分析
在清楚插入排序的思想后,我们来尝试实现代码。
我们需要一个变量end来表示已排序好部分的最后一个元素的下标,然后我们就可以找到下标为end+1的数是要插入的数,我们依次与已排序好的数比较,若下标为end+1的数小于下标为end的数就将下标为end的数后移,然后就应当比较前一个数了,怎么找呢?end--就可以了,然后写进循环里,直到不满足end>=0的条件就跳出循环,这样一次插入就大体完成了,一些细枝末节等会儿见代码讲解。
然后再看整体,end的取值应该在什么范围呢?很显然,end肯定从0开始,是0~n(数组大小)吗?如果是这样想的话那就错了,end应该最大是数组中倒数第二个元素,即下标为n-2。我们在前边的一次插入分析过程中也用到了end+1,如果end可以取到n-1那就会越界,这样考虑也可以得出end最多取到n-2。
上代码:
//插入排序
void InsertSort(int* arr, int n)
{
int i = 0;
//注意end的范围:0 <= end <= n-2
for (i = 0; i < n - 1; i++)
{
//先写一层交换,再看整体
int end = i;
int temp = arr[end+1];
//写外层循环里,因为每次插入都是与同一个数据比较
//如果写内层循环每次比较temp都会变,因为end在不断自减
while (end >= 0)
{
//将下标为end+1的数end之前的数比较、交换
//通过end--来控制比较对象,交换时将下标为end的数放在要插入数据的原位置
//所以需要先用临时变量来存储要插入的数据
if (arr[end] > temp)
{
arr[end + 1] = arr[end]; //数据后移
end--;
}
else
{
//arr[end+1]=temp;
//不交换则说明在正确位置(end+1),将end+1处补上temp的值,跳出循环
break;
}
}
//当要插入的数据比end前边的数据都小时,会一直交换至end = -1
arr[end + 1] = temp; //与循环中else部分一致,可合并不写else中的
}
}
3.测试结果:
二、希尔排序
1.思想
希尔排序(Shell Sort)是插入排序的一种更高效的改进版本,也称为递减增量排序算法。其基本思想在于先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行一次直接插入排序。
在希尔排序中,我们取一个小于n(数组大小)的整数gap(步长)作为增量,将待排序元素分成若干个子序列,所有距离为gap的倍数的记录放在同一个组中。然后,对各组内的元素进行直接插入排序。这一趟排序完成之后,每一个组的元素都是有序的。接着,我们减小gap的值,并重复执行上述的分组和排序。重复这样的操作,当gap=1时,整个数列就是有序的。
简单来说,就是先按照gap分组,然后将每一组进行插入排序,这时候还是无序的,但是比原来的顺序要好一点,因为是根据gap来分组的,可以使数据更快的进行交换,最后对整体进行插入排序。我们以草图来进行具体讲解:
可以看到,共有gap组,并且每一组的下标都是按照gap递增的,然后我们再来看看按照组插入后的:
初步排序后,比原来的有序一点,在后边排序时交换的次数会更少,效率高,所以希尔排序其实就是在插入排序基础上的优化。
关于gap:gap越大,数据跳跃的越快,小的更快的换到前边,大的更快的换到后边,但是如果gap过于大,那么会越不接近有序,因为每组排序的数据少;gap越小,数据跳跃的越慢,更接近有序,gap=1时就是插入排序。
2.代码实现
根据上边的思想分析,先要对每一组数据进行插入排序,我们很容易知道,就是将插入排序中每一次插入稍微修改一下即可,在上面的插入排序中,我们是end--,要插入的数据是下标为end+1的数,我们改成gap即可,即end -= gap,end+gap。总共有gap组,所以很容易想到再在外面套一层循环。即下面的写法:
然而,上面两个for循环是可以合并的,使i不断自增并满足条件i<n-gap就可以了,在两个循环中是一组一组插入交换的,而用一个循环后,则是三组交叉交换的,先是红色那组的前两个数(下标为end的数为每组的第一个数),然后是紫色那组,然后是黄色那组,end指向的数变成每组第二个数,依次进行下去(可以自己走一遍循环来观察)。
分析到这里时,大概已经清楚希尔排序了,但是gap到底应该怎么取值呢?答案是每次gap /= 2。这样到最后一次时gap正好为1,也避免了在最后单独写一遍插入排序了。
上代码:
//希尔排序
//先预排序,最后再插入排序
void ShellSort(int* arr, int n)
{
int gap = n;
while (gap > 1)
{
gap /= 2;
for (int i = 0; i < n - gap; i++) //注意i<n-gap,避免越界
{
int end = i;
int temp = arr[end + gap];
while (end >= 0)
{
if (temp < arr[end])
{
arr[end + gap] = arr[end];
end -= gap;
}
else
{
break;
}
}
arr[end + gap] = temp;
}
}
}
3.测试