8 时间复杂度

8 时间复杂度

常见的时间复杂度量级有:常数阶O(1),对数阶O(logn),线性阶O(n),线性对数阶O(nlogn),平方阶O(n2 ),立方阶O(n3 ),K次方阶O(n k ),指数阶O(2n )。他们的时间复杂度越来越大,执行的效率越来越低。

下面选取一些较为常用的来讲解一下。

常数阶O(1)

代码执行次数是一个常数,不随n的变化而变化,那这个代码的时间复杂度就都是O(1),如下的代码中虽然含有for循环,但循环次数是100次,不随问题规模变化而变化,因此是常数级O(1)的时间复杂度:


for(int i = 1; i <= 100; i++){
	sum += i;
}

线性阶O(n)

这段代码,for循环里面的代码会执行n遍,因此它消耗的时间是随着n的变化而变化的,因此这类代码都可以用O(n)来表示它的时间复杂度。


for(int i = 1; i <= n; i++){
	sum += i;
}

对数阶O(logn)

同样是for循环,但这段代码的时间复杂度是O(logn),因此不能单纯认为for循环就一定是O(n)的。

for(int i = 1; i <= n; i *= 2){
	cnt++;
}

在这个for循环里,i 开始是1,然后不是自增1,而是自乘2,因此i的值依次是1、2、4、8......2x ,也就是当2的x次方大于n时,跳出循环,因此x =logn, 程序执行了
logn次,时间复杂度为:O(logn)

线性对数阶O(nlogn)

线性对数阶其实非常容易理解,将时间复杂度为O(logn) 的代码循环n遍的话,那么它的时间复杂度就是n×O(logn)。

就拿上面的代码加一点修改来举例:


for(int i = 1; i <= n; i++){
	for(int j = 1; j <= n; j *= 2){
		cnt++;
    }
}

平方阶O(n2 )

如果把O(n)的代码再嵌套循环一遍,它的时间复杂度就是 O(n 2 ) 了。

for(int i = 1; i <= n; i++){
   for(int j = 1; j <= n; j++){
		cnt++;
    }
}

其实冒泡排序的时间复杂度也是O(n2),以下代码中,当i=1时,执行n-1次,i=2时,执行n-2次......因此总的执行次数也就是 (n-1)+(n-2)...+2+1就是n*(n-1)/2,显然n2的量级更大,因此常数项和n/2可以忽略,时间复杂度即为O(n2):


for(int i =1; i<=n; i++){
   for(int j=1; j<=n-i; j++){
		if(a[j] > a[j+1]) 
            swap(a[j], a[j+1]);
    }
}

阶乘O(n!)

比如用枚举的方法求1~n的全排列,时间复杂度是 O(n!),效率很低。

练习

8-1

给定一个含N 个不相同数字的数组,在最坏情况下,找出其中最大或最小的数,至少需要N−1 次比较操作。则最坏情况下,在该数组中同时找最大与 最小的数至少需要( )次比较操作。(⌈ ⌉表示向上取整,⌊ ⌋表示向下取整)

A. ⌈3N/2⌉−2
B.⌊3N/2⌋−2
C. 2N−2
D. 2N−4

8-2

基于比较的排序时间复杂度的下限是( ),其中n表示待排序的元素个数。

A. O(n)
B.O(nlogn)
C. O(logn)
D. O(n 2 )

8-3

冒泡排序算法的伪代码如下:

    输入:数组L, n ≥ k。输出:按非递减顺序排序的 L。
    算法 BubbleSort:
       1. FLAG ← n //标记被交换的最后元素位置
       2. while FLAG > 1 do
       3・ k ← FLAG -1
       4・ FLAG ← 1
       5for j=1 to k do
       6.   if L(j) > L(j+1) then do
       7L(j)  ↔ L(j+1)
       8・    FLAG ← j

对n 个数用以上冒泡排序算法进行排序,最少需要比较多少次?( )。

A. n2
B. n−2
C. n−1
D. n

8-4

以比较作为基本运算,在N个数中找出最大数,最坏情况下所需要的最少的比较次数为( )。
A.N 2
B.N
C.N−1
D.N+1

常见算法的时间复杂度

算法

二分查找(Binary Search):O(logn)

二分查找算法每次将搜索区间缩小一半,因此时间复杂度为O(log n)。
倍增法(Exponentiation by Squaring):O(log n) 倍增法用于快速计算幂,如 a^n。每次迭代将幂指数减半,因此时间复杂度为O(log n)。
深度优先搜索(Depth-First Search,DFS):O(V+E) 深度优先搜索遍历图的时间复杂度为O(V+E),其中V表示顶点数,E表示边数。
广度优先搜索(Breadth-First Search,BFS):O(V+E) 和深度优先搜索类似,广度优先搜索遍历图的时间复杂度为O(V+E),其中V表示顶点数,E表示边数。
快速排序(Quick Sort):平均情况下 O(n log n),最坏情况下 O(n^2) 快速排序算法的平均时间复杂度为O(n log n),但最坏情况下(极度不平衡的划分)可能达到O(n^2)。
归并排序(Merge Sort):O(n log n) 归并排序算法的时间复杂度为O(n log n),因为它将数组分成两半,并递归地排序它们,然后将它们合并。
迪杰斯特拉(Dijkstra):O((n+m)logn),是一种用于求解单源最短路径的贪心算法,常用于带有非负权重边的图。
动态规划(Dynamic Programming):时间复杂度因问题而异 动态规划算法的时间复杂度取决于具体问题和子问题的数量。通常,动态规划算法会填充一个表格,时间复杂度与填充该表格所需的操作次数成正比。

函数

sqrt(n):计算n 的平方根。时间复杂度为O(logn),采用二分法等快速算法实现。

sort(a, a+n):对数组 a 进行排序。时间复杂度为 O(nlogn),采用快速排序、归并排序等常用排序算法实现。

__gcd(a, b):计算 a 和b 的最大公约数。时间复杂度为O(log min(a,b)),采用辗转相除法实现。

pow(x, n):计算x 的n 次方。时间复杂度为 O(logn),采用快速幂算法。

abs(x):返回 x 的绝对值。时间复杂度为O(1)。

log(x)、log10(x)、log2(x):计算x 的自然对数、以 10 为底的对数、以 2 为底的对数。时间复杂度为 O(1)。

char数组

以下是常见的一些 char 数组函数及其时间复杂度:

strlen(a):返回字符串 a 的长度。时间复杂度为 O(n)。
strcpy(a, b):将字符串 b复制到 a中。时间复杂度为O(n),其中n 为字符串 b的长度。
strcat(a, b):将字符串 a连接到 b的末尾。时间复杂度为 O(n),其中n 为字符串 a的长度。
strcmp(a, b):比较字符串 a和 b的大小关系。时间复杂度为O(n),其中 n 为字符串 a和 b的长度的较小值。

string

string是一个常用的字符串类,以下是几个常用函数的时间复杂度:

length()/size()😮(1),即常数时间复杂度。这是因为字符串的长度是在构造函数中计算得到的,并且在字符串的操作过程中一直被维护着。
append(): O(n),其中 n 是要追加的字符串的长度。因为要将追加的字符串复制到原字符串的末尾,所以时间复杂度为 O(n)。
insert()😮(n),其中 n 是要插入的字符串的长度。因为要将插入位置后面的字符串往后移动,所以时间复杂度为 O(n)。
erase(): O(n),其中 n 是要删除的字符串的长度。因为要将删除位置后面的字符串往前移动,所以时间复杂度为 O(n)。
find(): O(n),其中 n 是字符串的长度。因为 find() 函数需要遍历整个字符串来查找子串,所以时间复杂度为 O(n)。

递归函数复杂度分析

在分析递归函数的时间复杂度时,我们需要考虑以下因素:

  • 每次递归调用的工作量。
  • 递归的深度(调用的次数)。
  • 每一层递归中的分支数。

通常,我们使用递归树来分析递归算法的时间复杂度。具体的时间复杂度取决于递归算法的实现细节。

我们来看一个简单的例子:计算斐波那契数列的递归实现。斐波那契数列的第n项可以用以下公式表示:

F(n)=F(n−1)+F(n−2),其中F(0)=0,F(1)=1。

下面是一个计算斐波那契数列的递归函数:

int fib(int n) {
    if (n == 0) return 0;
    if (n == 1) return 1;
    return fib(n - 1) + fib(n - 2);
}

为了分析这个递归函数的时间复杂度,我们可以画出它的递归树。每个节点表示一个递归调用,子节点表示从该调用产生的递归调用。这里是树的前几层(每个节点显示调用 fib(x)的x值):


         fib(4)
       /        \
 fib(3)          fib(2)
 /    \          /    \
fib(2) fib(1)  fib(1) fib(0)
/    \
fib(1) fib(0)

观察递归树,我们可以看到每层递归的分支数大约是2,深度是n。因此,这个递归函数的时间复杂度大约是O(2n)。请注意,这是一个非常低效的实现,可以使用动态规划或矩阵乘法将时间复杂度降低到O(n)或O(logn)。

练习

8-5

某算法的计算时间表示为递推关系式T(n)=T(n−1)+n(n 为正整数)及
T(0)=1,则该算法的时间复杂度为( )。

A. O(logn)
B. O(nlogn)
C. O(n)
D. O(n2)

主定理分析时间复杂度

posted @   Jue_Chen  阅读(166)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· DeepSeek在M芯片Mac上本地化部署
· 葡萄城 AI 搜索升级:DeepSeek 加持,客户体验更智能
点击右上角即可分享
微信分享提示