【数据结构】空间复杂度

目录

一、引入空间复杂度的原因

二、空间复杂度的分析

❥ 2.1 程序运行时内存大小 ~ 程序本身大小

❥ 2.2 程序运行时内存大小 ~ 算法运行时内存大小

❥ 2.3 算法运行时内存大小

❥ 2.4 不考虑算法全部运行空间的原因

三、空间复杂度

❥ 3.1空间复杂度的定义

❥ 3.2 空间复杂度不能准确算出空间大小的原因

❥ 3.3 最关注差空间复杂度的原因

四、空间复杂度的计算

五、常见空间复杂度举例

❥ 5.1 常数阶

❥ 5.2 线性阶

❥ 5.3 平方阶

六、递归与非递归空间复杂度

❥ 6.1 常规函数空间复杂度不重点算栈空间大小的原因

❥ 6.2 递归函数空间复杂度需要算栈空间大小的原因

七、权衡时间与空间效率


一、引入空间复杂度的原因

  1. 我们知道,一个算法的好坏主要从算法的执行时间和所需要占用的存储空间这两个方面来进行衡量。
  2. 当一个算法在执行过程中存储数据所需要占用的内存空间的大小,就是空间复杂度。它和时间复杂度一样,是衡量算法性能的重要指标之一。
  3. 在开发程序之前,分析算法的空间复杂度有助于开发者提前预估程序运行时所需的内存空间,从而合理地规划硬件资源。

二、空间复杂度的分析

❥ 2.1 程序运行时内存大小 ~ 程序本身大小

首先我们先弄清楚什么是程序本身内存的大小,什么是程序运行时内存的大小?有什么关系?

  1. 程序本身内存的大小:指的是程序文件存储在磁盘等存储设备上所占用的存储空间大小。它主要取决于程序的源代码经过编译、链接等操作后生成的可执行文件所包含的内容,包括代码段、数据段等。
  2. 程序运行时内存的大小:指的是程序在计算机内存中运行时所占用的存储空间的量。它涉及到程序运行过程中各个方面对内存的使用,包括代码区、数据区(如全局变量、静态变量、常量)、栈区(存储函数调用信息和局部变量)和堆区(用于动态内存分配)等所占用的内存总和。

程序运行时的内存大小是包含程序本身的大小的。

  • 通俗来讲,程序本身的大小好比一颗种子,而程序运行的大小就像生长后的植株。
  • 程序本身就像一颗种子,其大小是固定的,蕴含着生长的潜力和信息。
  • 当种子种下并开始生长后,就如同程序开始运行。当种子长成大树后,会有粗壮的树干和茂密的枝叶,需要占据很大的空间。这就像程序运行时,会在系统中展开庞大的运行架构,占用大量的内存、CPU 等资源,其运行时占据的 “空间” 和要比程序本身所占用的大得多,也有可能会随着业务的发展和数据量的增加不断扩展。

❥ 2.2 程序运行时内存大小 ~ 算法运行时内存大小

程序运行时内存大小和算法运行时内存大小有什么关系?

程序是一个更为宽泛的概念,它由多个部分组成,算法只是程序实现特定功能的核心逻辑。程序运行时,其内存占用除了算法运行所需的内存外,还包括其他诸多方面。算法运行的内存主要用于存储算法执行过程中使用的数据结构、中间变量、递归调用栈等。而程序运行内存还包含程序代码本身占用的空间(代码段)、全局和静态变量占用的空间(数据段)、程序与外部交互的输入输出缓冲区、加载的库文件所占用的内存等。

在一些非常简单的程序中,如果程序的主要功能就是执行一个单一的算法,且没有其他复杂的功能模块、全局变量、输入输出操作等,那么算法运行的内存大小可能与程序运行内存大小非常接近。例如,一个简单的控制台程序,其唯一的功能就是实现一个简单的递归算法计算斐波那契数列,此时程序运行内存主要就是算法运行所需的内存。

但在大多数实际应用中,程序运行时内存的大小通常会大于算法运行时内存的大小。

❥ 2.3 算法运行时内存大小

算法运行时内存大小主要分为以下几个部分:

输入数据空间:存储算法的输入数据

输出数据空间:存储算法的输出数据

暂存数据空间:用于存储算法在运行过程中的变量、对象、函数上下文等数据

暂存数据空间又可以分为:

  • 暂存数据:保存算法运行过程中的各种常量、变量、对象等
  • 栈帧空间:保存调用函数的上下文数据
  • 指令空间:保存编译后的程序指令,但在统计空间复杂度时通常忽略不计

空间复杂度通常统计的是暂存数据栈帧空间

❥ 2.4 不考虑算法全部运行空间的原因

  • 输入数据空间:

输入数据所占用的空间通常不纳入空间复杂度的考量范围。因为输入数据是算法处理的对象,它的规模是由问题本身决定的,并非算法为完成任务而额外使用的空间。

例:对一个包含n个元素的数组进行排序,数组本身占用的存储空间与算法的空间使用效率并无直接关联,所以不包含在空间复杂度的计算中。

  • 程序代码空间:

程序代码本身所占用的存储空间也不影响算法的空间复杂度。代码大小是固定的,不随输入规模的变化而变化,它与算法在处理不同规模输入时的内存需求增长特性没有直接联系。无论输入规模如何,程序代码的空间占用都是恒定的,因此不将其纳入空间复杂度的计算。

  • 输出数据空间:

通常而言,输出数据一般不计入空间复杂度。像常规算法的单一返回值,或是排序、查找算法的输出结果,其占用空间固定或由输入决定,不反映额外开销,可不考虑。

但当输出规模与输入紧密相关、或优化目标是输出空间时,则可能计入。

因为空间复杂度更专注于算法本身的逻辑实现对存储空间的需求,即专注于为了完成算法任务,输入数据本身所占空间之外,算法额外需要的辅助空间大小。所以空间复杂度不考虑算法全部运行空间。

三、空间复杂度

❥ 3.1空间复杂度的定义

空间复杂度(Space Complexity)也是一个数学表达式,是对一个算法在运行过程中临时占用存储空间大小的量度。

注意:函数运行时所需要的栈空间(存储参数、局部变量、一些寄存器信息等)在编译期间已经确定好了,因此空间复杂度主要通过函数在运行时候显式申请的额外空间来确定。

❥ 3.2 空间复杂度不能准确算出空间大小的原因

  1. 空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度,是一种渐近分析,使用大O表示法描述算法在问题规模趋于无穷大时,额外存储空间随问题规模增长的趋势而不是精确的内存使用量。
  2. 此外,程序运行时所占用的内存还受到很多与算法本身无关的因素影响,不同的计算机硬件环境(如内存架构、字长等)和软件环境(如操作系统的内存管理机制、编译器的优化程度等)会导致同一算法在不同环境下的实际内存占用有所不同。这会影响程序的实际内存使用,但空间复杂度分析通常不会考虑这些具体环境因素。

❥ 3.3 最关注差空间复杂度的原因

与时间复杂度不同的是,一般情况下,我们只关注最差空间复杂度,因为内存空间是有限的,我们要确保在所有输入数据下都有足够的内存空间。

四、空间复杂度的计算

空间复杂度直接计算变量个数即可,不需要程序占用了多少字节的空间。

计算变量个数的原因:

因为空间复杂度计算规则基本跟时间复杂度类似,也是使用大O渐进表示法来表示空间复杂度。

我们知道,大O渐进表示法表示的是估算值,而不是真实值,主要目的是为了算出空间复杂度属于哪个量级。

举例:

  • 一个int型变量,占用空间大小是4bit,4的大小是属于常数阶的,那么它的空间复杂度为O(1)
  • n个int型变量的数组,占用的空间大小是4nbit,4n的大小是属于线性阶的,那么它的空间复杂度为O(N)

所以说,为了方便计算空间复杂度的大小,我们可以直接计算变量个数。

五、常见空间复杂度举例

❥ 5.1 常数阶

常数阶的空间复杂度为:O(1)

  • 当空间复杂度为O(1)的情况下,也称算法为原地工作或者就地工作
  • 原地工作(就地工作):指的是算法的执行过程中不使用额外的存储空间,或者使用的额外空间相对输入数据量是常数。即所使用的额外空间量不随问题规模(即输入数据的大小)的变化而变化。

例题1:

//计算BubbleSort的空间复杂度
void BubbleSort(int* a, int n)
{assert(a);for (size_t end = n; end > 0; --end){int exchange = 0;for (size_t i = 1; i < end; ++i){if (a[i - 1] > a[i]){Swap(&a[i - 1], &a[i]);exchange = 1;}}if (exchange == 0)break;}
}

这段冒泡排序的代码中,分别有end,i,exchange这3个新的变量出现,也就是为这3个变量开辟了空间,因为3是常数,所以冒泡排序的空间复杂度为:O(1)

例题2:

void fun()
{return 0;
}void tmp(int n)
{int i = 0;for (i = 0; i < n; i++){fun();}}

注意:在循环中初始化变量或调用函数而占用的内存,在进入下一循环后就会被释放,因此不会累积占用空间,空间复杂度仍为 𝑂(1)

❥ 5.2 线性阶

线性阶的空间复杂度为:O(N)

例题:

// 计算阶乘递归Factorial的空间复杂度
long long Fac(int N)
{if (N == 0)return 1;return Fac(N - 1) * N;
}

Fac递归函数Fac用于计算阶乘。

函数递归调用的深度为N,开辟了N个栈帧,每个栈帧使用了常数个空间。所以空间复杂度为O(N)

❥ 5.3 平方阶

平方阶的空间复杂度为:O(N^2)

例题:

long long Fac(int N)
{if (N == 0)return 1;int arr[N];return Fac(N - 1) * N;
}

在每个递归函数中都初始化了一个数组,总长度为1+2+...+(N-1)+ N = N(N+1) / 2

因为空间复杂度算的是所处的量级,所以是O(N^2)

六、递归与非递归空间复杂度

❥ 6.1 常规函数空间复杂度不重点算栈空间大小的原因

编译阶段,编译器会根据函数的定义,分析函数中的参数、局部变量以及可能保存的寄存器信息等,从而确定函数调用时一个栈帧所需要的空间大小和布局。

例:


int add(int a, int b)
{int result;result = a + b;return result;
}

编译器知道需要为两个int类型的参数a,b,一个int类型的局部变量result,以及返回地址等分配空间。

这是对单个栈帧而言的,是一个固定的模式,不随输入规模或函数执行过程发生显著变化。在大O表示法这种渐进分析中,这部分固定大小的空间被视为常数项。

空间复杂度主要关注的是随着问题规模的增长,算法所需空间的增长趋势。对于常规函数,显式申请的额外空间,如动态分配的数组或对象等,才是随着问题规模可能呈线性、对数等不同趋势增长的部分,是影响空间复杂度的关键因素。

❥ 6.2 递归函数空间复杂度需要算栈空间大小的原因

栈在编译期间确定的是单个栈帧的空间布局,但在递归函数运行时会不断产生新的栈帧。

  • 在编译时,无法确定递归函数会被调用多少次,只有在程序运行时,随着递归函数的不断调用和返回,才会实际地在栈上创建和释放栈帧,从而动态地占用和释放栈空间。
  • 栈帧数量与递归深度直接相关,而递归深度往往与问题规模有关

例:在5.2的递归计算阶乘函数,递归深度与输入的n成正比,每一层递归都要占用一定栈空间存储当前层的参数、局部变量等,所以栈空间占用会随着递归深度增加而显著增加。

总言之,递归栈空间中单个栈帧的结构和大小在编译时期是可以确定的,但递归过程中栈空间的实际创建和使用是在运行时动态进行的,并不是完全在编译时期就确定好的。空间复杂度关注的是运行时内存的大小。

七、权衡时间与空间效率

  • 实际情况下,算法的时间效率和空间效率同时达到最优非常困难。
  • 通常情况下,我们不太关注算法的空间效率,更注重时间效率。
  • 但是,像互联网、嵌入式等行业对空间效率也是较为注重的。
  • 所以,选择优化时间复杂度或者空间复杂度取决于我们的侧重方面。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/9016.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

实践网络安全:常见威胁与应对策略详解

&#x1f4dd;个人主页&#x1f339;&#xff1a;一ge科研小菜鸡-CSDN博客 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; 引言 在数字化转型的浪潮中&#xff0c;网络安全的重要性已达到前所未有的高度。无论是个人用户、企业&#xff0c;还是政府机构…

Tensor 基本操作2 理解 tensor.max 操作,沿着给定的 dim 是什么意思 | PyTorch 深度学习实战

前一篇文章&#xff0c;Tensor 基本操作1 | PyTorch 深度学习实战 本系列文章 GitHub Repo: https://github.com/hailiang-wang/pytorch-get-started 目录 Tensor 基本操作torch.max默认指定维度 Tensor 基本操作 torch.max torch.max 实现降维运算&#xff0c;基于指定的 d…

图像处理之HSV颜色空间

目录 1 RGB 的局限性 2 HSV 颜色空间 3 RGB与HSV相互转换 4 HSV颜色模型对图像的色相、饱和度和明度进行调节 5 演示Demo 5.1 开发环境 5.2 功能介绍 5.3 下载地址 参考 1 RGB 的局限性 RGB 是我们接触最多的颜色空间&#xff0c;由三个通道表示一幅图像&#xff0c;分…

数据结构题目 课时9

题目 1、任何一个带权的无向连通图的最小生成树&#xff08; &#xff09;。 A. 只有一棵 B. 有一棵或多棵 C. 一定有多棵 D. 可能不存在 2、一个赋权网络如下图所示。从顶点 a 开始&#xff0c;用 Prim 算法求出一棵最小生成树。 3、请对下图的无向带权图按克鲁斯卡尔算法求…

Linux之详谈——权限管理

目录 小 峰 编 程 ​编辑 一、权限概述 1、什么是权限 2、为什么要设置权限 3、Linux中的权限类别- 4、Linux中文件所有者 1&#xff09;所有者分类&#xff08;谁&#xff09; 2&#xff09;所有者的表示方法 ① u(the user who owns it)&#xff08;属主权限&…

私有包上传maven私有仓库nexus-2.9.2

一、上传 二、获取相应文件 三、最后修改自己的pom文件

记录 | 基于Docker Desktop的MaxKB安装

目录 前言一、MaxKBStep 1Step2 二、运行MaxKB更新时间 前言 参考文章&#xff1a;如何利用智谱全模态免费模型&#xff0c;生成大家都喜欢的图、文、视并茂的文章&#xff01; MaxKB的Github下载地址 参考视频&#xff1a;【2025最新MaxKB教程】10分钟学会一键部署本地私人专属…

4.flask-SQLAlchemy,表Model定义、增删查改操作

介绍 SQLAlchemy是对数据库的一个抽象 开发者不用直接与SQL语句打交道 Python对象来操作数据库 SQLAlchemy是一个关系型数据库 安装 flask中SQLAlchemy的配置 from flask import Flask from demo.user_oper import userdef create_app():app Flask(__name__)# 使用sessi…

jemalloc 5.3.0的tsd模块的源码分析

一、背景 在主流的内存库里&#xff0c;jemalloc作为android 5.0-android 10.0的默认分配器肯定占用了非常重要的一席之地。jemalloc的低版本和高版本之间的差异特别大&#xff0c;低版本的诸多网上整理的总结&#xff0c;无论是在概念上和还是在结构体命名上在新版本中很多都…

【Elasticsearch】Elasticsearch的查询

Elasticsearch的查询 DSL查询基础语句叶子查询全文检索查询matchmulti_match 精确查询termrange 复合查询算分函数查询bool查询 排序分页基础分页深度分页 高亮高亮原理实现高亮 RestClient查询基础查询叶子查询复合查询排序和分页高亮 数据聚合DSL实现聚合Bucket聚合带条件聚合…

DeepSeek R1有什么不同

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

macbook安装go语言

通过brew来安装go语言 使用brew命令时&#xff0c;一般都会通过brew search看看有哪些版本 brew search go执行后&#xff0c;返回了一堆内容&#xff0c;最下方展示 If you meant "go" specifically: It was migrated from homebrew/cask to homebrew/core. Cas…

文本左右对齐

hello 大家好&#xff01;今天开写一个新章节&#xff0c;每一天一道算法题。让我们一起来学习算法思维吧&#xff01; function fullJustify(words, maxWidth) {// 用于存储最终排版好的每一行文本const result [];// 用于遍历单词数组的索引&#xff0c;初始化为 0let i 0;…

Oracle 创建用户和表空间

Oracle 创建用户和表空间 使用sys 账户登录 建立临时表空间 --建立临时表空间 CREATE TEMPORARY TABLESPACE TEMP_POS --创建名为TEMP_POS的临时表空间 TEMPFILE /oracle/oradata/POS/TEMP_POS.DBF -- 临时文件 SIZE 50M -- 其初始大小为50M AUTOEXTEND ON -- 支持…

树状数组讲解

文章目录 1395.统计作战单位数 树状数组b站博主 灵神博主 tree数组&#xff1a;Tree[i] 存储的是原本的数组中num[i - (i&-i)1]到nums[i]的和 更新的时候&#xff0c;num[i[更新&#xff0c;逐一修改num[i(i & -i)] 307.区间和检索-数组可修改 题目实战 总的代码&#…

PostGIS笔记:PostgreSQL中表、键和索引的基础操作

创建、查看与删除表 在数据库中创建一个表&#xff0c;使用如下代码&#xff1a; create table streets (id serial not null primary key, name varchar(50));这里的表名是streets&#xff0c;id是主键所以非空&#xff0c;采用serial数据类型&#xff0c;这个数据类型会自动…

【JavaEE进阶】图书管理系统 - 壹

目录 &#x1f332;序言 &#x1f334;前端代码的引入 &#x1f38b;约定前后端交互接口 &#x1f6a9;接口定义 &#x1f343;后端服务器代码实现 &#x1f6a9;登录接口 &#x1f6a9;图书列表接口 &#x1f384;前端代码实现 &#x1f6a9;登录页面 &#x1f6a9;…

大数据学习之SCALA分布式语言三

7.集合类 111.可变set一 112.可变set二 113.不可变MAP集合一 114.不可变MAP集合二 115.不可变MAP集合三 116.可变map一 package com . itbaizhan . chapter07 //TODO 2. 使用 mutable.Map 前导入如下包 import scala . collection . mutable // 可变 Map 集合 object Ma…

C++:多继承习题3

题目内容&#xff1a; 声明一个时间类Time&#xff0c;时间类中有3个私有数据成员(Hour&#xff0c;Minute&#xff0c;Second)和两个公有成员函数(SetTime和PrintTime)。要求&#xff1a; &#xff08;1&#xff09; SetTime根据传递的3个参数为对象设置时间&#xff1b; &a…

14-6-2C++STL的list

(一&#xff09;list对象的带参数构造 1.list&#xff08;elem);//构造函数将n个elem拷贝给本身 #include <iostream> #include <list> using namespace std; int main() { list<int> lst(3,7); list<int>::iterator it; for(itlst.begi…