数据结构C语言版 —— 时间复杂度空间复杂度概念和计算

news2025/1/19 19:28:52

文章目录

  • 时间复杂度&空间复杂度
    • 1. 算法效率
    • 2. 时间复杂度
      • 1) 时间复杂度的概念
      • 2) 大O的渐近表示法
      • 3) 时间复杂度案例举例
    • 3. 空间复杂度
      • 1) 空间复杂度概念
      • 2) 计算实例


时间复杂度&空间复杂度

1. 算法效率

算法效率分析一般分为两种,一种是时间效率,另外一种是空间效率。时间效率被称为时间复杂度,空间效率则被称为空间复杂度。时间复杂度是用来衡量一个算法的运行速度,而空间复杂度主要是用来衡量一个算法的所需要的额外空间,早期的计算机存储容量很小,所以比对空间复杂度很是在乎。但是随着计算机的叙述发展,计算机的存储已经到了一个很高的程度,比如现在的一台笔记本至少都是16G内存+512G磁盘,服务甚至是几百个G的内存,几百T的磁盘。所以现在并不那么关心空间复杂度,也经常出现空间换时间的做法。

2. 时间复杂度

1) 时间复杂度的概念

时间复杂度的一个定义:在计算机科学中,算法的时间复杂度是一个函数(一个数学函数),它定量描述了一个算法执行所消耗的时间,从理论上来说,是不能计算出来的,只有当你把代码放到机器上跑才能知道运行时间,但是通过机器测试这种方式明显不现实,因为每台计算机的配置都不一定相同,所以才有了时间复杂度的分析方式。一个算法所花费的时间与其语句的执行次数成正比,算法中的基本操作的执行次数,为算法的时间复杂度

2) 大O的渐近表示法

来看一段代码

void func1(int N)
{
    int count = 0;
    int i = 0;
    for (i = 0; i < N ; i++) //执行N次
    {
        for (int j = 0; j < N ; j++) //执行N次
        {
            count++;
        }
    }
    int k = 0;
    for (k = 0; k < 2 * N ; k++) //执行2*N次
    {
        count++;
    }
    int M = 10;
    while (M--) //执行10次
    {
        count++;
    }
    printf("%d\n",count);
}

那么我们来计算一下这个代码的运行次数就是

f ( N ) = N 2 + 2 ∗ N + 10 f(N) = N^{2}+2*N+10 f(N)=N2+2N+10

但实际我们计算时间复杂度时,并不一定要计算的那么精确,而是只计算大概的执行次数.

我看到func1的执行次数,如果当我们的N非常大时,假设N = 1000,那么这里的+10是可以忽略了,因为 100 0 2 = 1000000 1000^{2}=1000000 10002=1000000,在一百万面前+10可以说是微乎其微了,所以+1和+10没什么区别。同理 2 ∗ N 2*N 2N也是一样的,当N足够大趋近于无穷时, 2 ∗ N 2*N 2N也时微乎其微了。

那么就可以使用大O的渐近表示法

O O O符号:是用于描述函数渐进行为的数学符号

推到大 O O O阶方法

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

通过上面的方法来推导一下

用常数1取代运行时间汇总的所有加法常数

f ( N ) = N 2 + 2 ∗ N + 1 f(N) = N^{2}+2*N+1 f(N)=N2+2N+1

在修改后的运行次数函数中,只保留最高阶项

f ( N ) = N 2 f(N) = N^{2} f(N)=N2

这里的最高阶项不是1,所以func1函数的时间复杂度就是** O ( N 2 ) O(N^{2}) O(N2)**

O O O渐进表示法去掉了那些对结果影响不大的项数,只保留了最影响结果的那一项

另外有些算法存在着,最好、平均和最坏情况

  • 最坏情况:任意输入规模的最大运行次数(上界)
  • 平均情况:任意输入规模的期望运行次数
  • 最好情况:任意输入规模的最小运行次数(下界)

举个例子:

假设在一个数组中查找一个数字。

  • 最好情况:1次找到,数组第一个数子就是我们要找的, O ( 1 ) O(1) O(1)
  • 最坏情况:最后一个是我们要找的, O ( n ) O(n) O(n)
  • 平均情况:注意:这里的平均情况并不是最好和最坏情况相加的平均值,而是我们期望运行的次数,有时候平均情况可能和最好或者是最坏情况一样。

我们平常嘴上所说的时间复杂度就是最坏情况的时间复杂度

3) 时间复杂度案例举例

实例1

void Func2(int N)
{
    int count = 0;
    for (int k = 0; k < 2 * N ; ++ k)
    {
    	++count;
	}
    int M = 10;
    while (M--)
    {
        ++count;
    }
    printf("%d\n", count);
}

粗略计算就是 f ( N ) = 2 ∗ N + 10 f(N) = 2*N+10 f(N)=2N+10

在修改后的运行次数函数中,只保留最高阶项,如果最高阶存在且不是1,则去除与这个项数相乘的常数

那么这个代码的时间复杂度就是** O ( N ) O(N) O(N)**

实例2

void Func3(int N, int M)
{
    int count = 0;
    for (int k = 0; k < M; ++ k)
    {
        ++count;
    }
    for (int k = 0; k < N ; ++ k)
    {
        ++count;
    }
	printf("%d\n", count);
}

这种情况有一点特殊,因为不知到N和M谁大,所以对于这种不确定谁对结果的影响大,就都需要保留下来。所以这个代码的时间复杂度就是 O ( N + M ) O(N+M) O(N+M)

如果可以确定N远远大于M那么时间复杂度就是 O ( N ) O(N) O(N)

实例3

void Func4(int N)
{
    int count = 0;
    for (int k = 0; k < 100; ++ k)
    {
        ++count;
    }
    printf("%d\n", count);
}

对于这种场数次数的时间复杂度就是 O ( 1 ) O(1) O(1)

实例4

int find(int* arr, int N int key)
{
	assert(arr);
	int i = 0;
	for (i = 0; i < N; i++)
	{
		if (arr[i] == key)
		{
			return i;
		}
	}
	return -1;
}

这个代码是在一个数组中查找一个数字,对于这种情况就是直接取它的最坏情况的时间复杂度,就是 O ( N ) O(N) O(N)

实例5

void BubbleSort(int* arr, int n)
{
    assert(arr);
	int i = 0;
	for (i = 0; i < n-1; i++)//排序趟数
	{
		int flag = 0;
		int j = 0;
		for (j = 0; j < n - 1 - i; j++)//比较次数
		{
			if (arr[j] > arr[j + 1])
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
				flag = 1;
			}
		}
		if (flag == 0)
		{
			break;
		}
	}
    
}

这是冒泡排序的代码,冒泡排序的时间复杂度也是比较特殊的。

冒泡排序的外层循环是排序的趟数,每一趟冒泡排序都会确定一个数字的位置。所以每一趟排序后比较的次数都要减去1。

那么计算的次数就是 ( N − 1 ) + ( N − 2 ) + ( N − 3 ) . . . + 2 + 1 (N-1) + (N-2) + (N-3)... + 2 + 1 (N1)+(N2)+(N3)...+2+1

这是要给等比数列,通过等比数列的前N项和公式得出$\frac{N*(N-1)}{2} $

通过大 O O O渐进法得出冒泡排序的时间负责度就是 O ( N 2 ) O(N^{2}) O(N2)

实例6

void BinarySearch(int* arr, int size, int key)
{
    assert(arr);
	int left = 0;
	int right = size - 1;
	int mid = 0;
	while (left < right)
	{
		mid = (right - left) / 2 + left;
		if (arr[mid] < key)
		{
			left = mid + 1;
		}
		else if (arr[mid] > key)
		{
			right = mid - 1;
		}
		else
		{
			return mid;
		}

	}

	return -1;
    
}

这是一个二分查找的代码,二分查找一次砍掉数组的一半元素。那么查找的次数就是 N / 2 / 2 / 2 / 2... / 2 = 1 N/2/2/2/2.../2 = 1 N/2/2/2/2.../2=1,找了 x x x次,则 / 2 /2 /2 x x x次,那么长度为$N 的 数 组 最 坏 则 要 查 找 次 数 就 是 , 的数组最坏则要查找次数就是, N = 2^{x} , 转 为 对 数 的 形 式 就 是 ,转为对数的形式就是 ,x = \log_{2}{N} $

所以二分查找的时间复杂度就是 O ( log ⁡ 2 N ) O(\log_{2}{N}) O(log2N)

实例7

long long Factorial(size_t N)
{
	return N < 2 ? N : Factorial(N-1)*N;
}

这是一个递归计算N的阶层的代码,这个代码的时间负责度又是多少呢?

递归算法的时间复杂度 = = = 递归次数 * 每次递归函数中的执行的次数

这里的递归次数是 N − 2 N-2 N2,每次递归函数中的执行次数就是1,那么这个代码的时间复杂度就是 O ( N ) O(N) O(N)

实例8

long long Fibonacci(size_t N)
{
	return N < 2 ? N : Fibonacci(N-1)+Fibonacci(N-2);
}

这是递归计算斐波那契数列的函数

在这里插入图片描述

递归计算斐波那契数列,类似于一颗二叉树。假设这一棵二叉树是满二叉树。

那么第一层计算次数就是 2 0 2^{0} 20,第二层就是 2 1 2^{1} 21,第三层就是 2 2 2^{2} 22,以此类推…,那么函数每一次的执行次数就是 f ( N ) = 2 N − 1 f(N) = 2^{N-1} f(N)=2N1,根据等比数列前N项和公式$S_{n}\frac{a_{1}(1-q^{n} ) }{1-q} $,它的准确的时间复杂度就是 2 N − 1 − 空 缺 2^{N}-1-空缺 2N1

通过大 O O O渐进法推导后,这个代码的时间复杂度就是 O ( 2 N ) O(2^{N}) O(2N)

3. 空间复杂度

1) 空间复杂度概念

空间复杂度是衡量一个算法在运行过程中临时占用存储空间大小。空间复杂度不是很细致的计算一个代码所占用多少个字节的空间,而是计算变量的个数。空间复杂度基本和时间复杂度的计算方法类似,也是使用 O O O渐进表示法

2) 计算实例

实例1

void BubbleSort(int* arr, int n)
{
    assert(arr);//1
	int i = 0;//1
	for (i = 0; i < n-1; i++)//排序趟数
	{
		int flag = 0;//1
		int j = 0;//1
		for (j = 0; j < n - 1 - i; j++)//比较次数
		{
			if (arr[j] > arr[j + 1])
			{
				int tmp = arr[j];//1
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
				flag = 1;
			}
		}
		if (flag == 0)
		{
			break;
		}
	}
    
}

这个冒泡排序的空间复杂度为 1 + 1 + 1 + 1 + 1 1+1+1+1+1 1+1+1+1+1

加起来都是常数,所以空间复杂度就是 O ( 1 ) O(1) O(1),因为arr数组是从外面传递过来的,不是我们创建的,所以不算入时间复杂度。那么每次循环创建的变量不需要记录进去吗?因为变量用完就会自动回收的,所以也是不算进去的。

时间复杂度考虑的是算法运行中需要额外创建的空间

实例2

long long* Fibonacci(size_t n)
{
    if(n==0) return NULL;
    long long * fibArray = (long long *)malloc((n+1) * sizeof(long long));
    fibArray[0] = 0;
    fibArray[1] = 1;
    for (int i = 2; i <= n ; ++i)
    {
    	fibArray[i ] = fibArray[ i - 1] + fibArray [i - 2];
    }
    return fibArray ;
}

这里是一个计算斐波那契数列的函数,这里很明显创建了额外的空间。创建了一个大小为n的数组。所以这个代码的空间复杂度为O(N)

实例3

long long Factorial(size_t N)
{
	return N < 2 ? N : Factorial(N-1)*N;
}

这是一个递归求阶乘的代码,每次递归都会在栈上开辟空间,也就是开辟栈帧。一共要开辟 N − 1 N-1 N1层,所以通过大 O O O渐进法推导出这个代码空间复杂度就是 O ( N ) O(N) O(N)


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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Unity初学者Shader Graph教程

Unity初学者Shader Graph教程 了解面向非程序员的 Unity 引擎可视化着色器编程工具的来龙去脉 课程英文名&#xff1a;Your Ultimate Guide to Shader Graph for Beginners 此视频教程共28.0小时&#xff0c;中英双语字幕&#xff0c;画质清晰无水印&#xff0c;源码附件全 …

基于C++实现(WinForm)家谱管理系统【100010033】

⼀、需求分析 《家谱管理系统》程序的设计⽬的&#xff0c;是为了解决中国传统家谱不易保存、不易修改、不易统计的缺陷。利⽤计算机程序&#xff0c;可以实现在计算机上存储、管理、查看家谱的相关信息。 ⽬标功能&#xff1a; 建⽴家谱&#xff1a;在计算机上建⽴树状家谱结…

二叉树算法

写在前面 树的定义 typedef struct Node {int data;struct Node *lchild,*rchild; }Bnode,*Btree;最近公共祖先 已知一棵二叉树按顺序存储结构进行存储&#xff0c;设计一个算法&#xff0c;求编号分别为i和j的两个节点的最近公共祖先节点的值。 算法思想&#xff1a; 顺序…

牛客竞赛每日俩题 - Day9

目录 日期推算 分解因数 日期推算 美国节日__牛客网 思路&#xff1a; 首先&#xff0c;我们要想找到一个月第N个星期W&#xff0c;一定需要一个参照物&#xff0c;最好的目标当然是这个月的第一天。拿到参照物后&#xff0c;我要能得 到参照物的星期数&#xff0c;然后就能…

马来酰亚胺聚乙二醇叠氮,MAL-PEG-N3,Maleimide-PEG-Azide

马来酰亚胺聚乙二醇叠氮&#xff08;MAL-PEG-N3&#xff09;是MeloPEG的硫醇反应性“点击化学”PEG交联剂之一。马来酰亚胺与pH 6.5-7.5的游离巯基/巯基迅速反应&#xff0c;形成稳定的&#xff0c;不可裂解的硫醚键。马来酰亚胺和硫醇的反应使得叠氮化物功能性PEG能够快速有效…

[附源码]Python计算机毕业设计Django的家政服务平台

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

设计模式-Acyclic Visitor(非循环访问者模式)

目的&#xff1a;允许将新功能添加到现有的类层次结构中&#xff0c;而不会影响这些层次结构&#xff0c;也不会有四人帮访客模式中那样循环依赖的问题。 类图&#xff1a; 使用场景&#xff1a; 需要在现有层次结构中添加新功能而无需更改或影响该层次结构时。 当某些功能在层…

Linux服务器远程访问通过Tomcat部署的静态资源

一、安装Java和Tomcat 1.1 安装Java 下载jdk8切换到root用户&#xff0c;创建文件夹/usr/local/java&#xff0c;将下载的jdk压缩包上传到该目录下&#xff0c;解压 mkdir /usr/local/java cd /usr/local/java rz tar -zxvf jdk-8u351-linux-x64.tar.gz编辑配置文件&#xf…

数据技术篇之数据服务

第6章 数据服务 1.服务架构演进 演进过程 DWSOA &#xff08;1&#xff09;实施原理   将业务方对数据的需求通过SOA服务的方式暴露出去。有需求驱动&#xff0c;一个需求开发一个或则几个接口&#xff0c;编写接口文档&#xff0c;开放给业务方调用。 &#xff08;2&…

[附源码]Python计算机毕业设计SSM基于WEB的心理测评系统(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

【手把手教你】使用qstock进行量化回测

01qstock简介qstock由“Python金融量化”公众号开发&#xff0c;试图打造成个人量化投研分析开源库&#xff0c;目前包括数据获取&#xff08;data&#xff09;、可视化(plot)、选股(stock)和量化回测&#xff08;backtest&#xff09;四个模块。其中数据模块&#xff08;data&…

SAS中用单因素ANOVA研究不同疗法对焦虑症的有效性

本教程将介绍如何使用SAS进行单因素方差分析。 最近我们被客户要求撰写关于单因素ANOVA的研究报告&#xff0c;包括一些图形和统计输出。我们想研究不同疗法对焦虑症的有效性。我们收集了以下类别的75个主题的样本&#xff1a; 无处理&#xff08;1个n1 27&#xff09;。生物…

5W3H法与SMART原则的结合使用

5W3H 5W3H分析法&#xff0c;又称“八何分析法”。在实际工作中&#xff0c;人们常常运用5W3H分析法&#xff0c;进行顾客分析、市场需求分析&#xff0c;解决计划编制的结构问题、方向问题、执行力问题。 5W3H是描述问题的手段&#xff0c;其具体指的是&#xff1a;What&…

【图像处理】深入解析LBP算法

问题 LBP是一种常见的特征描述算法&#xff0c;用来提取局部的纹理特征&#xff0c;其原理其实很简单&#xff0c;下面我们就来看看它是怎么一回事吧。 LBP简介 LBP&#xff08;Local Binary Patterns&#xff0c;局部二值模式&#xff09;是一种很简单但很高效的局部纹理特…

2025年DMS前装搭载或突破750万辆!多方势力搅局「融合集成」

在智能驾驶功能和舱内人机交互体验不断融合演进的过程中&#xff0c;基于摄像头的DMS/OMS功能正在进入新的发展周期。而在欧洲等部分国家及地区&#xff0c;DMS的标配&#xff08;主要涉及驾驶安全的监控&#xff0c;比如&#xff0c;驾驶员疲劳、注意力分散、安全带使用、吸烟…

java工厂策略模式的开发应用

java工厂策略模式的开发应用前言准备工作具体实现测试仰天大笑出门去&#xff0c;我辈岂是蓬蒿人前言 大概内容&#xff1a; 假设我有五个车间&#xff0c;每一个车间生产不同的车子&#xff0c;想要统一管理&#xff0c;不关心哪个车间生产什么车&#xff1b;只需找一个代理…

Ajax(一)

1.客户端与服务器 1.1 服务器 上网过程中&#xff0c;负责存放和对外提供资源的电脑。 1.2 客户端 上网过程中&#xff0c;负责获取和消费资源的电脑。 2.URL地址 2.1 URL地址的概念 2.2 URL地址的组成部分 3. 分析网页的打开过程 3.1 图解客户端与服务器的通信过程 三个…

NLP学习笔记(一) RNN基本介绍

大家好&#xff0c;我是半虹&#xff0c;这篇文章来讲循环神经网络 (Recurrent Neural Network, RNN) 文章行文思路如下&#xff1a; 首先通过前馈神经网络引出为啥需要循环神经网络然后介绍循环神经网络的核心思想与运作方式最后拓展两个循环神经网络常见且常用的变体 在讲循…

Windows 7恢复分区丢失了怎么恢复?

问题&#xff1a;Windows 7恢复分区丢失 戴尔、惠普和华硕等电脑制造商现会在他们的计算机上创建一个恢复分区。恢复分区允许用户在系统出现问题时将计算机恢复到出厂默认设置。但是&#xff0c;由于丢失了恢复分区&#xff0c;因此某些用户无法将电脑恢复到出厂设置。这是一个…

析构函数可以为纯虚函数吗?纯虚函数可以有函数体吗?纯虚函数需要函数体吗?

先回答标题中中的几个问题&#xff1a; 析构函数可以为纯虚函数吗&#xff1f; yes。纯虚函数可以有函数体吗&#xff1f; yes。纯虚函数需要函数体吗&#xff1f; 一般来讲&#xff0c;如果析构函数是纯虚函数&#xff0c;那么析构函数必须要有函数体&#xff0c;如果是其它函…