算法时间复杂度的定义:在进行算法分析时,语句总的执行次数T(n)是关于问题规模n的函数,进而分析T(n)随n的变化情况并确定T(n)的数量级。
算法的时间复杂度,也就是算法的时间量度,记作:T(n)= O(f(n))。 它表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐近时间复杂度,简称为时间复杂度。其中f(n)是问题规模n的某个函数。 关键需要知道执行次数==时间 这样用大写O()来体现算法时间复杂度的记法,我们称之为大O记法。一般情况下,随着输入规模n的增大,T(n)增长最慢的算法为最优算法。
那么如何分析一个算法的时间复杂度呢?即如何推导大O阶呢?
整理了以下攻略: 1.用常数1取代运行时间中的所有加法常数。 2.在修改后的运行次数函数中,只保留最高阶项。 3.如果最高阶项存在且不是1,则去除与这个项相乘的常数。 4.得到的最后结果就是大O阶。 常数阶: int sum = 0, n = 100; printf(“hello”); printf(“hello”); printf(“hello”); sum = (1+n)*n/2; 时间复杂度为O(1) 第一条就说明了所有加法常数给他个O(1)即可。 线性阶: 一般含有非嵌套循环涉及线性阶,线性阶就是随着问题规模n的扩大,对应计算次数呈直线增长。 int i , n = 100, sum = 0; for( i=0; i < n; i++ ) { sum = sum + i; } 上面这段代码,它的循环的时间复杂度为O(n),因为循环体中的代码需要执行n次。 平方阶: 刚才是单个循环结构,那么嵌套呢? int i, j, n = 100; for( i=0; i < n; i++ ) { for( j=0; j < n; j++ ) { printf(“hello”); } } n等于100,也就是说外层循环每执行一次,内层循环就执行100次,那需要执行100*100次,也就是n的平方。所以这段代码的时间复杂度为O(n^2)。
int i, j, n = 100; for( i=0; i < n; i++ ) { for( j=i; j < n; j++ ) { printf(“I love FishC.comn”); } } 由于当i=0时,内循环执行了n次,当i=1时,内循环则执行n-1次……当i=n-1时,内循环执行1次,所以总的执行次数应该是: n+(n-1)+(n-2)+…+1 = n(n+1)/2 n(n+1)/2 = n^2/2+n/2 用我们推导大O的攻略,第一条忽略,因为没有常数相加。第二条只保留最高项,所以n/2这项去掉。第三条,去除与最高项相乘的常数,最终得O(n^2)。 对数阶: int i = 1, n = 100; while( i < n ) { i = i * 2; } 由于每次i*2之后,就距离n更近一步,假设有x个2相乘后大于或等于n,则会退出循环。 于是由2^x = n得到x = log(2)n,所以这个循环的时间复杂度为O(logn)。
函数的时间复杂度:
int i, j; for(i=0; i < n; i++) { function(i); } void function(int count) { printf(“%d”, count); } 函数体是打印这个参数,这很好理解。function函数的时间复杂度是O(1),所以整体的时间复杂度就是循环的次数O(n)。 假如function是下面这样,又该如何呢: void function(int count) { int j; for(j=count; j < n; j++) { printf(“%d”, j); }}
n+(n-1)+(n-2)+…+1 = n(n+1)/2 n(n+1)/2 = n^2/2+n/2
function内部的循环次数随count的增加(接近n)而减少,所以根据攻略,算法的时间复杂度为O(n^2)。常见的时间复杂度:
常用的时间复杂度所耗费的时间从小到大依次是:O(1) < O(logn) < (n) < O(nlogn) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n) 平均运行时间是期望的运行时间,最坏运行时间是一种保证。 在应用中,这是一种最重要的需求,通常除非特别指定,我们提到的运行时间都是最坏情况的运行时间。