一篇文章搞懂时间复杂度和空间复杂度
zhezhongyun 2025-09-06 16:13 19 浏览
不知道小伙伴们有没有刷过力扣上的算法题,我在上研究生的时候,刷过了前40道题,上面的算法题,我觉得还挺难的,当你写完代码的时候,就可以提交自己写的代码到系统上,系统会给你写的代码计算时间复杂度和空间复杂度,并给出了你的排名。可能有的小伙伴不理解这个概念,今天我们来详细讲一下。
在编写和优化代码时,时间复杂度和空间复杂度是两个关键概念。它们帮助我们衡量算法的效率和资源使用情况,从而选择最优的解决方案。本文将详细探讨这两个概念,并通过具体的例子来说明它们在实际编程中的应用。
时间复杂度?
时间复杂度描述了一个算法的运行时间如何随着输入数据规模的增长而变化。它通常用大O符号表示,例如O(1)、O(n)、O(log n)等。以下是几种常见的时间复杂度类型及其含义:
O(1) - 常数时间复杂度: 不管输入数据的大小如何,算法的执行时间始终相同。常数时间复杂度表示算法的运行时间不受输入数据规模的影响。
例子:
public int GetFirstElement(int[] array)
{
return array[0];
}
解释: 这段代码总是返回数组的第一个元素,不管数组有多大,花费的时间都是一样的。这就像从一堆书里拿起最上面的一本书,不管书堆有多高,拿起一本书的时间始终不变。、
示例图:
函数调用: 数组:
GetFirstElement(array) [10, 20, 30, 40, 50]
| 0 1 2 3 4
| ↑
返回 array[0] |
|
O(1) <--------------------+
注释:访问数组第一个元素的操作始终是常数时间,不受数组大小影响。
通过这种方式,我们可以清晰地展示O(1)时间复杂度的概念,即无论输入数据的大小如何,算法的执行时间始终相同。
O(n) - 线性时间复杂度: 算法的执行时间与输入数据规模成正比。当输入数据增大时,运行时间也线性增长。
例子:求数组每个元素之和
public int Sum(int[] array)
{
int sum = 0;
foreach (int item in array)
{
sum += item;
}
return sum;
}
解释: 这个函数需要遍历整个数组来计算所有元素的总和。数组越大,需要的时间就越长。就像你需要逐个数数一堆硬币,硬币越多,数完的时间就越长。
示例图
scss复制代码函数调用: 数组:
Sum(array) [10, 20, 30, 40, 50]
| 0 1 2 3 4
| ↑ ↑ ↑ ↑ ↑
遍历每个元素 <------------------ |
|
时间复杂度:O(n) <---------------+
注释:计算数组所有元素的总和需要遍历整个数组,运行时间与数组大小成正比。
O(log n) - 对数时间复杂度: 算法的执行时间随着输入数据规模的增长按对数比例增加。这通常出现在需要将数据集分成较小部分的算法中,例如二分查找。
例子:在有序数组中查找目标元素的索引
public int BinarySearch(int[] array, int target)
{
int left = 0;
int right = array.Length - 1;
while (left <= right)
{
int mid = left + (right - left) / 2;
if (array[mid] == target)
return mid;
if (array[mid] < target)
left = mid + 1;
else
right = mid - 1;
}
return -1;
}
解释: 这个函数使用二分查找法来寻找目标元素。每次迭代都会把搜索范围缩小一半。这就像在一本按字母顺序排列的字典里查一个词,每次翻开中间的页面,决定目标词在前半部分还是后半部分,然后重复这个过程。
示例图
scss复制代码函数调用: 数组:
BinarySearch(array, target) [10, 20, 30, 40, 50]
| 0 1 2 3 4
| 第一次比较:
| mid = 2 -> 30 == target ?
| 目标小于30,搜索范围缩小一半
| 新范围: [10, 20]
| 0 1
| 第二次比较:
| mid = 0 -> 10 == target ?
| 目标大于10,搜索范围缩小一半
| 新范围: [20]
| 1
| 第三次比较:
| mid = 1 -> 20 == target ?
| 找到目标,返回索引1
↑
|
|
O(log n) <----------------+
注释:每次迭代搜索范围缩小一半,因此执行时间按对数比例增加。
O(n^2) - 平方时间复杂度: 算法的执行时间与输入数据规模的平方成正比。这通常出现在嵌套循环中,每个循环遍历输入数据。
例子:将数组中的数据从小到大排序
public void BubbleSort(int[] array)
{
for (int i = 0; i < array.Length - 1; i++)
{
for (int j = 0; j < array.Length - i - 1; j++)
{
if (array[j] > array[j + 1])
{
int temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
}
}
}
}
解释: 这个函数使用冒泡排序算法对数组进行排序。它包含两个嵌套的循环,每个循环都会遍历数组。就像你要给一堆学生排座次,每次都要两两比较,整个过程需要很多次。
示例图
less复制代码函数调用: 数组:
BubbleSort(array) [30, 10, 40, 20, 50]
外层循环 i = 0:
| 第一次比较:30 > 10, 交换
| 新数组:[10, 30, 40, 20, 50]
内层循环 j = 0:
| 第二次比较:30 > 40, 不交换
| 新数组:[10, 30, 40, 20, 50]
| 第三次比较:40 > 20, 交换
| 新数组:[10, 30, 20, 40, 50]
| 第四次比较:40 > 50, 不交换
外层循环 i = 1:
| 第一次比较:10 > 30, 不交换
| 新数组:[10, 30, 20, 40, 50]
内层循环 j = 1:
| 第二次比较:30 > 20, 交换
| 新数组:[10, 20, 30, 40, 50]
| 第三次比较:30 > 40, 不交换
... (持续进行直到排序完成)
↑
|
|
O(n^2) <---------------------+
注释:每次外层循环遍历数组,内层循环也遍历数组,因此执行时间与数组大小的平方成正比。
O(2^n) - 指数时间复杂度: 算法的执行时间随着输入数据规模的增长按指数比例增加。这种复杂度通常出现在递归算法中,尤其是解决组合问题时。
例子:
public int Fibonacci(int n)
{
if (n <= 1)
return n;
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
解释: 这个函数计算斐波那契数列的第n个数字。每次调用函数时,它会递归调用自身两次,导致总的调用次数按指数增长。
示例图
scss复制代码Fibonacci(5)
|
-------------
| |
Fib(4) Fib(3)
| |
---- -----
| | | |
Fib(3) Fib(2) Fib(2) Fib(1)
| | | |
--- --- --- ---
| | | | | | |
Fib Fib Fib Fib Fib Fib
(2) (1) (1) (0) (1) (0)
|
...
注释:每次调用函数时,它会递归调用自身两次,导致总的调用次数按指数增长,时间复杂度为 O(2^n)。
空间复杂度?
空间复杂度描述了一个算法在运行时需要的内存空间如何随着输入数据规模的增长而变化。以下是几种常见的空间复杂度类型及其含义:
O(1) - 常数空间复杂度: 算法所需的内存空间不随输入数据规模的变化而变化。
例子:
public int GetFirstElement(int[] array)
{
return array[0];
}
解释: 这个函数只返回数组的第一个元素,不需要额外的内存空间。就像你只拿一张桌子放书,无论书堆多高,只用一张桌子就够了。
示例图
scss复制代码函数调用: 数组:
GetFirstElement(array) [10, 20, 30, 40, 50]
| 0 1 2 3 4
| ↑
返回 array[0] |
|
O(1) <---------------------+
注释:访问数组第一个元素的操作不需要额外的内存空间,内存使用量不随输入数据规模变化。
O(n) - 线性空间复杂度: 算法所需的内存空间与输入数据规模成正比。
例子:
public int[] CopyArray(int[] array)
{
int[] newArray = new int[array.Length];
for (int i = 0; i < array.Length; i++)
{
newArray[i] = array[i];
}
}
解释: 这个函数创建了一个和输入数组一样大的新数组。就像你需要一个新的箱子来装一堆硬币,硬币越多,箱子就得越大。
示例图
O(n^2) - 平方空间复杂度: 算法所需的内存空间与输入数据规模的平方成正比。
例子:
public int[,] CreateMatrix(int n)
{
int[,] matrix = new int[n, n];
return matrix;
}
解释: 这个函数创建了一个n x n的矩阵。随着n的增加,所需的内存空间按平方增长。
示例图
scss复制代码函数调用: 矩阵:
CreateMatrix(n) n = 2
| [1, 1]
| [1, 1]
| 矩阵大小:2 x 2
|
| n = 3
| [1, 1, 1]
| [1, 1, 1]
| [1, 1, 1]
| 矩阵大小:3 x 3
|
| n = 4
| [1, 1, 1, 1]
| [1, 1, 1, 1]
| [1, 1, 1, 1]
| [1, 1, 1, 1]
| 矩阵大小:4 x 4
↑
|
|
O(n^2) <---------------------+
注释:创建一个n x n的矩阵所需的内存空间随着n的平方增长。
O(2^n) - 指数空间复杂度: 算法所需的内存空间随着输入数据规模的增长按指数比例增加。
例子:字符串的所有子集
public List<string> GenerateSubsets(string str)
{
if (str.Length == 0)
{
return new List<string> { "" };
}
char first = str[0];
List<string> subsets = GenerateSubsets(str.Substring(1));
List<string> newSubsets = new List<string>();
foreach (string subset in subsets)
{
newSubsets.Add(first + subset);
newSubsets.Add(subset);
}
return newSubsets;
}
解释: 这个函数生成字符串的所有子集。随着字符串长度的增加,生成的子集数量按指数增长,需要的内存空间也按指数增长。
示例图
scss复制代码函数调用:
GenerateSubsets("abc")
|
---------------
| |
GenSub("bc") GenSub("bc")
| |
GenSub("c") GenSub("c")
| |
GenSub("") GenSub("")
| |
[""] [""]
生成的子集:
["", "c", "b", "bc", "a", "ac", "ab", "abc"]
空间复杂度:O(2^n)
相关推荐
- perl基础——循环控制_principle循环
-
在编程中,我们往往需要进行不同情况的判断,选择,重复操作。这些时候我们需要对简单语句来添加循环控制变量或者命令。if/unless我们需要在满足特定条件下再执行的语句,可以通过if/unle...
- CHAPTER 2 The Antechamber of M de Treville 第二章 特雷维尔先生的前厅
-
CHAPTER1TheThreePresentsofD'ArtagnantheElderCHAPTER2TheAntechamber...
- CHAPTER 5 The King'S Musketeers and the Cardinal'S Guards 第五章 国王的火枪手和红衣主教的卫士
-
CHAPTER3TheAudienceCHAPTER5TheKing'SMusketeersandtheCardinal'SGuard...
- CHAPTER 3 The Audience 第三章 接见
-
CHAPTER3TheAudienceCHAPTER3TheAudience第三章接见M.DeTrévillewasatt...
- 别搞印象流!数据说明谁才是外线防守第一人!
-
来源:Reddit译者:@assholeeric编辑:伯伦WhoarethebestperimeterdefendersintheNBA?Here'sagraphofStea...
- V-Day commemorations prove anti-China claims hollow
-
People'sLiberationArmyhonorguardstakepartinthemilitaryparademarkingthe80thanniversary...
- EasyPoi使用_easypoi api
-
EasyPoi的主要特点:1.设计精巧,使用简单2.接口丰富,扩展简单3.默认值多,writelessdomore4.springmvc支持,web导出可以简单明了使用1.easypoi...
- 关于Oracle数据库12c 新特性总结_oracle数据库12514
-
概述今天主要简单介绍一下Oracle12c的一些新特性,仅供参考。参考:http://docs.oracle.com/database/121/NEWFT/chapter12102.htm#NEWFT...
- 【开发者成长】JAVA 线上故障排查完整套路!
-
线上故障主要会包括CPU、磁盘、内存以及网络问题,而大多数故障可能会包含不止一个层面的问题,所以进行排查时候尽量四个方面依次排查一遍。同时例如jstack、jmap等工具也是不囿于一个方面的问题...
- 使用 Python 向多个地址发送电子邮件
-
在本文中,我们将演示如何使用Python编程语言向使用不同电子邮件地址的不同收件人发送电子邮件。具体来说,我们将向许多不同的人发送电子邮件。使用Python向多个地址发送电子邮件Python...
- 提高工作效率的--Linux常用命令,能够决解95%以上的问题
-
点击上方关注,第一时间接受干货转发,点赞,收藏,不如一次关注评论区第一条注意查看回复:Linux命令获取linux常用命令大全pdf+Linux命令行大全pdf为什么要学习Linux命令?1、因为Li...
- linux常用系统命令_linux操作系统常用命令
-
系统信息arch显示机器的处理器架构dmidecode-q显示硬件系统部件-(SMBIOS/DMI)hdparm-i/dev/hda罗列一个磁盘的架构特性hdparm-tT/dev/s...
- 小白入门必知必会-PostgreSQL-15.2源码编译安装
-
一PostgreSQL编译安装1.1下载源码包在PostgreSQL官方主页https://www.postgresql.org/ftp/source/下载区选择所需格式的源码包下载。cd/we...
- Linux操作系统之常用命令_linux系统常用命令详解
-
Linux操作系统一、常用命令1.系统(1)系统信息arch显示机器的处理器架构uname-m显示机器的处理器架构uname-r显示正在使用的内核版本dmidecode-q显示硬件系...
- linux网络命名空间简介_linux 网络相关命令
-
此篇会以例子的方式介绍下linux网络命名空间。此例中会创建两个networknamespace:nsa、nsb,一个网桥bridge0,nsa、nsb中添加网络设备veth,网络设备间...
- 一周热门
- 最近发表
-
- perl基础——循环控制_principle循环
- CHAPTER 2 The Antechamber of M de Treville 第二章 特雷维尔先生的前厅
- CHAPTER 5 The King'S Musketeers and the Cardinal'S Guards 第五章 国王的火枪手和红衣主教的卫士
- CHAPTER 3 The Audience 第三章 接见
- 别搞印象流!数据说明谁才是外线防守第一人!
- V-Day commemorations prove anti-China claims hollow
- EasyPoi使用_easypoi api
- 关于Oracle数据库12c 新特性总结_oracle数据库12514
- 【开发者成长】JAVA 线上故障排查完整套路!
- 使用 Python 向多个地址发送电子邮件
- 标签列表
-
- HTML 教程 (33)
- HTML 简介 (35)
- HTML 实例/测验 (32)
- HTML 测验 (32)
- JavaScript 和 HTML DOM 参考手册 (32)
- HTML 拓展阅读 (30)
- HTML文本框样式 (31)
- HTML滚动条样式 (34)
- HTML5 浏览器支持 (33)
- HTML5 新元素 (33)
- HTML5 WebSocket (30)
- HTML5 代码规范 (32)
- HTML5 标签 (717)
- HTML5 标签 (已废弃) (75)
- HTML5电子书 (32)
- HTML5开发工具 (34)
- HTML5小游戏源码 (34)
- HTML5模板下载 (30)
- HTTP 状态消息 (33)
- HTTP 方法:GET 对比 POST (33)
- 键盘快捷键 (35)
- 标签 (226)
- HTML button formtarget 属性 (30)
- opacity 属性 (32)
- transition 属性 (33)