数据结构第一课!时间复杂度

大家好呀!我是小桑。

要我说,谈到计算机就不能不说数据结构与算法,谈到数据结构与算法就不能不说复杂度分析。

作为编程界的老大哥,他的重要性不言而喻。在我看来,这是数据结构与算法中最重要的知识点。有多重要呢?反正很重要就是了。

尼尼 :真假的!就这玩意儿能有多重要,我才不信。
小桑 :切,你可别不信,人们判断一个算法的优劣可离不开他。
尼尼 :这么厉害,那我可要好好听了。快讲,快讲。
小桑:先别急,还不赶快拿个小板凳做好。

复杂度分析

从【时效性】和【存储】两方面看待问题,我们不难理解好的算法具备高时效性和低存储需求的特点。对于人类而言,我们总是希望在做一件事情时付出最小的代价,获得最大的回报。在算法领域,这被翻译成了在解决问题时花最少时间和最少存储,做成最出色的解决方案。

科学家们深谋研究,发现将算法步骤数量作为度量指标是一个合理而有效的选择。这种独立于具体机器和编程语言的度量方式,为我们提供了一种公正的比较算法优劣的方法。

那如何去考量"更少的时间和更少的存储",复杂度分析为此而生。

时间复杂度

ini 复制代码
1  int sum = 0;
2  int i = 1;
3
4  for (i = 1; i <= n; i++) {
5    sum += i;
6  }

在上面假设的情况,这段求累加和的代码总的运行时间是多少呢?

  • 第 1.2 行代码需要 1 Btime 的运行时间
  • 第 4 行和第 5 行运行了 n 次,所以每个需要 n * Btime 的运行时间。

所以总的运行时间就是 (2 + 2n) * Btime

嘿嘿,那我们怎么表示这段时间复杂度呢?

那我们就不得不介绍我们最隆重的嘉宾大 O 出场了。

什么是大 O?

按照算法导论给出的解释:大 O 是用来表示上界的,作为算法的最坏情况运行时间的上界。

代表着一种趋势,一种随着数据集的规模增大,算法代码运行时间变化的一种趋势。记为

乍一看这个公式是不是目瞪口呆?让我来给你细细演示一番。

还是以之前的代码为例。当 n = 1000 时, 2n + 2 = 2002, 当 n = 10000 时,2n + 2 = 20002,当 n 持续增大时,常数 2 和系数 2 对于最后的结果越来越没存在感,即对趋势的变化影响不大。

最终T(n) = O(n)

时间复杂度分析

代码
ini 复制代码
1  int i = 0;
2  int j = 0;
3  for(i = 0; i < n; i++){
4    for(j = 0; j < n; j++){
5      //some O(1) expressions
6    }
7  }

第 1 行和第 2 行各需要运行 1 次 ,第 3 行和第 4 行的嵌套循环共需要运行 n² 次。所以总的运行次数 f(n) = 2 + n²。

当 n 为 5 的时候,f(n) = 2 + 25,当 n 为 10000 的时候,f(n) = 2 + 100000000,当 n 更大呢?

这个时候其实很明显的就可以看出来 n² 起到了决定性的作用,像常数 1 和系数 2 对最终的结果(即趋势)影响不大,所以我们可以把它们直接忽略掉,所以执行的总步数就可以看成是"主导"结果的那个,也就是 f(n) = n²。

自然代码的运行时间 T(n) = O(f(n)) = O(n²)。

小桑 :尼尼,看懂了吗?
尼尼 :当然,也就是说对于时间复杂度,我们只要关心最高的那个复杂度就行,其他的不用考虑。
小桑 :可以啊,领悟的很快。
尼尼 :那可不,也不看看我是谁。
小桑:。。。

补充

一般我们计算时间复杂度总是考虑最坏的情况下的时间复杂度,以保证算法的运行时间不会比他更长。

在分析一个程序的时间复杂性是,有以下两条规则:

1)加法规则: <math xmlns="http://www.w3.org/1998/Math/MathML"> T ( n ) T(n) </math>T(n) = <math xmlns="http://www.w3.org/1998/Math/MathML"> T 1 ( n ) {T1}(n) </math>T1(n) + <math xmlns="http://www.w3.org/1998/Math/MathML"> T 2 ( n ) {T2}(n) </math>T2(n) = <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( f ( n ) ) + O ( g ( n ) ) = O ( m a x ( f ( n ) , g ( n ) ) ) O( f(n) ) + O( g(n) ) = O( max( f(n) , g(n) ) ) </math>O(f(n))+O(g(n))=O(max(f(n),g(n)))

2)乘法规则: <math xmlns="http://www.w3.org/1998/Math/MathML"> T ( n ) = T 1 ( n ) ∗ T 2 ( n ) = O ( f ( n ) ) ∗ O ( g ( n ) ) = O ( ( f ( n ) ∗ g ( n ) ) ) T(n) = T1(n)*T2(n) = O( f(n) )*O( g(n) ) = O( (f(n) * g(n) ) ) </math>T(n)=T1(n)∗T2(n)=O(f(n))∗O(g(n))=O((f(n)∗g(n)))

常见的时间复杂度

顺便在这里给大家把常见的时间复杂度排排序,记住这些就足够了。

O(1) < O(logn) < O(n) < O(nlogn) < O( <math xmlns="http://www.w3.org/1998/Math/MathML"> n 2 n^2 </math>n2) < O( <math xmlns="http://www.w3.org/1998/Math/MathML"> n 3 n^3 </math>n3) < O( <math xmlns="http://www.w3.org/1998/Math/MathML"> 2 n 2^n </math>2n) < O(n!) < O( <math xmlns="http://www.w3.org/1998/Math/MathML"> n n n^n </math>nn)

O(1)

O(1) 是 O(1) 常量级时间复杂度

arduino 复制代码
1  void sunny(){
2  printf("1");
3  printf("2");
4  printf("3");
5  printf("4");
6  }

只要算法是有限次(常量、和规模 n 无关)执行次数,就都是常量级。可以看到无论 printf 写了多少行都是 O(1)。

比如上面这段代码,他运行了 4 次,但他的时间复杂度仍然是 O(1),而不是 O(4)。

O(logn,nlogn)

这里先引入高中数学的换底公式,还记得不?

在计算过程中我们忽略了底,直接用 O(logn) 来表示对数时间复杂度。

ini 复制代码
1  int i = 0;
2  int j = 0;
3  for(i = 0;i < n; i++){
4    for(j = 0; j < n; j = j * 2){
5      //some O(1) expressions
6    }
7  }

例如这段代码,这次是O(n)O(logn) 的结合,外层循环 n 次,所以最终的算法复杂度是O(nlogn)


时间复杂度算是我们学习数据结构的敲门砖。当然了,还有他的好兄弟空间复杂度。

希望各位童鞋们看完这篇文章能对时间复杂度有个最基本的认识。多编码,多练习,熟能生巧,你一定可以的!

相关推荐
是小Y啦11 分钟前
leetcode 106.从中序与后续遍历序列构造二叉树
数据结构·算法·leetcode
liuyang-neu21 分钟前
力扣 42.接雨水
java·算法·leetcode
y_dd29 分钟前
【machine learning-12-多元线性回归】
算法·机器学习·线性回归
m0_6312704029 分钟前
标准c语言(一)
c语言·开发语言·算法
万河归海42829 分钟前
C语言——二分法搜索数组中特定元素并返回下标
c语言·开发语言·数据结构·经验分享·笔记·算法·visualstudio
小周的C语言学习笔记33 分钟前
鹏哥C语言36-37---循环/分支语句练习(折半查找算法)
c语言·算法·visual studio
y_dd34 分钟前
【machine learning-七-线性回归之成本函数】
算法·回归·线性回归
小魏冬琅1 小时前
K-means 算法的介绍与应用
算法·机器学习·kmeans
凌肖战2 小时前
力扣上刷题之C语言实现(数组)
c语言·算法·leetcode
秋夫人2 小时前
B+树(B+TREE)索引
数据结构·算法