【数据结构】空间复杂度

目录

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

二、空间复杂度的分析

❥ 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成正比,每一层递归都要占用一定栈空间存储当前层的参数、局部变量等,所以栈空间占用会随着递归深度增加而显著增加。

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

七、权衡时间与空间效率

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[Java]快速入门

java是什么 Java是美国的sun 公司(Stanford University Network)在1995年推出的一门计算机高级编程语言 sun公司于2009年被Oracle(甲骨文)公司收购。 普遍认同lava的联合创始人之一: 詹姆斯高斯林(James Gosling)为Java之父。 Java是世界上最流行的编程语言之一&#xff0c;…

数据分析系列--②RapidMiner导入数据和存储过程

一、下载数据 点击下载AssociationAnalysisData.xlsx数据集 二、导入数据 1. 在本地计算机中创建3个文件夹 2. 从本地选择.csv或.xlsx 三、界面说明 四、存储过程 将刚刚新建的过程存储到本地 Congratulations, you are done.

【源码+文档+调试讲解】基于springboot的高校实验室预约系统

摘 要 高校实验室预约系统是一款专为高等教育机构设计的在线管理工具&#xff0c;旨在简化实验室资源的分配和使用。通过该系统&#xff0c;学生和教师可以轻松查看实验室的空闲时间&#xff0c;并进行实时预约。系统支持不同用户权限设置&#xff0c;确保资源合理分配&#x…

MIMIC-IV数据部署(博主较忙,缓慢更新)

1. 用到的数据准备 在下面的网站&#xff0c;注册、申请、推荐人从邮箱里帮忙确认。 通过后&#xff0c;拉到页面的最下面。把那个将近10个G的文件给下载下来。 可以在晚上睡觉的时候下载&#xff0c;第二天早上起来“收数据”。 MIMIC-IV v3.1 2. 用到的软件准备 7-zip …

6. 使用springboot做一个音乐播放器软件项目【1.0版项目完结】附带源码~

#万物OOP 注意&#xff1a; 本项目只实现播放音乐和后台管理系统。 不分享任何音乐歌曲资源。 上一篇文章我们 做了音乐播放器后台的功能。参考地址&#xff1a; https://jsonll.blog.csdn.net/article/details/145214363 这个项目已经好几天也没更新了&#xff0c;因为临近放…

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…

装机爱好者的纯净工具箱

对于每一位电脑用户来说&#xff0c;新电脑到手后的第一件事通常是检测硬件性能。今天为大家介绍一款开源且无广告的硬件检测工具——入梦工具箱。 主要功能 硬件信息一目了然 打开入梦工具箱&#xff0c;首先看到的是硬件信息概览。这里不仅包含了内存、主板、显卡、硬盘等常…

数据分析系列--③RapidMiner算子说明及数据预处理

一、算子说明 1 新建过程 2 算子状态灯 状态灯说明: (1)状态指示灯&#xff1a; 红色:指示灯说明有参数未被设置或输入端口未被连接等问题; 黄色:指示灯说明还未执行算子&#xff0c;不管配置是否基本齐全; 绿色:指示灯说明一切正常&#xff0c;已成功执行算子。 (2)三角…

PVE 虚拟机安装 Debian 无图形化界面服务器

Debian 安装 Debian 镜像下载 找一个Debian镜像服务器&#xff0c;根据需要的版本和自己硬件选择。 iso-cd/&#xff1a;较小&#xff0c;仅包含安装所需的基础组件&#xff0c;可能需要网络访问来完成安装。有镜像 debian-12.9.0-amd64-netinst.isoiso-dvd/&#xff1a;较…

操作系统指定用户密码永不过期

背景 实际生产环境中&#xff0c;数据中心操作系统通常会有基线要求&#xff08;比如等保之类&#xff09;&#xff0c;要求设置操作系统密码有效期&#xff0c;但是infra团队或者操作系统管理员或者某些业务配置使用的操作系统用户又需要密码不能不停修改&#xff08;或者说一…

npm:升级自身时报错:EBADENGINE

具体报错信息如下&#xff1a; 1.原因分析 npm和当前的node版本不兼容。 // 当前实际版本: Actual: {"npm":"10.2.4","node":"v20.11.0"}可以通过官网文档查看与自己 node 版本 兼容的是哪一版本的npm&#xff0c;相对应进行更新即可…

解决报错“The layer xxx has never been called and thus has no defined input shape”

解决报错“The layer xxx has never been called and thus has no defined input shape”(这里写自定义目录标题) 报错显示 最近在跑yolo的代码时遇到这样一个错误&#xff0c;显示“the layer {self.name} has never been called”.这个程序闲置了很久&#xff0c;每次一遇到…

【图文详解】lnmp架构搭建Discuz论坛

安装部署LNMP 系统及软件版本信息 软件名称版本nginx1.24.0mysql5.7.41php5.6.27安装nginx 我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新功能,帮助你用它写博客: 关闭防火墙 systemctl stop firewalld &&a…

基于物联网的火灾报警器设计与实现(论文+源码)

1 总体方案设计 本次基于物联网的火灾报警器&#xff0c;其系统总体架构如图2.1所示&#xff0c;采用STM32f103单片机作为控制器&#xff0c;通过DS18B20传感器实现温度检测&#xff1b;通过MQ-2烟雾传感器实现烟雾检测&#xff1b;.通过火焰传感器实现火焰检测&#xff0c;当…

记录 | MaxKB创建本地AI智能问答系统

目录 前言一、重建MaxKBStep1 复制路径Step2 删除MaxKBStep3 创建数据存储文件夹Step4 重建 二、创建知识库Step1 新建知识库Step2 下载测试所用的txtStep3 上传本地文档Step4 选择模型补充智谱的API Key如何获取 Step5 查看是否成功 三、创建应用Step1 新建应用Step2 配置AI助…

机器学习 - 初学者需要弄懂的一些线性代数的概念

一、单位矩阵 在数学中&#xff0c;单位矩阵是一个方阵&#xff0c;其主对角线上的元素全为1&#xff0c;其余元素全为0。单位矩阵在矩阵乘法中起到类似于数字1在数值乘法中的作用&#xff0c;即任何矩阵与单位矩阵相乘&#xff0c;结果仍为原矩阵本身。 单位矩阵的定义&…

FPGA 使用 CLOCK_LOW_FANOUT 约束

使用 CLOCK_LOW_FANOUT 约束 您可以使用 CLOCK_LOW_FANOUT 约束在单个时钟区域中包含时钟缓存负载。在由全局时钟缓存直接驱动的时钟网段 上对 CLOCK_LOW_FANOUT 进行设置&#xff0c;而且全局时钟缓存扇出必须低于 2000 个负载。 注释&#xff1a; 当与其他时钟约束配合…

React第二十六章(createPortal)

createPortal 注意这是一个API&#xff0c;不是组件&#xff0c;他的作用是&#xff1a;将一个组件渲染到DOM的任意位置&#xff0c;跟Vue的Teleport组件类似。 用法 import { createPortal } from react-dom;const App () > {return createPortal(<div>小满zs<…

文献阅读 250128-Tropical forests are approaching critical temperature thresholds

Tropical forests are approaching critical temperature thresholds 来自 <Tropical forests are approaching critical temperature thresholds | Nature> 热带森林正在接近临界温度阈值 ## Abstract: The critical temperature beyond which photosynthetic machinery…

RubyFPV开源代码之系统简介

RubyFPV开源代码之系统简介 1. 源由2. 工程架构3. 特性介绍&#xff08;软件&#xff09;3.1 特性亮点3.2 数字优势3.3 使用功能 4. DEMO推荐&#xff08;硬件&#xff09;4.1 天空端4.2 地面端4.3 按键硬件Raspberry PiRadxa 3W/E/C 5. 软件设计6. 参考资料 1. 源由 RubyFPV以…