时间,空间复杂度讲解——夯实根基

        前言:本节内容属于数据结构的入门知识——算法的时间复杂度和空间复杂度。

        时间复杂度和空间复杂度的知识点很少, 也很简单。 本节的主要篇幅会放在使用具体例题来分析时间复杂度和空间复杂度。本节内容适合刚刚接触数据结构或者基础有些薄弱的友友们哦。

衡量一个算法的性能

       我们用什么来衡量一个算法的性能呢? 其实就是用到了时间复杂度和空间复杂度时间复杂度是一个时间的量度, 它用来衡量一个算法的快慢程度空间复杂度是一个空间的量度, 它用来衡量一个算法所需要的空间多少。、

        算法的时间复杂度和空间复杂度都是一个函数, 这个函数并不是数学中的函数, 是在计算机科学之中进行定义的。 对于时间复杂度来说, 时间复杂度描述的是一个算法的执行次数; 对于空间复杂度来说, 空间复杂度描述的是一个算法在运行过程中额外开辟的空间个数

        执行次数:这里的这个概念很重要, 什么是执行次数? 执行次数就是一个算法中循环的的次数。 例如如图的代码:

void test() 
{for (int i = 0; i < 10; i++) {printf("%d\n", i);}
}

        图中的循环有10次, 所以执行次数就是10。这就是执行次数的概念。

        空间消耗:其实随着科技的进步, 现在的计算机的硬件已经非常优秀了。 所以一个算法对于空间消耗的关注不再像以前那样关注。 但是, 这里我们还是要知道空间消耗的概念。我们看一个实例:


void test1() 
{int i = 0;int a = 0;int b = 0;scanf("%d %d %d", &a, &b, &i);printf("%d %d %d", a, b, i);
}

        图中创建了三个变量, 所以空间消耗就是3, 这就是空间消耗的概念

表示方法     

       时间复杂度和空间复杂度的表示方法使用了大O的渐进表示法。 什么时大O的渐进表示法?

        我们在上文中说过, 算法的时间复杂度和空间复杂度都是一个函数, 但是与数学的函数又有所区别。  其实, 大O的渐进表示法就可以使用这个函数来理解。 

时间复杂度

        我们这里将使用具体的实例来分析算法的时间复杂度:

void func1() 
{int N;scanf("%d", &N);for (int i = 0; i < N; i++){printf("%d", i);}for (int i = 0; i < N; i++) {for (int j = 0; j < N; j++) {printf("%d", j);}printf("\n");}}int main() 
{func1();return 0;
}

     时间复杂度的计算其实就是计算算法的执行次数, 由上文的执行次数的概念我们知道func1算法的执行次数是N^2 + N。

        那么利用大O的渐进标识法表示后,算法func1的时间复杂度就是O(N^2)。由此我们可以见到N^2 + N使用大O的渐进表示法之后, 只保留了最大项。这里的N^2 + N中的N为什么会被省略, 我们接下来会进行讲解:

        对于上面的算法来说: 执行次数是N^2 + N, 对于这里的N我们取三个值进行分别讨论: 一个是当N == 10的时候, 第二个是当N == 10000的时候, 第三个是当N == 1000000的时候。

        现在我们先来看当N == 10的时候。 那么N ^ 2 是100, N 是10。 这个时候两者的差距不大。 我们使用程序来跑一下,  下面是测试过程:

        测试时间差距需要使用头文件time.h里面的一个函数: clock(),  这个函数的作用是返回从程序的开始到该执行语句所经历的时间。 并且这个返回值的单位是毫秒

        如图为我们进行测试执行次数N^2和N的时间差距:

#include<time.h>void func2()
{int N;scanf("%d", &N);int begin1 = clock();//进入循环前,记一下时间for (int i = 0; i < N; i++){int x = 1;}int end1 = clock();//出循环后, 记一下时间int begin2 = clock();//进入第二个循环前, 记一下时间for (int i = 0; i < N; i++){for (int j = 0; j < N; j++){int y = 1;}}int end2 = clock();//出第二个循环前, 记一下时间。 int time1 = end1 - begin1;//time1就是第一个循环所消耗的时间int time2 = end2 - begin2;//time2就是第二个循环所消耗的时间//对这两次的时间进行打印:cout << "N时间为: " << time1 << endl;cout << "N^2时间为: " << time2 << endl;}int main()
{func2();return 0;
}

   

        如图可见, 两者的时间都是0, 说明N == 10对cpu来说太小了, 我们加大N的大小。

下面测试第二组: N = 10000.

        如图为第二组的测试结果。 在这组测试中, 第一个循环执行了N == 10000次, 消耗的时间小于1毫秒。

        第二个循环执行了1亿次, 消耗的时间为24毫秒。

        那么到现在我们其实已经可以稍微有点发现:当N变得越来越大时, N^2所消耗的时间是远远大于N的。

        

        上面的第二次测试可能还不明显, 现在我们来看一下第三次测试, 这一次的N == 1000000, 也就是N 为 一百万, N^2为1万亿的时候。(ps : 这个时候其实已经需要跑很久了。)

        由图可以看到, 其实第一个循环, cpu只用了1毫秒就完成了, 而第二个循环,cpu用了6分钟的时间。 而1毫秒相对于6分钟是可以省略的, 所以当N为一百万的时候我们可以不考虑第一个循环, 也就是不考虑N, 只需要考虑N ^ 2即可。

       而且,我们也能看出来, cpu的执行速度是很高的。 在上面cpu执行一亿次循环也只用了二十几毫秒。 所以, 当N很小的时候, 我们是不关心的, 我们只关心当N很大的时候, 像后面的一百万。 而当N为一百万的时候, 第一个循环我们是可以忽略的。 所以对于N^2 + N的执行次数来说, 他的时间复杂度就是O(N^2)。

        所以, 综上, 大O的渐进表示法, 考虑的是当N极大时的情况, 当N很小时我们不进行考虑。 所以, 我们在使用大O的渐渐表示法的时候, 如果有了关于N的函数, 我们只去最大项。

       

        本节的知识点内容很少, 主要是使用例题来学习如果使用大O的渐进表示法, 下面我们来看例题

例题1:

        计算如图的时间复杂度


void test1(int n) 
{int x = 0;for (int i = 0; i < n; i++) {x++;}int k = 10;for (int i = 0; i < k; i++) {x++;}
}

       解析: 如图中有两个循环, 第一个循环是从i = 0到 n。 第二个循环的执行次数是10, 那么可以得到执行次数为 : n + 10,  那么使用大O的渐进表示法取最大项就是O(n)。

例题2:

        计算如图的时间复杂度


void test2(int n, int k)
{int x = 0;for (int i = 0; i < n; i++){x++;}for (int i = 0; i < k; i++){x++;}
}

        解析:如图有两个未知量, 一个是n, 一个是k, 同时有两个循环, 第一个循环是从0~n, 第二个循环是从0~k, 那么他一共的执行次数就是n + k。 并且由于n和k都不是常数, 他们同时都是未知量。 所以时间复杂度就是O(n + k)。

例题3:

        计算如图的时间复杂度

void test3(int n)
{int x = 0;for (int i = 0; i < 50; i++) {x++;}
}

        解析:图中虽然有位置量n, 但是图中的循环执行是从i = 0到50, 也就是执行次数为50. 执行次数永远为一个常数。 而常数级别使用大O的渐进表示法我们规定是: O(1)。 这就代表常数级别的时间复杂度。

例题4:

        计算冒泡排序的时间复杂度:

//本串代码不是原创, 是从本人学习的课件中搬运而来。如果和别人的相同, 属于巧合。非是抄袭。
void BubbleSort(int* a, int n)
{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;}
}

        解析:  冒泡排序有两层循环, 第二层循环的执行次数会随着第一层循环执行次数的增长而减小。 根据推到, 我们其实可以计算出冒泡排序的执行次数是这样的 : (n - 1) + (n - 2) + (n - 3) + (n - 4) + (n - 5) + …… + 1 = n^2 / 2;

        如果最大项带有系数, 就像现在的n^2 / 2, 我们一般都是将它的系数省略。 所以冒泡的时间复杂度就是O(n^2)。

 例题5

        计算如图的时间复杂度

//本串代码不是原创, 是从本人学习的课件中搬运而来。如果和别人的相同, 属于巧合。非是抄袭。
int BinarySearch(int* a, int n, int x)
{int begin = 0;int end = n - 1;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;
}

        解析 :  同种是一个朴素二分查找的算法, 对于这个算法来说, 最坏的一种情况就是当左右指针相等的时候才找到要寻找的值。 如图:        

        而这种情况下我们可以假设执行次数是k次, 那么就有2 ^ k = n, 所以它的执行次数计算出来就是lgn, 所以二分算法的时间复杂度就是O(lgn)。 

以上, 就是时间复杂度的全部例题, 下面开始讲解空间复杂度。

空间复杂度

        空间复杂度同样是使用大O的渐进表示法进行标识, 同时规则和时间复杂度的一样, 我们只考虑最大项即可。

        我们直接通过例题来进行空间复杂度的学习:

例题1

        计算下面算法的空间复杂度:

//本串代码不是原创, 是从本人学习的课件中搬运而来。如果和别人的相同, 属于巧合。非是抄袭。
void BubbleSort(int* a, int n)
{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;}
}

        还是冒泡排序, 计算这个排序算法的空间复杂度。 在计算空间复杂度之前, 我们需要想清楚一件事情:空间复杂度计算的是什么? 

        这里要强调: 空间复杂度计算的是额外空间!额外空间!额外空间!

        那么, 既然空间复杂度计算的是额外空间, 冒泡排序中的参数n, 还有数组a, 就不算在空间复杂度中。 为什么会这样? 

        这里要想明白一个问题, 我们在计算空间复杂度的时候, 计算的是这个算法的空间复杂度。 但是, 对于数组a和参数n来说, 任何一个算法设计,我们是不是都要使用参数接收他们?他们是不可能避免的。 是不属于这个算法的。 所以, 我们说数组a和参数n不算在空间复杂度中。 

        那么再回过头去看我们的冒泡排序, 在这个排序中, 我们只开了三个变量: end,  i, exchange。 那么它的空间复杂度就是O(1)。  

例题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;
}

        解析: 首先malloc开辟了n + 1个long long类型的空间。 然后for循环中创建了一个int i 的变量。 所以一共二外开辟了n + 2个空间。 所以空间复杂度是O(n)。

空间复杂度的内容较少, 实际上, 因为硬件的发展。人们对于空间复杂度的关注已经少于时间复杂度。当然, 也有一些比如单片机领域就会注重空间复杂度。但是, 在本节中关于空间复杂度的讲解已经完毕。 

        以上, 后续会添加一个复杂度系数的对比图。 (待博主学会画图再来)

        

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

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

相关文章

C/C++开发,opencv-ml库学习,随机森林(RTrees)应用

目录 一、随机森林算法 1.1 算法简介 1.2 OpenCV-随机森林&#xff08;Random Forest&#xff09; 二、cv::ml::RTrees应用 2.2 RTrees应用 2.2 程序编译 2.3 main.cpp全代码 一、随机森林算法 1.1 算法简介 随机森林算法是一种集成学习&#xff08;Ensemble Learning&a…

软件著作权合作开发协议撰写指南

由两个以上的自然人、法人或者非法人组织合作开发的软件,其著作权的归属由合作开发者签订书面合同约定。无书面合同或者合同未作明确约定,合作开发的软件可以分割使用的,开发者对各自开发的部分可以单独享有著作权;但是,行使著作权时,不得扩展到合作开发的软件整体的著作…

大语言模型在研究领域的应用——推荐系统中的大语言模型

推荐系统中的大语言模型 大语言模型作为推荐模型基于特定提示的方法基于指令微调的方法大语言模型增强的推荐模型数据输入增强语义表示增强偏好表示增强大语言模型作为推荐模拟器总结应用建议现存问题和未来方向推荐系统的核心在于捕捉并理解用户的潜在偏好,进而为用户推送合适…

Python 语音识别系列-实战学习之初识语音识别

Python 语音识别系列-实战学习之初识语音识别 前言1.语音识别简介2.语音识别的工作原理3.语音识别的基本模块4.语音识别的发展历史5.语音识别的应用场景6.语音识别的Python案例7.总结 前言 在本篇博客中&#xff0c;将介绍语音识别的基础知识&#xff0c;包括其定义、工作原理…

百度沈抖:智能,生成无限可能

4月16日&#xff0c;Create 2024百度AI开发者大会在深圳举行。会上&#xff0c;百度集团执行副总裁、百度智能云事业群总裁沈抖正式发布新一代智能计算操作系统——百度智能云万源。它能管理万卡规模的集群&#xff0c;极致地发挥GPU、CPU的性能&#xff1b;它有强大的大模型作…

本地环境运行Llama 3大型模型:可行性与实践指南

简介&#xff1a; Llama 是由 Meta&#xff08;前身为 Facebook&#xff09;的人工智能研究团队开发并开源的大型语言模型&#xff08;LLM&#xff09;&#xff0c;它对商业用途开放&#xff0c;对整个人工智能领域产生了深远的影响。继之前发布的、支持4096个上下文的Llama 2…

19. TypeScript 类型运算符

TypeScript作为JavaScript的超集&#xff0c;不仅提供了静态类型检查&#xff0c;还引入了多种类型运算符&#xff0c;使得类型定义更加灵活和强大。这些运算符可以帮助我们构建更加精确和灵活的类型定义。本文将详细介绍这些类型运算符的用法和应用场景。 keyof 运算符 keyo…

Python 数据可视化 boxplot

Python 数据可视化 boxplot import pandas as pd import matplotlib.pyplot as plt import numpy as np import seaborn as sns# 读取 TSV 文件 df pd.read_csv(result.tsv, sep\t)normal_df df[df["sample_name"].str.contains("normal")] tumor_df df…

重磅!!!监控分布式NVIDIA-GPU状态

简介&#xff1a;Uptime Kuma是一个易于使用的自托管监控工具&#xff0c;它的界面干净简洁&#xff0c;部署和使用都非常方便&#xff0c;用来监控GPU是否在占用&#xff0c;非常美观。 历史攻略&#xff1a; docker应用&#xff1a;搭建uptime-kuma监控站点 win下持续观察…

新能源汽车电池盒尺寸检测

SNK施努卡电池盒错漏装和尺寸检测 随着环境污染和能源短缺问题日益突出&#xff0c;新能源汽车作为一种环保、可持续的交通工具得到了广泛关注和推广。其中&#xff0c;电池是新能源汽车的核心部件之一&#xff0c;其性能和质量直接影响到整车的续航能力和安全性。而电池盒作为…

Unity Meta Quest MR 开发(七):使用 Stencil Test 模板测试制作可以在虚拟与现实之间穿梭的 MR 传送门

文章目录 &#x1f4d5;教程说明&#x1f4d5;Stencil Test 模板测试&#x1f4d5;Stencil Shader&#x1f4d5;使用 Unity URP 渲染管线设置模板测试⭐Render Pipeline Asset 与 Universal Renderer Data⭐删除场景中的天空盒⭐设置虚拟世界的层级 Layer⭐设置模板测试 &#…

《Vid2Seq》论文笔记

原文链接 [2302.14115] Vid2Seq: Large-Scale Pretraining of a Visual Language Model for Dense Video Captioning (arxiv.org) 原文笔记 What&#xff1a; 《Vid2Seq: Large-Scale Pretraining of a Visual Language Model for Dense Video Captioning》 作者提出一种多…

uniapp实现相册、拍照及视频录制功能

一、调用相册、拍照及视频录制功能要先获取相册权限&#xff0c;摄像头权限&#xff0c;要不然&#xff0c;调用不了下面的方法 1.1、到插件市场先下载js_sdk 1.2、引入js_sdk import permision from "/js_sdk/wa-permission/permission.js" 1.3 、安卓手机获取权…

Python内置函数input()详解

Python内置函数input()详解 在Python编程中&#xff0c;input()函数是一个基本的内置函数&#xff0c;它允许程序从用户那里获取输入。这个函数对于创建交互式程序来说非常重要&#xff0c;因为它让程序能够接收用户的文本输入。 函数功能 input()函数的主要功能是从标准输入…

深度学习检测算法YOLOv5的实战应用

在当前的检测项目中&#xff0c;需要一个高效且准确的算法来处理大量的图像数据。经过一番研究和比较&#xff0c;初步选择了YOLOv5作为算法工具。YOLOv5是一个基于深度学习的检测算法&#xff0c;以其快速和准确而闻名。它不仅能够快速处理图像数据&#xff0c;还能提供较高的…

两数、三数以及四数之和

两数之和 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出和为目标值 target 的那两个整数&#xff0c;并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是&#xff0c;数组中同一个元素在答案里不能重复出现。 你可以按…

Java基础教程(7)-Java中的面向对象和类

面向对象编程 Java是一种面向对象的编程语言。面向对象编程,英文是Object-Oriented Programming,简称OOP 面向对象和面向过程的区别 面向过程编程是自顶而下的编程模式;把问题分解成一个一个步骤,每个步骤用函数实现,依次调用即可 面向对象编程是将事务高度抽象化的编程…

acwing算法提高之图论--拓扑排序

目录 1 介绍2 训练3 参考 1 介绍 本专题用来记录拓扑排序相关的题目。 求拓扑序列算法的关键步骤&#xff1a; 把入度为0的结点插入队列q。弹出队头t&#xff08;将t记录下来&#xff09;&#xff0c;遍历队头t的下一个结点&#xff0c;将其入度减1。操作之后&#xff0c;如…

【OceanBase诊断调优】——hpet(高精度时钟源)引起的CPU高问题排查

最近总结一些诊断OCeanBase的一些经验&#xff0c;出一个【OceanBase诊断调优】专题出来&#xff0c;也欢迎大家贡献自己的诊断OceanBase的方法。 1. 前言 昨天在问答区帮忙排查一个用户CPU高的问题&#xff0c;帖子链接&#xff1a;《刚刚新安装的OceanBase集群&#xff0c;…

Rime 如何通过 iCloud 实现词库多端同步,Windows、iOS、macOS

Rime 如何通过 iCloud 实现词库多端同步&#xff0c;Windows、iOS、macOS 一、设备环境 最理想的输入环境就是在多端都使用同一个词库&#xff0c;这样能保持多端的输入习惯是一致的。 以我为例&#xff0c;手头每天都要用到的操作平台和对应的输入法&#xff1a; 操作系统设…