如何提高代码效率——时间复杂度与空间复杂度——【C语言】

当我们面对一个问题时,会有许多种解题思路。我们现在的计算机技术已经达到非常先进的地步,所以当我们用不同的方法对待问题时,时间差异不会很明显,内存差异我们一般在平常小问题时感受不到,所以我们不会去纠结程序的优化过程。

但是在以后的生活中,程序内容将会非常丰富,时间与空间的效率也就能体现出来,今天就让我们对程序的时间与空间进行学习。

目录

算法效率

 如何衡量一个算法的好坏

算法的复杂度 

 时间复杂度

时间复杂度的概念

大O的渐进表示法

常见时间复杂度计算举例

空间复杂度

常见空间复杂度计算举例

常见复杂度对比


算法效率

 如何衡量一个算法的好坏

如何衡量一个算法的好坏呢?比如对于以下斐波那契数列:

long long Fib(int N)
{if(N < 3)return 1;return Fib(N-1) + Fib(N-2);
}

斐波那契数列的递归实现方式非常简洁,但简洁一定好吗?那该如何衡量其好与坏呢?

虽然代码非常简洁,但是当我们将N的取值大于50之后,想要算出结果就非常的困难,电脑会进行大量计算。

算法的复杂度 

算法在编写成可执行程序后,运行时需要耗费时间资源和空间(内存)资源 。因此衡量一个算法的好坏,一般 是从时间和空间两个维度来衡量的,即时间复杂度和空间复杂度。 时间复杂度主要衡量一个算法的运行快慢,而空间复杂度主要衡量一个算法运行所需要的额外空间。在计算机发展的早期,计算机的存储容量很小。所以对空间复杂度很是在乎。但是经过计算机行业的迅速发展,计 算机的存储容量已经达到了很高的程度。所以我们如今已经不需要再特别关注一个算法的空间复杂度。

 时间复杂度

时间复杂度的概念

时间复杂度的定义:在计算机科学中,算法的时间复杂度是一个函数,它定量描述了该算法的运行时间。一 个算法执行所耗费的时间,从理论上说,是不能算出来的,只有你把你的程序放在机器上跑起来,才能知 道。但是我们需要每个算法都上机测试吗?是可以都上机测试,但是这很麻烦,所以才有了时间复杂度这个 分析方式。一个算法所花费的时间与其中语句的执行次数成正比例,算法中的基本操作的执行次数,为算法 的时间复杂度。

即:找到某条基本语句与问题规模N之间的数学表达式,就是算出了该算法的时间复杂度。

我们通过一个程序进一步了解时间复杂度:

// 请计算一下Func1中++count语句总共执行了多少次?
void Func1(int N)
{
int count = 0;
for (int i = 0; i < N ; ++ i)
{for (int j = 0; j < N ; ++ j){++count;}
}for (int k = 0; k < 2 * N ; ++ k)
{++count;
}
int M = 10;
while (M--)
{++count;
}
printf("%d\n", count);
}

我们需要计算出++count总共执行了多少次?在for循环嵌套中++count使用了N²次,在第三个for循环中又执行了2N次,最后在while循环中M=10,所以++count循环了10次,所以++count总共执行了N²+2N+10次

当我们在N取大值时,表达式中只有最高次数项对表达式影响大。 

实际中我们计算时间复杂度时,我们其实并不一定要计算精确的执行次数,而只需要大概执行次数,那么这里我们使用大O的渐进表示法。 

大O的渐进表示法

大O符号(Big O notation):是用于描述函数渐进行为的数学符号。

推导大O阶方法:

1、用常数1取代运行时间中的所有加法常数。

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

3、如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶。

使用大O的渐进表示法以后,Func1的时间复杂度为:O(N²) ;

N = 10 F(N) = 100                    N = 100 F(N) = 10000                N = 1000 F(N) = 1000000

通过上面我们会发现大O的渐进表示法去掉了那些对结果影响不大的项,简洁明了的表示出了执行次数。

另外有些算法的时间复杂度存在最好、平均和最坏情况:

最坏情况:任意输入规模的最大运行次数(上界)

平均情况:任意输入规模的期望运行次数 最好情况:任意输入规模的最小运行次数(下界) 例如:在一个长度为N数组中搜索一个数据x

最好情况:1次找到 最坏情况:N次找到 平均情况:N/2次找到 

在实际中一般情况关注的是算法的最坏运行情况,所以数组中搜索数据时间复杂度为O(N)

常见时间复杂度计算举例

// 计算Func3的时间复杂度?
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);
}

这段程序在第一个for循环中执行了M次,在第二个for循环中执行了N次,所以用大O表示O(M+N)

// 计算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;}
}

求BubbleSort的时间复杂度。这个函数就是冒泡排序法,如果我们对一组数组进行排序,最好的情况是N,只需要检查一次即可。最坏的情况就是一直要进行顺序调换,需要执行(N*(N+1))/2次,通过推导大O阶方法+时间复杂度一般看最 坏,时间复杂度为 O(N^2)。

// 计算BinarySearch的时间复杂度?
int BinarySearch(int* a, int n, int x)
{assert(a);int begin = 0;int end = n-1;// [begin, end]:begin和end是左闭右闭区间,因此有=号while (begin <= end){int mid = begin + ((end-begin)>>1);if (a[mid] < x)begin = mid+1;else if (a[mid] > x)end = mid-1;elsereturn mid;}return -1;
}

这个函数是二分查找法,我们需要求此函数的时间复杂度。

 根据上图我们就可以得出二分查找的时间复杂度为O(logN).

// 计算斐波那契递归Fib的时间复杂度?
long long Fib(size_t N)
{if(N < 3)return 1;return Fib(N-1) + Fib(N-2);
}

我们现在已经学会了时间复杂度,我们可以算一下用递归求斐波那契数列的时间复杂度,我们就能知道为什么用递归求斐波那契数列只是代码简洁,但并不是好方法!!

通过上图我们就可以得到这个递归的时间复杂度为O(2^N),所以这个算法很不好!!!

空间复杂度

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

空间复杂度不是程序占用了多少bytes的空间,因为这个也没太大意义,所以空间复杂度算的是变量的个数。 空间复杂度计算规则基本跟实践复杂度类似,也使用大O渐进表示法。

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

常见空间复杂度计算举例

// 计算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;}
}

此函数使用了常数个额外空间,开辟了size_t int、int exchange、size_t i这三个变量空间,所以空间复杂度为 O(1)。

// 计算斐波那契递归Fib的空间复杂度?
long long Fib(size_t N)
{if(N < 3)return 1;return Fib(N-1) + Fib(N-2);
}

使用递归计算斐波那契数,因为在每次调用函数时,都要开辟一块函数栈帧,当调用完成后就会对此函数的栈帧空间进行销毁,所以当我们计算空间复杂度时,我们得按着顺序进行累加计算,当函数还没有使用完成时不会去递归调用别的内容。因为斐波那契额数递归如同一个树,但是空间复杂度与时间复杂度不相同,空间复杂度是看在同一时间调用的次数,所以这个函数的空间复杂度为N,用大O表示为O(N).

常见复杂度对比

 

所以复杂度对一个程序有很大的影响,当我们在设计程序时不妨先停下来好好想一想怎样设计才是最优解呢!!!!! 

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

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

相关文章

JavaEE——Bean的生命周期

目录 1、实例化Bean 2、设置Bean的属性 3、初始化Bean &#xff08;1&#xff09;、执行通知 &#xff08;2&#xff09;、初始化的前置方法 &#xff08;3&#xff09;、初始化方法 &#xff08;4&#xff09;、执行自定义方法 &#xff08;5&#xff09;、初始化的后置…

3个月精通Python(基础篇)——第1天:Python和Vscode环境安装

安装 Python&#xff1a; 访问 Python 官网 https://www.python.org/downloads/ &#xff0c;下载最新的 Python 安装程序。 双击安装程序&#xff0c;按照提示进行安装设置即可。 在安装过程中&#xff0c;请勾选“Add Python X.X to PATH”选项&#xff0c;这样安装后 Python…

Redis学习路线(3)——Redis的Java客户端

一、如何使用Redis的Java客户端 官方文档&#xff1a; https://redis.io/docs/clients/java/ Java-Redis客户端使用场景Jeids 以Redis命令作为方法名称&#xff0c;学习成本低&#xff0c;简单实现&#xff0c;但是Jedis实例是线程不安全的&#xff0c;多线程环境下需要基于连…

华为数字化转型之道-读书笔记1

第一章 数字化转型&#xff0c;华为的战略选择 1. 数字化转型是企业的必答题 1.1 解决时代难题&#xff1a;“鲍莫尔成本病” “体验变流量&#xff0c;流量变收入”是其常见的商业模式。数字化平台通过“作业即记录、记录及数据”的方式&#xff0c;能给企业带来很多好处&a…

短视频账号矩阵系统源码开发部署路径

一、短视频批量剪辑的开发逻辑算法 1.视频剪辑之开发算法 自己研发视频剪辑是指通过对视频素材进行剪切、调整、合并等操作&#xff0c;利用后台计算机算法&#xff0c;进行抽帧抽组抽序进行排列以达到对视频内容进行修改和优化的目的。自己研发的视频剪辑工具可以通过后台码…

iOS中的一些锁

多线程在日常开发中能起到性能优化的作用&#xff0c;但是一旦没用好就会造成线程不安全&#xff0c;本文就来讲讲如何保证线程安全 锁 线程安全 当一个线程访问数据的时候&#xff0c;其他的线程不能对其进行访问&#xff0c;直到该线程访问完毕。简单来讲就是在同一时刻&a…

单片机中的通用LED驱动

前言 项目中需要用到很多的LED灯&#xff0c;存在不同的闪烁方式&#xff0c;比如单闪&#xff0c;双闪&#xff0c;快闪&#xff0c;慢闪等等&#xff0c;我需要一个有如下特性的LED驱动 方便的增加不同闪烁模式可以切换闪烁模式增加LED数目不会有太多的改动方便移植&#x…

k8s详解

目录 一、k8s 核心概念二、k8s 架构及服务三、k8s卷四、k8s 存储类五、k8s 网络 Kubernetes&#xff08;简称 k8s&#xff09;是一个开源的容器编排系统&#xff0c;用于自动化部署、扩展和管理容器化应用程序。本篇详解将介绍 k8s 的核心概念、架构和使用方法&#xff0c;帮助…

热门免费的API接口汇总,程序员必备

通用文字识别OCR&#xff1a;多场景、多语种、高精度的整图文字检测和识别服务&#xff0c;多项指标行业领先&#xff0c;可识别中、英、日、韩、法、德多种语言。 企业基本信息API&#xff1a;通过公司名称/公司ID/注册号或社会统一信用代码获取企业基本信息&#xff0c;企业…

【C++杂货铺】模板(文末有彩蛋哟)

文章目录 一、泛型编程二、函数模板2.1 函数模板的原理2.2 函数模板的实例化2.3 模板参数的匹配原则 三、类模板四、非类型模板参数五、模板的特化5.1 函数模板特化5.2 类模板特化 六、模板分离编译七、模板总结好书推荐&#x1f381;彩蛋 一、泛型编程 &#x1f4d6;实现一个…

Spring Boot单元测试入门指南

Spring Boot单元测试入门指南 JUnit是一个成熟和广泛应用的Java单元测试框架&#xff0c;它提供了丰富的功能和灵活的扩展机制&#xff0c;可以帮助开发人员编写高质量的单元测试。通过JUnit&#xff0c;开发人员可以更加自信地进行重构、维护和改进代码&#xff0c;同时提高代…

volley 学习笔记1--发送请求

一、概览 Volley 具有以下优势&#xff1a; 自动网络请求调度。 多个并发网络连接。 透明磁盘和具有标准 HTTP 缓存一致性的内存响应缓存。 支持请求优先级。 取消请求 API。您可以取消单个请求&#xff0c;也可以设置要取消的请求的时间段或范围。 可轻松自定义&#xff…

linux下i2c调试神器i2c-tools安装及使用

i2c-tools介绍 在嵌入式linux开发中&#xff0c;有时候需要确认i2c硬件是否正常连接&#xff0c;设备是否正常工作&#xff0c;设备的地址是多少等等&#xff0c;这里我们就需要使用一个用于测试I2C总线的工具——i2c-tools。 i2c-tools是一个专门调试i2c的开源工具&#xff…

Kafka 入门到起飞系列 - 消费者组管理、位移管理

消费者组 - Consumer Group 上文我们已经讲过消费者组了&#xff0c;我们知道消费组的存在可以保证一个主题下一个分区的消息只会被组内一个消费者消费&#xff0c;从而避免了消息的重复消费 什么是消费组 - Consumer Group&#xff1f; 消费者组是Kafka 提供的可扩展且具有容…

事后多重比较方法

一、案例介绍 由单因素方差分析案例中&#xff0c;为研究郁金对低张性缺氧小鼠存活时间的影响&#xff0c;将36只小鼠随机生成A、B以及 C 三组&#xff0c;每组12个&#xff0c;雌雄各半&#xff0c;分别以10g/kg、20g/kg、40g/kg三种不同剂量的郁金灌胃&#xff0c;各组小鼠均…

动态规划:跳跃游戏

一)跳跃游戏: 55. 跳跃游戏 - 力扣&#xff08;LeetCode&#xff09; 一)定义一个状态表示: dp[i]表示以i未知元素为起点&#xff0c;是否能够到达最后一个位置 二)根据状态表示推到状态转移方程:根据最近的一步来进行划分问题 我们可以从当前i位置向后走j步&#xff0c;看看从…

【vim 学习系列文章 3 - vim 选中、删除、复制、修改引号或括号内的内容】

文章目录 vim 快捷命令vim 操作符vim 文本对象vim 动作 上篇文章: vim 学习系列文章 2 - vim 常用插件配置 vim 快捷命令 Vim 有一个模块化的结构&#xff0c;允许你使用各种命令的组合操作。大多数命令有两个、三个或四个部分。三部分结构的一个版本是这样的&#xff1a;操作…

记录一下Kotlin: Module was compiled with an incompatible version of Kotlin.的问题

我遇到的整个报错是这样的&#xff1a; Error:Kotlin: Module was compiled with an incompatible version of Kotlin. The binary version of its metadata is 1.7.1, expected version is 1.1.16. 大概意思就是不匹配&#xff0c;但是我这是个不太能随便改代码的项目&#x…

从原理到实践,分析 Redisson 分布式锁的实现方案(二)

上篇讲解了如何用 Redis 实现分布式锁的方案&#xff0c;它提供了简单的原语来实现基于Redis的分布式锁。然而&#xff0c;Redis作为分布式锁的实现方式也存在一些缺点。本文将引入Redisson来实现分布式锁。 一、Redisson是什么 Redisson是一个基于Redis的分布式Java框架。它提…

信息安全:网络安全体系 与 网络安全模型.

信息安全&#xff1a;网络安全体系 与 网络安全模型. 网络安全保障是一项复杂的系统工程&#xff0c;是安全策略、多种技术、管理方法和人员安全素质的综合。一般而言&#xff0c;网络安全体系是网络安全保障系统的最高层概念抽象&#xff0c;是由各种网络安全单元按照一定的规…