https://blog.csdn.net/daijin888888/article/details/66970902算法
1、算法的时间复杂度定义数组
在进行算法分析时,语句总的执行次数T(n)是关于问题规模n的函数,进而分析T(n)随n的变化状况并肯定T(n)的数量级。算法的时间复杂度,也就是算法的时间量度。记做:T(n)=O(f(n))。它表示随问题n的增大,算法执行时间的增加率和f(n)的增加率相同,称做算法的渐进时间复杂度,简称为时间复杂度。其中,f(n)是问题规模n的某个函数。函数
这样用大写O()来体现算法时间复杂度的记法,咱们称之为大0记法。.net
2、推导大O阶方法blog
一、用常数1取代运行时间中的全部加法常数。内存
二、在修改后的运行次数函数中,只保留最高阶项。数学
三、若是最高阶项存在且不是1,则去除与这个项目相乘的常数。获得的结果就是大O阶。class
3、推导示例效率
一、常数阶变量
首先顺序结构的时间复杂度。下面这个算法,是利用高斯定理计算1,2,……n个数的和。
int sum = 0, n = 100; /*执行一次*/
sum = (1 + n) * n / 2; /*执行一次*/
printf("%d",sum); /*执行一次*/
这个算法的运行次数函数是f (n) =3。 根据咱们推导大0阶的方法,第一步就是把常数项3 改成1。在保留最高阶项时发现,它根本没有最高阶项,因此这个算法的时间复杂度为0(1)。
另外,咱们试想一下,若是这个算法当中的语句 sum = (1+n)*n/2; 有10 句,则与示例给出的代码就是3次和12次的差别。这种与问题的大小无关(n的多少),执行时间恒定的算法,咱们称之为具备O(1)的时间复杂度,又叫常数阶。对于分支结构而言,不管是真,仍是假,执行的次数都是恒定的,不会随着n 的变大而发生变化,因此单纯的分支结构(不包含在循环结构中),其时间复杂度也是0(1)。
二、线性阶
线性阶的循环结构会复杂不少。要肯定某个算法的阶次,咱们经常须要肯定某个特定语句或某个语句集运行的次数。所以,咱们要分析算法的复杂度,关键就是要分析循环结构的运行状况。
下面这段代码,它的循环的时间复杂度为O(n), 由于循环体中的代码需要执行n次。
int i;
for(i = 0; i < n; i++){
/*时间复杂度为O(1)的程序步骤序列*/
}
三、对数阶
以下代码:
int count = 1;
while (count < n){
count = count * 2;
/*时间复杂度为O(1)的程序步骤序列*/
}
因为每次count乘以2以后,就距离n更近了一分。 也就是说,有多少个2相乘后大于n,则会退出循环。 由2^x=n 获得x=logn。 因此这个循环的时间复杂度为O(logn)。
四、平方阶
下面例子是一个循环嵌套,它的内循环刚才咱们已经分析过,时间复杂度为O(n)。
int i, j;
for(i = 0; i < n; i++){
for(j = 0; j < n; j++){
/*时间复杂度为O(1)的程序步骤序列*/
}
}
而对于外层的循环,不过是内部这个时间复杂度为O(n)的语句,再循环n次。 因此这段代码的时间复杂度为O(n^2)。
若是外循环的循环次数改成了m,时间复杂度就变为O(mXn)。
因此咱们能够总结得出,循环的时间复杂度等于循环体的复杂度乘以该循环运行的次数。
那么下面这个循环嵌套,它的时间复杂度是多少呢?
int i, j;
for(i = 0; i < n; i++){
for(j = i; j < n; j++){ /*注意j = i而不是0*/
/*时间复杂度为O(1)的程序步骤序列*/
}
}
因为当i=0时,内循环执行了n次,当i = 1时,执行了n-1次,……当i=n-1时,执行了1次。因此总的执行次数为:
用咱们推导大O阶的方法,第一条,没有加法常数不予考虑;第二条,只保留最高阶项,所以保留时(n^2)/2; 第三条,去除这个项相乘的常数,也就是去除1/2,最终这段代码的时间复杂度为O(n2)。
从这个例子,咱们也能够获得一个经验,其实理解大0推导不算难,难的是对数列的一些相关运算,这更多的是考察你的数学知识和能力。
五、立方阶
下面例子是一个三重循环嵌套。
int i, j;
for(i = 1; i < n; i++)
for(j = 1; j < n; j++)
for(j = 1; j < n; j++){
/*时间复杂度为O(1)的程序步骤序列*/
}
这里循环了(1^2+2^2+3^2+……+n^2) = n(n+1)(2n+1)/6次,按照上述大O阶推导方法,时间复杂度为O(n^3)。
4、常见的时间复杂度
常见的时问复杂度如表所示。
经常使用的时间复杂度所耗费的时间从小到大依次是:
咱们前面已经谈到了。O(1)常数阶、O(logn)对数阶、O(n)线性阶、 O(n^2)平方阶等,像O(n^3),过大的n都会使得结果变得不现实。一样指数阶O(2^n)和阶乘阶O(n!)等除非是很小的n值,不然哪怕n 只是100,都是噩梦般的运行时间。因此这种不切实际的算法时间复杂度,通常咱们都不去讨论。
5、最坏状况与平均状况
咱们查找一个有n 个随机数字数组中的某个数字,最好的状况是第一个数字就是,那么算法的时间复杂度为O(1),但也有可能这个数字就在最后一个位置上待着,那么算法的时间复杂度就是O(n),这是最坏的一种状况了。
最坏状况运行时间是一种保证,那就是运行时间将不会再坏了。 在应用中,这是一种最重要的需求, 一般, 除非特别指定, 咱们提到的运行时间都是最坏状况的运行时间。
而平均运行时间也就是从几率的角度看, 这个数字在每个位置的可能性是相同的,因此平均的查找时间为n/2次后发现这个目标元素。平均运行时间是全部状况中最有意义的,由于它是指望的运行时间。也就是说,咱们运行一段程序代码时,是但愿看到平均运行时间的。可现实中,平均运行时间很难经过分析获得,通常都是经过运行必定数量的实验数据后估算出来的。通常在没有特殊说明的状况下,都是指最坏时间复杂度。
6、算法空间复杂度
咱们在写代码时,彻底能够用空间来换取时间,好比说,要判断某某年是否是闰年,你可能会花一点心思写了一个算法,并且因为是一个算法,也就意味着,每次给一个年份,都是要经过计算获得是不是闰年的结果。 还有另外一个办法就是,事先创建一个有2050个元素的数组(年数略比现实多一点),而后把全部的年份按下标的数字对应,若是是闰年,此数组项的值就是1,若是不是值为0。这样,所谓的判断某一年是不是闰年,就变成了查找这个数组的某一项的值是多少的问题。此时,咱们的运算是最小化了,可是硬盘上或者内存中须要存储这2050个0和1。这是经过一笔空间上的开销来换取计算时间的小技巧。到底哪个好,其实要看你用在什么地方。
算法的空间复杂度经过计算算法所需的存储空间实现,算法空间复杂度的计算公式记做:S(n)= O(f(n)),其中,n为问题的规模,f(n)为语句关于n所占存储空间的函数。
通常状况下,一个程序在机器上执行时,除了须要存储程序自己的指令、常数、变量和输入数据外,还须要存储对数据操做的存储单元,若输入数据所占空间只取决于问题自己,和算法无关,这样只须要分析该算法在实现时所需的辅助单元便可。若算法执行时所需的辅助空间相对于输入数据量而言是个常数,则称此算法为原地工做,空间复杂度为0(1)。
一般, 咱们都使用"时间复杂度"来指运行时间的需求,使用"空间复杂度"指空间需求。当不用限定词地使用"复杂度'时,一般都是指时间复杂度。
7、一些计算的规则
一、加法规则
T(n,m) = T1(n) + T2(m) = O(max{f(n), g(m)})
二、乘法规则
T(n,m) = T1(n) * T2(m) = O(max{f(n)*g(m)})
三、一个经验
复杂度与时间效率的关系:
c(常数) < logn < n < n*logn < n^2 < n^3 < 2^n < 3^n < n!
l------------------------------l--------------------------l--------------l
较好 通常 较差
8、经常使用算法的时间复杂度和空间复杂度