深入理解算法效率:时间复杂度与空间复杂度

embedded/2024/9/25 2:32:42/

目录

引言

一、算法效率的基础

二、时间复杂度

1.概念

2.常见类型

1.O(1) — 常数阶 

2.O(n) — 线性阶

3.O(n^2) — 平方阶

4.O(2^𝑛) — 指数阶

5.O(log 𝑛) — 对数阶 

3.总结 

 三、空间复杂度

1.概念

2.常见类型

1.O(1) — 常数阶

2.O(n) — 线性阶

3.O(n^2) — 平方阶

四、总结


引言

在现代计算机科学和编程中,算法的效率至关重要。算法效率不仅影响程序的运行时间,还直接关系到程序的内存使用情况。为了评估和优化算法,我们常用两个主要指标:时间复杂度和空间复杂度。本文将详细介绍这两个概念,并通过C语言示例来解释它们的实际应用。

一、算法效率的基础

算法设计中,我们先后追求以下两个层面的目标。

1. 找到问题解法:算法需要在规定的输入范围内可靠地求得问题的正确解。

2. 寻求最优解法:同一个问题可能存在多种解法,我们希望找到尽可能高效的算法

也就是说,在能够解决问题的前提下,算法效率已成为衡量算法优劣的主要评价指标,它包括以下两个维度。

时间效率算法运行速度的快慢。

空间效率算法占用内存空间的大小。

简而言之,我们的目标是设计“既快又省”的数据结构算法时间效率和空间效率的评估可以帮助我们选择合适的算法来处理特定问题,并优化程序性能。时间复杂度和空间复杂度是用于衡量这两个方面的关键指标。

二、时间复杂度

1.概念

时间复杂度(Time Complexity)用来衡量算法执行所需时间如何随着输入规模的增长而变化。它帮助我们评估算法在处理大数据量时的表现。时间复杂度通常用大O符号表示,描述了算法在最坏情况下的运行时间。

O的渐进表⽰法
⼤O符号(Big O notation):是⽤于描述函数渐进⾏为的数学符号
💡 推导⼤O阶规则
1. 时间复杂度函数式 T(N) 中,只保留最⾼阶项,去掉那些低阶项,因为当 N 不断变⼤时,
低阶项对结果影响越来越⼩,当 N ⽆穷⼤时,就可以忽略不计了。
2. 如果最⾼阶项存在且不是 1 ,则去除这个项⽬的常数系数,因为当 N 不断变⼤,这个系数
对结果影响越来越⼩,当 N ⽆穷⼤时,就可以忽略不计了。
3. T(N) 中如果没有 N 相关的项⽬,只有常数项,⽤常数 1 取代所有加法常数。

 

2.常见类型

1.O(1) — 常数阶 

常数阶时间复杂度指的是算法的运行时间与输入数据大小 𝑛 无关,即不随着 𝑛 的变化而变化。

在以下函数中,尽管操作数量 size 可能很大,但由于其与输入数据大小 𝑛 无关,因此时间复杂度仍为 𝑂(1)
/* 常数阶 */
int constant(int n) {
int count = 0;
int size = 100000;
int i = 0;
for (int i = 0; i < size; i++) {
count++;
}
return count; }

2.O(n) — 线性阶

线性阶时间复杂度指的是算法的运行时间随着输入规模n增加而以线性级别增长。

线性阶通常出现在单层循环中:
/* 线性阶 */
int linear(int n) {int count = 0;for (int i = 0; i < n; i++) {count++;}return count;
}

遍历数组和遍历链表等操作的时间复杂度均为 𝑂(𝑛) ,其中 𝑛 为数组或链表的长度:  

/* 线性阶(遍历数组) */
int arrayTraversal(int* nums, int n) {int count = 0;// 循环次数与数组长度成正比for (int i = 0; i < n; i++) {count++;}return count;
}

3.O(n^2) — 平方阶

平方阶时间复杂度指的是算法的运行时间随着输入规模n增加而以平方级别增长。

平方阶通常出现在嵌套循环中,外层循环和内层循环的时间复杂度都为 𝑂(𝑛) ,因此总体的时间复杂度为 𝑂(𝑛 2 )
/* 平方阶 */
int quadratic(int n) {int count = 0;// 循环次数与数据大小 n 成平方关系for (int i = 0; i < n; i++) {for (int j = 0; j < n; j++) {count++;}}return count;
}

4.O(2^𝑛) — 指数阶

指数阶时间复杂度指的是算法的运行时间随着输入规模n增加而以指数级别增长。
生物学的“细胞分裂”是指数阶增长的典型例子:初始状态为 1 个细胞,分裂一轮后变为 2 个,分裂两轮后变为 4 个,以此类推,分裂 𝑛 轮后有 2 𝑛 个细胞。

指数时间复杂度通常出现于解决组合问题或递归深度较大的算法

/* 指数阶(递归实现) */
int expRecur(int n) {if (n == 1)return 1;return expRecur(n - 1) + expRecur(n - 1) + 1;
}

5.O(log 𝑛) — 对数阶 

对数阶时间复杂度指的是算法的运行时间随着输入规模n增加而以对数级别增长。

与指数阶类似,对数阶也常出现于递归函数中。以下代码形成了一棵高度为 log𝑛 的递归树:

/* 对数阶(递归实现) */
int logRecur(int n) {if (n <= 1)return 0;return logRecur(n / 2) + 1;
}
O(log 𝑛) 的底数是多少?
准确来说,“一分为 𝑚 ”对应的时间复杂度是 𝑂( log 𝑚 𝑛) 。而通过对数换底公式,我们可以得到具有不同底数、相等的时间复杂度:
也就是说,底数 𝑚 可以在不影响复杂度的前提下转换。因此我们通常会省略底数 𝑚 ,将对数阶直接记为 𝑂( log 𝑛)

3.总结 

设输入数据大小为 𝑛 ,常见的时间复杂度类型如图 2‑9 所示(按照从低到高的顺序排列)。
 O(1)  < O(log n)  < 𝑂(𝑛) O(n log n) <  O(2^𝑛)  <  O(2^𝑛) O(𝑛!)
常数阶 < 对数阶 < 线性阶 < 线性对数阶 < 平方阶 < 指数阶 < 阶乘阶

 三、空间复杂度

1.概念

空间复杂度(Space Complexity)衡量算法在执行过程中所需的额外内存空间如何随着输入规模的增长而变化。它描述了算法对内存的需求,通常也用大O符号表示。(这个概念与时间复杂度非常类似,只需将“运行时间”替换为“占用内存空间”。

2.常见类型

1.O(1) — 常数阶

常数空间复杂度表示算法所需的额外内存空间不随输入规模变化。例如,交换两个变量的值:
#include <stdio.h>void swap(int *a, int *b) {int temp = *a;*a = *b;*b = temp;
}int main() {int x = 10, y = 20;swap(&x, &y);printf("x = %d, y = %d\n", x, y);return 0;
}

2.O(n) — 线性阶

线性空间复杂度表示算法所需的额外内存空间与输入规模成正比。例如,创建一个数组:
#include <stdio.h>
#include <stdlib.h>int *create_array(int size) {int *arr = (int *)malloc(size * sizeof(int));for (int i = 0; i < size; i++) {arr[i] = i;}return arr;
}int main() {int size = 5;int *arr = create_array(size);for (int i = 0; i < size; i++) {printf("%d ", arr[i]);}printf("\n");free(arr);return 0;
}

3.O(n^2) — 平方阶

平方空间复杂度表示算法的额外内存使用与输入规模的平方成正比。例如,创建一个二维矩阵:
#include <stdio.h>
#include <stdlib.h>int **create_matrix(int n) {int **matrix = (int **)malloc(n * sizeof(int *));for (int i = 0; i < n; i++) {matrix[i] = (int *)malloc(n * sizeof(int));}return matrix;
}void print_matrix(int **matrix, int n) {for (int i = 0; i < n; i++) {for (int j = 0; j < n; j++) {printf("%d ", matrix[i][j]);}printf("\n");}
}int main() {int n = 3;int **matrix = create_matrix(n);for (int i = 0; i < n; i++) {for (int j = 0; j < n; j++) {matrix[i][j] = i * n + j + 1;}}print_matrix(matrix, n);for (int i = 0; i < n; i++) {free(matrix[i]);}free(matrix);return 0;
}

四、总结

理解时间复杂度和空间复杂度是编写高效程序的基础。时间复杂度告诉我们算法的运行时间如何随输入规模变化,而空间复杂度则描述了算法对内存的需求。掌握这些概念可以帮助我们选择和优化算法,提高程序的性能。

希望本文能帮助你更好地理解算法复杂度。如果有任何问题或讨论,请在评论区留言!


http://www.ppmy.cn/embedded/112323.html

相关文章

数据挖掘顶会ICDM 2024论文分享┆MetaSTC:一种基于聚类和元学习的时空预测框架

第24届IEEE国际数据挖掘会议&#xff08;IEEE International Conference on Data Mining&#xff0c;ICDM&#xff09;将于2024年12月9日至12日在阿拉伯联合酋长国首都阿布扎比隆重举行。ICDM是世界数据挖掘研究顶级会议&#xff0c;创办于2001年&#xff0c;每年举办一届,会议…

Wordpress右下角表单弹出插件

Ultimate Sticky Popup & Widgets Charcoal Making Machine | Equipment for Sale - Kingtiger

Unity面试:什么是UnityEvent?

UnityEvent是Unity引擎中一种特殊的事件系统&#xff0c;属于Unity的事件和委托机制。它允许开发者在运行时定义和管理事件的响应&#xff0c;从而实现松耦合的事件处理。 以下是UnityEvent的一些主要特点和用途&#xff1a; 松耦合的设计&#xff1a;UnityEvent允许对象之间…

Vue3.0组合式API:computed计算属性、watch监听器、watchEffect高级监听器

1、computed() 计算属性 在模板中绑定表达式只能用于简单的运算。如果运算比较复杂&#xff0c;可以使用 Vue.js 提供的计算属性&#xff0c;通过计算属性可以处理比较复杂的逻辑。 1.1 计算属性的应用 通过计算属性可以实现各种复杂的逻辑&#xff0c;包括运算、函数调用等…

2024最全网络安全工程师面试题(附答案),金九银十找工作必看!

《网安面试指南》http://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247484339&idx1&sn356300f169de74e7a778b04bfbbbd0ab&chksmc0e47aeff793f3f9a5f7abcfa57695e8944e52bca2de2c7a3eb1aecb3c1e6b9cb6abe509d51f&scene21#wechat_redirect 《Java代码审…

k8s service如何实现流量转发

1 基本概念 Service&#xff1a;在Kubernetes&#xff08;K8s&#xff09;中&#xff0c;Service用于将流量转发到后端的Pod中。Service提供了一种稳定的网络入口&#xff0c;尽管后端的Pod可能会动态改变 kube-proxy: kube-proxy是Kubernetes集群中的核心组件之一&#xff0…

python乱炖6——sum(),指定维度进行求和

python乱炖6——sum&#xff08;&#xff09;&#xff0c;指定维度进行求和 import torch# 创建一个三维张量 x torch.tensor([[[1, 2, 3], [4, 5, 6]],[[7, 8, 9], [10, 11, 12]] ])print("Original tensor x:") print(x) print(x.shape)>>> tensor([[[ …

显示器最佳分辨率设置

文章目录 最佳分辨率原则MacBook 的视网膜屏的物理分辨率通常远高于系统推荐的设置分辨率 显示器常见长宽比16:9显示器最佳分辨率推荐16:10显示器最佳分辨率推荐 最佳分辨率原则 No1. 显示器的默认分辨率设置&#xff08;实际上是系统设置&#xff09;一般就是最佳分辨率。 N…