时间复杂度

本文详细介绍了时间复杂度的概念,强调了它在衡量算法效率中的作用。大O符号用于表示算法运行时间的上限,插入排序和快速排序的时间复杂度分析作为例子进行了讲解。此外,还阐述了如何计算时间复杂度,包括识别基本语句、计算执行次数的数量级以及使用大O记号表示。最后,通过具体示例展示了不同循环和条件语句结构的时间复杂度分析方法。
摘要由CSDN通过智能技术生成

时间复杂度

怎么理解时间复杂度?

时间复杂度是一个函数,它定性描述该算法的运行时间。

假设算法的问题规模为n,那么操作单元数量便用函数f(n)来表示,随着数据规模n的增大,算法执行时间的增长率和f(n)的增长率相同,这称作为算法的渐近时间复杂度,简称时间复杂度,记为 O(f(n))。

大O是什么?

大O用来表示上界的,当用它作为算法的最坏情况运行时间的上界,就是对任意数据输入的运行时间的上界。

拿插入排序来说,插入排序的时间复杂度我们都说是O(n2) 。输入数据的形式对程序运算时间是有很大影响的,在数据本来有序的情况下时间复杂度是O(n),但如果数据是逆序的话,插入排序的时间复杂度就是O(n2), 也就对于所有输入情况来说,最坏是O(n2) 的时间复杂度,所以称插入排序的时间复杂度为O(n2)。

同样的同理再看一下快速排序,都知道快速排序是O(nlogn),但是当数据已经有序情况下,快速排序的时间复杂度是O(n2) 的,所以严格从大O的定义来讲,快速排序的时间复杂度应该是O(n2)。
但是我们依然说快速排序是O(nlogn)的时间复杂度,这个就是业内的一个默认规定,这里说的O代表的就是一般情况,而不是严格的上界。

参考文章关于时间复杂度,你不知道的都在这里!

如何计算算法的时间复杂度?

  1. 找出算法中的基本语句;
    算法中执行次数最多的那条语句就是基本语句,通常是最内层循环的循环体。

  2. 计算基本语句的执行次数的数量级;
    只需保留f(n)中的最高次幂正确即可,可以忽略所有低次幂和最高次幂的系数。

  3. 用大Ο记号表示算法的时间性能。
    将基本语句执行次数的数量级放入大Ο记号中。

如果算法中包含嵌套的循环,则基本语句通常是最内层的循环体,如果算法中包含并列的循环,则将并列循环的时间复杂度相加。例如:

for (i=1; i<=n; i++)
  x++;
for (i=1; i<=n; i++)
  for (j=1; j<=n; j++)
    x++;

第一个for循环的时间复杂度为Ο(n),第二个for循环的时间复杂度为Ο(n²),则整个算法的时间复杂度为Ο(n+n²)=Ο(n²)。
注、加法原则:T(n)=O(f(n))+O(g(n))=O(max(fn,gn))

常见的算法时间复杂度由小到大依次为:Ο(1)<Ο(log2n)<Ο(n)<Ο(nlog2n)<Ο(n²)<Ο(n³)<…<Ο(2n)<Ο(n!)<O(nn)

Ο(1)表示基本语句的执行次数是一个常数,一般来说,只要算法中不存在循环语句,其时间复杂度就是Ο(1)。Ο(log2n)、Ο(n)、Ο(nlog2n)、Ο(n2)和Ο(n3)称为多项式时间,而Ο(2n)和Ο(n!)称为指数时间。计算机科学家普遍认为前者是有效算法,把这类问题称为P类问题,而把后者称为NP问题。

下面列举几种常见的情况:

①对于一个循环,假设循环体的时间复杂度为 O(n),循环次数为 m,则这个循环的时间复杂度为 O(n×m)。

void aFunc(int n) {

  for(int i = 0; i < n; i++) { // 循环次数为 n

  printf("Hello, World!\n"); // 循环体时间复杂度为 O(1)

  }
}

此时时间复杂度为 O(n × 1),即 O(n)。

②对于多个循环,假设循环体的时间复杂度为 O(n),各个循环的循环次数分别是a, b, c…,则这个循环的时间复杂度为 O(n×a×b×c…)。分析的时候应该由里向外分析这些循环。

void aFunc(int n) {
    for(int i = 0; i < n; i++) { // 循环次数为 n
        for(int j = 0; j < n; j++) { // 循环次数为 n
            printf("Hello, World!\n"); // 循环体时间复杂度为 O(1)
        }
    }
}  

此时时间复杂度为 O(n × n × 1),即 O(n^2)。

③对于顺序执行的语句或者算法,总的时间复杂度等于其中最大的时间复杂度。

void aFunc(int n) {
// 第一部分时间复杂度为 O(n^2)
for(int i = 0; i < n; i++) {
  for(int j = 0; j < n; j++) {
    printf("Hello, World!\n");
  }
}
// 第二部分时间复杂度为 O(n)
for(int j = 0; j < n; j++) {
  printf("Hello, World!\n");
}
}

此时时间复杂度为 max(O(n2), O(n)),即 O(n2)。

④对于条件判断语句,总的时间复杂度等于其中 时间复杂度最大的路径 的时间复杂度。

void aFunc(int n)
 {
	if (n >= 0) 
	{
		// 第一条路径时间复杂度为 O(n^2)
		for(int i = 0; i < n; i++)
		 {
			for(int j = 0; j < n; j++)
			 {
				printf("输入数据大于等于零\n");
			}
		}
	}
	 else 
	 {
		// 第二条路径时间复杂度为 O(n)
		for(int j = 0; j < n; j++)
		 {
			printf("输入数据小于零\n");
		}
	}
}

此时时间复杂度为 max(O(n2), O(n)),即 O(n2)。

提升一下难度:
①求该方法的时间复杂度:

void aFunc(int n) {
    for (int i = 2; i < n; i++) {
        i *= 2;
        printf("%i\n", i);
    }
}

参考答案:
假设循环次数为 t,则循环条件满足 2^t < n。
可以得出,执行次数t = log(2)(n),即 T(n) = log(2)(n),可见时间复杂度为 O(log(2)(n)),即 O(log n)。

②递归的时间复杂度

long aFunc(int n) {
    if (n <= 1) {
        return 1;
    } else {
        return aFunc(n - 1) + aFunc(n - 2);
    }
}

递归算法的时间复杂度本质上是要看: 递归的次数 * 每次递归的时间复杂度。

可以看出上面的代码每次递归都是O(1)的操作。再来看递归了多少次,这里将i为5作为输入的递归过程 抽象成一颗递归树
在这里插入图片描述
从图中,可以看出f(5)是由f(4)和f(3)相加而来,那么f(4)是由f(3)和f(2)相加而来 以此类推。

在这颗二叉树中每一个节点都是一次递归,那么这棵树有多少个节点呢?

我们之前也有说到,一棵深度(按根节点深度为1)为k的二叉树最多可以有 2^k - 1 个节点。

所以该递归算法的时间复杂度为 O(2^n) 。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值