从插入排序一窥时间复杂度的计算方法

为什么需要分析时间复杂度

通常在运行一段代码之前,我们需要预测其需要的资源。虽然有时我们主要关心像内存、网络带宽或者计算机硬件这类资源,但是通常我们想度量的是计算时间。
接下来我们以插入排序算法为切入点一窥时间复杂度的计算方法。

时间复杂度分析

一般来说,算法需要的时间于输入的规模同步增长,所以通常把一个程序的运行时间描述成其输入规模的函数。为此,我们必须先给出术语运行时间输入规模

输入规模通常依赖于研究的问题。比如,对于排序问题来说,最自然的量度是需要排序元素的数量。又比如对于最短路算法而言,其输入是一个图,则输入规模可以用该图中的顶点数及边数来描述。

一个算法在特定输入上的运行时间是指执行的基本操作数或步数。首先我们假设执行一行代码需要常量时间。常量时间是指:无论输入规模如何变化,执行这一行代码的时间都不受输入规模的影响。我们记第 i 行代码的执行时间为 C i C_i Ci

在用插入排序举例之前,我们先看下该算法的基本思想:每步将一个待排序的元素,按其值的大小插入前面已经排序的序列中适当位置上,直到全部元素插入完为止。以升序排序为例,具体代码如下:

template<typename Array>
void InsertionSort(Array &data) {                       
    for(size_t i = 1, n = data.size(); i < n; i++) {  // 1           
        auto key = data[i];                           // 2  
        auto j = i-1;                                 // 3
        while(j >= 0 && data[j] > key) {              // 4
            data[j+1] = data[j];                      // 5
            j--;                                      // 6
        }
        data[j+1] = key;                              // 7
    }
}

我们设上述代码的 for 循环中七行代码的执行时间分别为:
C 0 , C 1 , C 2 , C 3 , C 4 , C 5 , C 6 C_0, C_1, C_2, C_3, C_4, C_5, C_6 C0,C1,C2,C3,C4,C5,C6
设枚举到第 i 个元素时,第四行代码的执行次数为 T i T_i Ti
每行代码的执行次数可表示为:
n , n − 1 , n − 1 , ∑ i = 2 n T i , ∑ i = 2 n ( T i − 1 ) , ∑ i = 2 n ( T i − 1 ) , n − 1 n, n-1, n-1, \sum_{i=2}^nT_i, \sum_{i=2}^n(T_i-1), \sum_{i=2}^n(T_i-1),n-1 n,n1,n1,i=2nTi,i=2n(Ti1),i=2n(Ti1),n1

具体对应关系如下:
每行的执行耗时和执行次数
该算法的运行时间是执行每条语句的运行时间之和。为计算在具有 n 个元素的输入上该算法的运行时间S(n),我们将代价和次数列对应元素之积求和,得:
S ( n ) = C 0 n + C 1 ( n − 1 ) + C 2 ( n − 1 ) + C 3 ∑ i = 2 n T i + C 4 ∑ i = 2 n ( T i − 1 ) + C 5 ∑ i = 2 n ( T i − 1 ) + C 6 ( n − 1 ) \begin{aligned} S(n)=&C_0n + C_1(n-1)+C_2(n-1)\\ &+C_3\sum_{i=2}^nT_i\\ &+C_4\sum_{i=2}^n(T_i-1)\\ &+C_5\sum_{i=2}^n(T_i-1)\\ &+C_6(n-1) \end{aligned} S(n)=C0n+C1(n1)+C2(n1)+C3i=2nTi+C4i=2n(Ti1)+C5i=2n(Ti1)+C6(n1)

即使对给定规模的输入,一个算法的运行时间也有可能依赖于给定输入的一些特点。例如,对于插入算法来说,若输入数组已排好序,则出现最佳情况。这时,对每个 i = 1 , 2 , 3 , . . . , n − 1 i = 1,2,3,...,n-1 i=1,2,3,...,n1 T i = 1 Ti = 1 Ti=1 ,该最佳情况的运行时间为
S ( n ) = C 0 n + C 1 ( n − 1 ) + C 2 ( n − 1 ) + C 3 ( n − 1 ) + C 6 ( n − 1 ) \begin{aligned} S(n) =&C_0n\\ &+ C_1(n-1)\\ &+C_2(n-1)\\ &+C_3(n-1)+C_6(n-1)\\ \end{aligned} S(n)=C0n+C1(n1)+C2(n1)+C3(n1)+C6(n1)
整理一下上述式子得:
S ( n ) = ( C 0 + C 1 + C 2 + C 3 + C 6 ) n − ( C 1 + C 2 + C 3 + C 6 ) \begin{aligned} S(n)= &(C_0+C_1+C_2+C_3+C_6)n\\ &-(C_1+C_2+C_3+C_6) \end{aligned} S(n)=(C0+C1+C2+C3+C6)n(C1+C2+C3+C6)
我们可以把该运行时间表示为 a n + b an+b an+b,其中常量 a 和 b 依赖于语句代价 C i Ci Ci。因此,它是n的线性函数

若输入数组已反向排序,即按递减序列排好序,则导致最坏情况。这时,对于 i = 1 , 2 , 3 , . . . n − 1 i=1,2,3,...n-1 i=1,2,3,...n1 T i = i + 1 Ti=i+1 Ti=i+1。将 T i Ti Ti 代入 S ( n ) S(n) S(n) 得:
S ( n ) = ( C 3 2 + C 4 2 + C 5 2 ) n 2 + ( C 0 + C 1 + C 2 + C 3 2 − C 4 2 − C 5 2 − C 6 ) n − ( C 1 + C 2 + C 3 + C 6 ) \begin{aligned} S(n) &=(\frac{C_3}{2}+\frac{C_4}{2}+\frac{C_5}{2})n^2\\ &+(C_0+C_1+C_2+\frac{C_3}{2}-\frac{C_4}{2}-\frac{C_5}{2}-C_6)n\\ &-(C_1+C_2+C_3+C_6) \end{aligned} S(n)=(2C3+2C4+2C5)n2+(C0+C1+C2+2C32C42C5C6)n(C1+C2+C3+C6)
我们可以把最坏情况运行时间表示为 a n 2 + b n + c an^2+bn+c an2+bn+c,其中常量 a , b , c a,b,c a,b,c依赖于语句执行耗时 C i C_i Ci。因此,它是n的二次函数。

最坏情况与平均情况分析

在分析插入排序时,我们同时研究了最坏情况和最佳情况。然而我们往往集中于最坏情况运行时间,即规模为n的所有输入中,算法运行时间最长的情况。原因如下:

  • 一个算法的最坏情况运行时间给出了运行时间的上界。从而可以保证在任何情况下,算法的运行时间绝对不会超过这个上界。
  • 对于大多数算法来说,最佳情况出现的频率极低。
  • "平均情况"往往和最坏情况大致一样差。

增长量级

我们使用某些简化的抽象来使插入排序的分析更加容易。
首先,通过使用常量 C i C_i Ci表示每条语句的执行耗时以忽略每条语句的细节。
其次,我们进一步整合总体耗时的计算公式,使其表示为 a n 2 + b n + c an^2+bn+c an2+bn+c,进一步忽略了每条语句的执行耗时。
现在我们做出一种更简化的抽象:即我们真正感兴趣的运行时间的增长率或增长量级。所以我们只考虑公式中最重要的项,因为当 n 的值很大时,低阶项相对来说并不重要。我们也忽略最重要的项的常系数,因为对大的输入,在确定计算效率时常量因子不如增长率重要。对于插入排序,当我们忽略掉低阶项和最重要的项的常系数时,只剩下最重要的项中的因子 n 2 n^2 n2。我们记插入排序的时间复杂度为 O ( n 2 ) O(n^2) O(n2)

如果一个算法的最坏情况运行时间具有比另一个算法更低的增长量级,那么我们通常认为前者比后者更有效。由于常量因子和低阶项,对于小的输入,运行时间具有较高增长量级的一个算法与运行时间具有较低增长量级的另一个算法相比,其可能需要较少时间。但是当输入足够大时,例如,一个 O ( l o g 2 n ) O(log_2^n) O(log2n)算法在最坏情况下比另一个 O ( n 2 ) O(n^2) O(n2)的算法要运行的更快。

扫码关注,快乐加倍

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 时间复杂度推导的基本方法是使用求和法。假设插入排序的数组长度为n,则插入排序时间复杂度可以用以下公式表示:T(n)=T(n-1)+n,其中T(n-1)表示n-1个元素排序的时间复杂度,n表示将第n个元素插入到前面的数组的时间复杂度。根据求和法,最终的结果是T(n)=O(n^2)。 ### 回答2: 插入排序是一种简单直观的排序算法,它的核心思想是将待排序的元素逐个插入到已经有序的序列中。 在最坏情况下,即待排序序列为逆序时,插入排序时间复杂度可以推导如下: 设待排序序列的长度为n,则需要进行n-1 次插入操作,每次插入操作都需要比较和移动已排序序列中的元素,直到找到合适的位置插入当前元素。 在最坏情况下,每次插入操作都需要将当前元素与已排序序列中的所有元素进行比较和移动,直到找到合适的位置。每次比较都需要遍历已排序序列的元素,移动操作最多需要移动整个已排序序列。 所以,在最坏情况下,每次插入操作的时间复杂度是O(n)。而插入排序需要进行n-1 次插入操作,因此最坏情况下插入排序时间复杂度为O(n^2)。 此外,插入排序的最好情况下的时间复杂度是O(n)。当待排序序列是一个有序序列时,每次插入操作都只需要比较一次即可找到合适的位置,不需要进行元素的移动。 综上所述,插入排序时间复杂度在最好和最坏情况下分别为O(n)和O(n^2)。但是,由于插入排序具有部分有序的特点,因此在大多数实际情况下,插入排序的效率较高,平均时间复杂度也接近于O(n)。 ### 回答3: 插入排序是一种简单直观的排序算法,其时间复杂度可通过推导分析得出。 插入排序的原理是将一个元素插入到已排序好的部分有序序列中。具体步骤如下: 1. 从第二个元素开始,将其与已排序好的序列逐个比较,找到合适的位置插入。 2. 再继续取下一个未排序的元素,重复步骤1,直至所有元素都插入到有序序列中。 假设待排序的序列长度为n,最坏情况下,所有元素都需比较并移动位置,即每个元素都需要从末尾依次与已有序列中的元素进行比较。则第i个元素需要比较i-1次,因此整个插入排序算法需要进行的比较次数为: 1 + 2 + 3 + ... + (n-1) = (n-1)n/2 = O(n^2) 此外,我们还需计算移动元素的次数。在最坏情况下,每个元素都需要从末尾依次移动到其最终的位置,因此元素移动的次数与比较次数相同。 综上,插入排序时间复杂度为O(n^2)。当n较小时,插入排序的效率较高,但当n较大时,时间复杂度的平方项会导致算法效率下降,不适合处理大规模数据的排序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值