数据结构——时间复杂度VS空间复杂度

本文详细介绍了算法的时间复杂度和空间复杂度概念及其重要性。通过对比两种复杂度,探讨了它们之间的区别和联系,并以万年历程序为例说明了如何在实际应用中寻找最优平衡。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我们在这篇博客中主要来介绍一下时间复杂度和空间复杂度的区别和联系。我们也将通过一个万年历的例子来分析二者如何平衡,如何取舍。

虽然随着科学技术的发展,计算机硬件的性能已经发生了天翻地覆的变化。我们再也不用像计算机前辈们那样为了几M,几KB,甚至几字节而反复斟酌修改我们的程序。但是,如何优化算法,如何降低空间复杂度,如何降低时间复杂度,以及如何在时间复杂度和空间复杂度之间取得平衡,却是我们永恒不变的话题。从中我们可以窥其精髓,体其妙处,收获快乐。

一、时间复杂度

定义:

在计算机科学中,算法的时间复杂度是一个函数,它定量描述了该算法的运行时间。

时间复杂度度量方法(推导大O阶的方法步骤):

  1. 用常数1取代运行时间中的所有加法常数。
  2. 在修改后的运行次数函数中,只保留最高阶项。
  3. 如果最高阶项存在且系数不为1,则去除与这个项相乘的系数,得到的结果就是大O阶。

常见的时间复杂度:

算法执行次数的函数大O阶非正式术语
12O(1)常数阶
2n+3O(n)线性阶
3n2+2n+1O(n2)平方阶
5log2n+20O(logn)对数阶
2n+3nlog2n+19O(nlogn)nlog2n
6n3+2n2+3n+4O(n3)立方阶
2no(2n)指数阶

时间复杂度从小到大为(越小越好):

O(1)<O(logn)<O(n)<O(nlogn)<O(n2)<O(n3)<O(2n)<O(n!)<O(nn)

最坏时间复杂度是应用运行的保证,而平均时间复杂度则是最有意义的,因为它是期望的运行时间。

二、空间复杂度

定义:

一个程序的空间复杂度是指运行完一个程序所需内存的大小。利用程序的空间复杂度,可以对程序的运行所需要的内存多少有个预先估计。一个程序执行时除了需要存储空间和存储本身所使用的指令、常数、变量和输入数据外,还需要一些对数据进行操作的工作单元和存储一些为现实计算所需信息的辅助空间。程序执行时所需存储空间包括以下两部分。
(1)固定部分。这部分空间的大小与输入/输出的数据的个数多少、数值无关。主要包括指令空间(即代码空间)、数据空间(常量、简单变量)等所占的空间。这部分属于静态空间。
(2)可变空间,这部分空间的主要包括动态分配的空间,以及递归栈所需的空间等。这部分的空间大小与算法有关。

三、时间复杂度VS空间复杂度:

我们首先来看一下,什么是算法复杂度:

算法复杂度分为时间复杂度和空间复杂度。其作用: 时间复杂度是指执行算法所需要的计算工作量;而空间复杂度是指执行这个算法所需要的内存空间。(算法的复杂性体现在运行该算法时的计算机所需资源的多少上,计算机资源最重要的是时间和空间(即寄存器)资源,因此复杂度分为时间和空间复杂度)。

就目前来说,除了在一些特殊情况下,我们都是更加注重时间复杂度,而不是空间复杂度。注意,这里我们强调了,除了一些特殊情况外,有些特殊情况下,空间复杂度可能会更加重要。

那么,究竟什么时候应该着重考虑时间复杂度,什么时候应该着重考虑空间复杂度呢?我们来看一个例子:

设想现在需要由你来完成一个程序设计,程序要求是这样的:要求输入年份,返回该年份是否是闰年。

一提到这个问题,我想如果你学习过任何一门语言,你可能都做过类似的题目。你可能思路已经非常清晰了,满百除四百,不满除以4。

额,先不要急。我们来看看还能不能进一步提高性能,降低时间复杂度。也就是用空间复杂度来换取时间复杂度。比如,如果使用我们程序的用户,只会查看当前年份未来几年和过去几年的日历的话,我们完全可以使用一个比如:2100个元素的数组,每个元素为0或1,分别表示平年和闰年。这样当用户查询的时候,就不需要再进行复杂的逻辑判断,而只需要取出对应下标位置的元素即可。
反过来,如果我们的用户经常查询跨度上万年的日历信息(万年历),那么,我们肯定不能使用上面牺牲空间复杂度来换取时间复杂度的方案解决。因为如此巨大的空间消耗是我们损失不起的。

而,编程的精髓和美,并不在于一方的退让和妥协。而是在于如何在二者之间取一个平衡点,完成华丽变身。那么,对于我们这种程序应该如何权衡呢?

我想到的一种方案是:将与当前年份相近的几年存为固定数据,查询时只需要读取即可。而对于那些和当前年份相距较远的年份的数据,在用户请求查询时动态生成。

这样,既能在损失可接受空间的情况下,大幅度提高性能,又能保证空间的损失不至于太大而无法接受。我想当用户查询据今较远的数据时,有一些时间上的等待,也是可以接受的。

### 计算数据结构时间复杂度 对于时间复杂度而言,其核心在于评估算法执行过程中基本操作的频率。具体来说,通过分析算法中每条语句被执行的次数来确定整个过程所需的相对时间开销[^1]。 #### 时间复杂度计算方法 为了简化这一过程,可以采用如下策略: - **忽略低次项**:当存在多项式表达形式时,仅保留最高幂次部分作为主导因素。 - **常数因子省略**:即使某些情况下有固定倍率影响整体耗时,但在比较不同规模下的增长趋势时不考虑这些系数差异。 例如,在快速排序算法里,平均状况下每次分割都能大致均分数组,则递归调用树高度大约为`log₂N`层;每一层涉及遍历当前子序列全部元素的操作,故总代价近似于`N * log₂N`级别[^5]。 ```python def quick_sort(arr): if len(arr) <= 1: return arr else: pivot = arr[len(arr) // 2] left = [x for x in arr if x < pivot] middle = [x for x in arr if x == pivot] right = [x for x in arr if x > pivot] return quick_sort(left) + middle + quick_sort(right) ``` 上述实现展示了经典的快速排序逻辑框架,其最佳情形最差情形分别对应着不同的渐进性能特征——理想状态下能够达到O(n log n),而在极端分布模式(如已有序列)下降至O(n²)。 ### 数据结构空间复杂度解析 空间复杂度关注的是存储需求随着输入尺寸扩大而产生的变动规律。这不仅涵盖了显式的变量声明占用量,还包括间接由函数调用栈帧、动态分配对象等引起的额外消耗[^4]。 #### 常见场景及其对应的复杂度等级 | 场景 | 描述 | | --- | --- | | `O(1)` | 不论处理多少数量的数据集始终维持恒定内存足迹,像简单的整型计数器或是单个指针引用就属于此类别[^2]。| | `O(n)` | 当前工作区内的临时实体数目与待解决问题实例呈线性关联,典型例子包括链表节点创建以及基于索引访问的一维向量复制操作。| 以堆栈为例说明后者特性:每当遇到新的括号匹配任务时便新增一层记录环境直至完成配对检验为止,期间累积使用的辅助单元总数显然依赖于原始字符串长度参数n。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值