您的位置:首页 > 其它

时间复杂度分析及其案例

2017-04-08 12:33 162 查看
一、时间复杂度介绍
1、时间复杂度定义
在进行算法分析,语句总得执行次数 T(n) 是关于问题规模 n 的函数,进而分析 T(n) 随 n 的变化情况并确定 T(n) 数量级。算法的时间复杂度,也就是算法的时间量度,记作:T(n) = O(f(n)),它表示随问题规模 n 的增大算法执行时间的增长率和 f(n) 的增长率相同,称作算法的渐近时间复杂度,简称为时间复杂度,其中f(n) 是问题规模 n 的某个函数。
2、求解时间复杂度具体步骤
(1)找出算法中的基本语句
算法中执行次数最多的那条语句就是基本语句,通常是最内层循环的循环体。
(2)计算基本语句的执行次数的数量级
只需计算基本语句执行次数的数量级,这就意味着只要保证基本语句执行次数的函数中的最高次幂正确即可。可以忽略所有低次幂和最高次幂的系数。这样能够简化算法分析,并且使注意力集中在最重要的一点上:增长率。
(3)用大 O 记号表示算法的时间性能
将基本语句执行次数的数量级放入大 O 记号中。
大 O 中的 O 的意思就是”order of”(大约是),它是种概念,就比如 大型车、小型车和中型车,忽略具体大小尺寸,来描述汽车。
3、时间复杂度计算方法
(1)用常数 1 取代运行时间中的所有加法常数。
(2)在修改后的运行次数函数中,只保留最高阶项
(3)如果最高阶存在且不是 1,则去除与这个项相乘的常数。最后,得到的最后结果就是时间复杂度。
简单来说,就是保留求出次数的最高次幂,并且把系数去掉,如 T(n) = 2n^2+n+1 = O(n^2)
4、常见的时间复杂度
(1)常数级复杂度:O(1)
(2)对数级复杂度:O(logN)
(3)线性级复杂度:O(N)
(4)线性对数级复杂度:O(NlogN)
(5)平方级复杂度:O(N^2)


各位可以加下群466572167(资料和视频),一起交流提升
复杂度曲线越平越好,越陡越差,常数级复杂度最为理想。
常用的时间复杂度所耗费的时间从小到大依次是:
O(1) < O(logn) < (n) < O(nlogn) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n)
举例说明:


各位可以加下群466572167(资料和视频),一起交流提升
二、具体说明各种时间复杂度
按照时间复杂度从小到大依次讲解:
讲解之前需要了解一个概念:频度
参看:数据结构频度
在数据结构中,频度是指一个定义变量在它的函数中,并且是它在执行到该段语句为止时,这个定义变量在函数总共执行基本操作的次数。
例如下函数中各行频度n的计算:
for(i=0;i<n;i++) —————————– (1)
{
for(j=0;j<n;j++) ————————- (2)
{
c[i][j]=0; —————————— (3)
for(k=0;k<n;k++) ——————- (4)
{
c[i][j]=c[i][j]+a[i][k]*b[k][j]; ——- (5)
}
}
}
(1) for(i=0;i<n;i++) 频度为: n+1
(2) for(j=0;j<n;j++) 频度为:n*(n+1)
(3) c[i][j]=0 频度为:n*n
(4) for(k=0;k<n;k++) 频度为:n*n*(n+1)
(5) c[i][j]=c[i][j]+a[i][k]*b[k][j] 频度为:n*n*n
解释:
(1)i 变量在第一个 for 循环中,从取 i = 0 开始执行,直到i=n-1时为止,至此,i 执行了n次。加上最后i=n跳出循环的判断,故频度共n+1 次;
(2)与(1)不同,当 i 在 0~(n-1) 范围内,内层循环 [即是(2)的for循环] 频度为 n ; 当 i = n 时,内层循环语句没执行。所以相当此时第(1)中 for 循环执行了n次,第二个for 循环执行了n次,加上最后j=n跳出循环的判断,即,频度共 n * (n+1);
(3)此句语句,是要利用(1)、(2)for循环语句的i ,j 对 c[i][j] 进行赋值,此时,i 得到的赋值只有从 0 到 n , j 得到的赋值也是从0到n ,都是 n次,此时(当 i 达到n-1 .\当 j 达到 n-1.)的 i++ \j++都不会执行。 故,频度共 n*n 次;
(4)同上(1),(2)的理由,单独的(4)的for 循环执行了n+1 次,综上,频度为 n*n*(n+1);
(5)同理(3),对于三个for 循环, i 得到的赋值只有从 0 到 n , j 得到的赋值也是从0到n ,k得到的赋值也是从 0 到 n ,即,频度为n*n*n。
1、常数阶(O(1))
#include <stdio.h>
int main (void)
{
int n = 10;
printf (“n = %d\n”, n);
printf (“n = %d\n”, n);
printf (“n = %d\n”, n);
printf (“n = %d\n”, n);
printf (“n = %d\n”, n);
printf (“n = %d\n”, n);
printf (“n = %d\n”, n);
printf (“n = %d\n”, n);
return 0;
}
分析:
上述代码中,每条语句的频度均为 1,即 T(n) = O(9),但是一般记作 O(1)。因此它的时间复杂度为 O(1)
(2)对数阶(O(logn))
#include <stdio.h>
int main (void)
{
int i = 1, n = 1000; //语句1
while (i < n)
i = i*2; //语句2
return 0;
}
分析:
语句1频度为:1
语句2频度为:f(n),则2f(n)<=n;f(n)<=log2n 取最大值 f(n)= log2n
即T(n) = log2n + 1 = O(log2n ),因此它的时间复杂度为 O(logn)
(3)线性阶(O(n))
#include <stdio.h>
int main (void)
{
int i = 0, sum = 0, n = 10; //语句1
for (i = 0; i < n; i++) //语句2
sum += i; //语句3
printf (“sum = %d\n”, sum); //语句4
return 0;
}
输出结果:
sum = 45
分析:
语句1频度为:1
语句2频度为:n+1
语句3频度为:n
语句4频度为:1
即T(n) = 1 + (n + 1) + n + 1 = 2n + 3 = O(n),因此它的时间复杂度为O(n)
(4)线性对数阶(O(nlongn))
#include <stdio.h>
void quick (int arr[], int left, int right)
{
int p = (left + right) / 2;
int pivot = arr[p];
int i = 0, j = 0;
for(i = left, j = right; i < j;)
{
while (arr[i] < pivot && i < p)
i++;
if (i < p)
{
arr[p] = arr[i];
p = i;
}
while(arr[j] >= pivot && j > p)
j–;
if (j > p)
{
arr[p] = arr[j];
p = j;
}
arr[p] = pivot;
if(p - left > 1)
quick (arr, left, p - 1);
if (right - p > 1)
quick (arr, p + 1, right);
}
}
int main()
{
int arr[9] = {20, 8, 25, 3, 15, 9, 30, 5, 22};
quick (arr, 0, 8);
int i = 0;
for(i = 0; i < 9; i++)
printf (“%d “, arr[i]);
printf (“\n”);
return 0;
}
输出结果:
3 5 8 9 15 20 22 25 30
分析:
上述例子为快速排序,如果每次都是均等的划分即T(n)=T(n/2)+T(n/2)+O(n),即每次分成两段,则分的次数为logn,每一次处理需要n次计算,那么时间复杂度就是nlogn
如果每次的划分都是完全不平衡的即T(n)=T(n-1)+O(n),那么快排的时间复杂度是n^2
所以它是不稳定的,因此我们说 快排的平均时间复杂度是nlogn
(5)平方阶(O(n^2))
#include <stdio.h>
int main (void)
{
int i, j, n = 10; //语句1
for (i = 0;i < n; i++) //语句2
{
for (j = 0; j < n; j++) //语句3
printf (“*”);
printf (“\n”);
}
return 0;
}
分析:
语句1频度为:1
语句2频度为:n + 1
语句3频度为 n * (n + 1)
即T(n) = 1 + (n + 1) + n * (n + 1) = n^2 + 2n + 2 = O(n^2),因此它的时间复杂度为 O(N^2)
(6)立方阶(O(n^3))
#include <stdio.h>

int main (void)

{

int i, j, k, n = 3; //语句1

for (i = 0;i < n; i++) //语句2

{

for (j = 0; j < n; j++) //语句3

{

for (k = 0; k < n; k++) //语句4

printf (“*”);

printf (“\n”);

}

}

return 0;

}

分析:
语句1频度为:1
语句2频度为:n + 1
语句3频度为:n * (n + 1)
语句4频度为:n * n * (n + 1)
即T(n) = 1 + (n + 1) + n * (n + 1) + n * n * (n + 1) = n^3 + 2n^2 + 2n + 2 = O(n^3)
因此它的时间复杂度为 O(n^3)
(7)指数阶(O(2^n))
#include <stdio.h>

int i = 1;

void move (int n, char from, char to)

{

printf (“第%d步:将%d号盘子%c—->%c\n”, i++, n, from, to); //语句1

}

void hanoi (int n, char from, char denpend_on, char to)

{

if (n == 1)

move (1, from, to);

else

{

hanoi (n - 1, from, to, denpend_on);

move (n, from, to);

hanoi (n - 1, denpend_on, from, to);

}

}

int main (void)

{

printf (“请输入盘子的个数:\n”);

int n;

scanf (“%d”, &n);

char x = ‘A’, y = ‘B’, z = ‘C’;

printf (“盘子移动情况如下:\n”);

hanoi (n, x, y, z);

}

分析:
语句1频度为:1
因此,当 if (n == 1) 时, T(n) = 1 = O(1)
否则,T(n) = 2T(n - 1) + 1 = 2^n + 1 = O(2^n) 因此它的时间复杂度为 O(2^n)
相关递推公式就不写了,写了也看不懂。
三、常用数据结构和算法的时间复杂度 各位可以加下群466572167(资料和视频),一起交流提升
1、数据结构部分



2、排序部分


各位可以加下群466572167(资料和视频),一起交流提升
3、平均和最坏时间复杂度
(1)最坏时间复杂度
顾名思义,时间复杂度不能再坏了。
以快排为例,在最坏情况下的时间复杂度为T(n) = O(n^2),它表示对于任何输入实例,该算法的运行时间不可能大于0(n^2)
(2)平均时间复杂度
平均时间复杂度是指所有可能的输入实例均以等概率出现的情况下。
还以快排为例,如果每次均等划分,如果每次都是均等的划分即T(n)=T(n/2)+T(n/2)+O(n),即每次分成两段,则分的次数为logn,每一次处理需要n次计算,那么时间复杂度就是nlogn。如果每次的划分都是完全不平衡的即T(n)=T(n-1)+O(n),那么快排的时间复杂度是n^2。但它是不稳定的,无法确保它是否均等划分,因此我们说快排的平均时间复杂度是nlogn。
四、时间复杂度和实际运行时间
时间复杂度和实际运行时间不是一码事,我们在计算时间复杂度的时候,是忽略所有低次幂和最高次幂的系数的。
比如有一个算法,输入n个数据,经过3n^2+log2(n)+n+5次计算得到结果,其时间复杂度为O(n^2)。另外一个算法只需2n^2次计算就能得到结果,其时间复杂度也是O(n^2),但明显比第一个算法要快。
所以说时间复杂度是可以推演计算的,而实际运算时间不可预测。这也是为什么使用时间复杂度而不是使用实际运算时间来判断一个算法的优劣了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: