排序算法(三) —— 插入排序

来源:互联网 发布:超越无限 知乎 编辑:程序博客网 时间:2024/06/03 14:53

原创文章,转载请注明出处。

本文是排序算法系列文章的第三篇,主要讲述 “插入排序” 算法。

一、原理:

       插入排序的基本操作是每步将一个待排序的对象,按其关键字大小,插入到前面已经排好序的数据序列的适当位置上,直到对象全部插入为止。当插入第 i(i >= 1)个对象时,前面的 a[0], a[1], …, a[i - 1] 已经排好序。用 a[i] 与 a[i - 1], a[i - 2], … 顺序进行比较,若 a[x] 大于 a[i],则从 a[x] 开始顺序后移,找到插入位置即将 a[i] (inserter)插入。

二、代码及注释:

void insertSort(int n, int *arr){int i, j;int inserter;// 插入点,也即待插入值for (i = 1; i < n; i++)// 只需排 n - 1 趟,因为第 i 趟时,前面的 i - 1 个数均已排好序{inserter = arr[i];// 待插入值for (j = i - 1; j >= 0 && inserter < arr[j]; j--)// 从待插入值的前一个位置开始,从后往前查找,找到大于待插入的数的位置{arr[j + 1] = arr[j];// 从该位置往后顺移,并接着查找直至已排序列的某个位置及之前的位置均不再有大于待插入值的数据}arr[j + 1] = inserter;// 这个位置之后的一个位置(因为跳出循环时j多减了 1 次)即为插入位置}
三、算法性能分析:

       当输入规模为 n = 10000、20000、30000、40000、50000 时,执行程序,可以得到在不同输入规模下,20 组随机样本数据执行插入排序的单组执行时间及平均执行时间为:


       理论上来说,插入排序的时间复杂度为 O(n2)。同时,由上图可以看出,当 n = 10000 时,20 组样本排序的平均执行为 13.2813 ms。以输入规模为 10000 的数据运行时间为基准点,则理论上的平均执行时间为:t = k * n2,(k 为常数),代入 t = 13.2813 ms、n = 10000,可得:k = t / n2 =13.2813 / 100002 。

       所以,在相应输入规模下,插入排序的理论执行时间为:


       根据上表,可以作出插入排序理论效率曲线和实测效率曲线如下:


       如上图表,是插入排序的理论效率曲线和实测效率曲线,位于上方的数据标签是实测效率曲线的标注,位于下方的数据标签是理论效率曲线的标注。由图表我们可以看出,在问题规模固定时,理论消耗时间和实际消耗时间十分相近,二者吻合得很好。

       同样地,我们可以比较直观地看出,对于理论效率曲线和实测效率曲线,都大致满足抛物线 y = k * n2 在某个定义域的一段曲线(其中 k 为系数)。这与我们的理论认知也是相一致的。

       在插入排序过程中,每步将一个待排序的对象,按其关键字大小,插入到前面已经排好序的有序表的适当位置上,直到对象全部插入为止。若初始序列为正序,则每趟只需与前面有序记录序列的最后一个记录比较 1 次,移动 2 次记录,总的关键字的比较次数为 (n - 1),记录移动次数为 2(n - 1);若初始序列为逆序,则第 i 趟时第 i 个记录必须与前面 i 个记录都做关键字比较,并且每做 1 次比较就要做 1 次数据移动,那么比较操作次数为 n(n - 1) / 2,记录移动操作次数为 (n + 4)(n - 1) / 2;若待排序记录是随机的,即待排序序列中的记录可能出现的各种排列的概率相同,则我们可以取上述最小值和最大值的平均值作为插入排序所需关键字比较和移动操作次数,约为 n/ 4。因此,总的时间复杂度为 O(n2),这也是理论效率曲线所反映出来的。

       而实测效率曲线与理论效率曲线吻合得很好,随着输入规模的增大,实际消耗时间与理论消耗时间基本相同。但也可以看到一点趋势,在输入规模越来越大时,移动操作也会像冒泡排序一样对最终时间消耗造成影响,但不至于像冒泡排序一样影响那么大。就实验结果来看,理论效率曲线与实测效率曲线的一点细微差异,可能是少量移动操作引起的。




原创粉丝点击