【夜深人静学数据结构与算法 | 第七篇】时间复杂度与空间复杂度

news2024/11/26 21:45:17

目录

前言: 

引入: 

时间复杂度:

 案例:

空间复杂度:

案例:

TIPS:       

总结:


前言: 

        今天我们将来介绍时间复杂度和空间复杂度,我们代码的优劣就是依靠这个在评判,以此为背景,我们诞生出了不少的经典思路:用时间换空间,用空间换取时间。而大多数同学对此并不在意,只是草草的看一眼知道这两个词就关闭文章,但这样是不对的,只有熟练的掌握时间复杂度与空间复杂度的计算,我们才可以更好的优化自己的代码,向拥有更低的时间和空间复杂度的代码靠近。

引入: 

        在最开始我们并没有时间复杂度和空间复杂度这个概念,大家对一个程序的的时间与空间消耗还采用一种朴素的方法:直接运行。是骡子是马拉出来溜溜,手动计算运行时间,查看占用空间量,这种方法虽然可以应用,但是误差太大,并且费人,如果我们要观测一个运行了100000000000000次代码的程序呢?是否要一直盯着电脑直到运行结束,因此为了方便快速的对一个程序有大致的判断,我们就创造了时间复杂度和空间复杂度。

时间复杂度:

        时间复杂度是衡量算法运行时间性能的指标,它表示算法运行时间随着输入规模增大而增长的趋势。时间复杂度分为最优、最坏和平均复杂度。通常,最坏时间复杂度是最重要的,因为我们需要保证算法在最坏情况下也能够很好地工作。

        时间复杂度的计算方法就是将所有代码语句的时间复杂度相加,忽略掉常数项和低阶项,得到的结果就是算法的时间复杂度,用大O符号表示。例如,在一个for循环中执行n次操作,每次操作的时间复杂度是O(1),那么整个循环的时间复杂度就是O(n)

一些常见的时间复杂度如下所示,按照从小到大的顺序排列:

  • O(1):常数时间复杂度,表示算法的执行时间不随输入规模变化而变化;
  • O(logn):对数时间复杂度,表示算法的执行时间随输入规模增大而增加,但增长缓慢;
  • O(n):线性时间复杂度,表示算法的执行时间随输入规模增大而线性增加;
  • O(nlogn):线性对数时间复杂度,表示算法的执行时间随输入规模增大而略微增加;
  • O(n2):平方时间复杂度,表示算法的执行时间随输入规模增大而增加得非常快;
  • O(n3):立方时间复杂度,表示算法的执行时间随输入规模增大而增加得非常快;
  • O(2n):指数时间复杂度,表示算法的执行时间随输入规模增大而呈指数级别增加。

 案例:

我们来计算以下这段代码的时间复杂度:

int i, j, n;
for(i=0; i<n; ++i)
{
    for(j=0; j<n; ++j)
    {
        cout<<"Hello, World!"<<endl;
    }
}
for(int m=0;m<n;m++)
{
    cout<<"abc";
}

我们分为两步来确定这个语句的时间复杂度

1.确定出每一个语句的时间复杂度

  • 数据初始化语句 int i, j, 的时间复杂度为O(1);
  • 外层for循环的时间复杂度为O(n),循环n次;
  • 内层for循环的时间复杂度为O(n),循环n次;
  • 输出语句 cout<<"Hello, World!"<<endl; 的时间复杂度也为O(1)。
  • 最后的一个循环时间复杂度为0(n),循环n次

2.累加所有语句的时间复杂度

得到时间复杂度的式子为:1+n*n*1+n

此时按照我们的要求:忽略掉常数项和低阶项

就可以得到结果:n2.

这就是时间复杂度的计算方法。我们可以自行写一些程序判断他的时间复杂度算法。

二分法的时间复杂度是O(log (m+n))

我们为大家介绍一下C++STL库中常见算法的时间复杂度

序号算法时间复杂度(平均)最坏时间复杂度备注
1std::sortO(nlogn)O(nlogn)快速排序
2std::stable_sortO(nlogn)O(nlogn)归并排序
3std::partial_sortO(nlogk)O(nlogn)
4std::nth_elementO(n)O(n^2)
5std::make_heapO(n)O(nlogn)堆排序
6std::push_heapO(logn)O(logn)堆排序
7std::pop_heapO(logn)O(logn)堆排序
8std::uniqueO(n)O(n)只保留相邻的元素
9std::reverseO(n)O(n)
10std::rotateO(n)O(n)
11std::mergeO(n)O(n)归并排序
12std::inplace_mergeO(nlogn)O(nlogn)归并排序
13std::shuffleO(n)O(n)
14std::random_shuffleO(n)O(n)
15std::max_elementO(n)O(n)
16std::min_elementO(n)O(n)
17std::binary_searchO(logn)O(logn)
18std::lower_boundO(logn)O(logn)二分查找
19std::upper_boundO(logn)O(logn)二分查找
20std::equal_rangeO(logn)O(logn)二分查找

篇幅原因,这里不对所有的函数都进行介绍,STL库为我们提供了大量的实用的算法函数,希望大家可以多多翻看查阅,掌握更多的STL库函数。

空间复杂度:

空间复杂度也是衡量算法性能的指标之一,它表示算法在执行过程中所需的额外空间(除了输入数据本身的内存空间)随数据规模增长而增长的趋势。通常,我们希望算法所需的额外空间尽可能小。

空间复杂度通常用字节(byte)或位(bit)来表示。算法所需的空间包括以下几种:

程序代码占用的空间,也就是代码段;
静态分配的数据空间,如全局变量、static变量等;
动态分配的数据空间,如堆空间、栈空间、指针等。

计算空间复杂度的方法和计算时间复杂度类似,也是将所有占用内存空间的代码语句和数据结构的空间占用相加。常用的空间复杂度表示方法有:

  • O(1):常数空间复杂度;
  • O(n):线性空间复杂度;
  • O(n^2):平方空间复杂度;
  • O(logn):对数空间复杂度;
  • O(nlogn):线性对数空间复杂度;
  • O(2^n):指数空间复杂度。

需要注意的是,计算空间复杂度时通常不考虑程序所使用的语言和编译器,而是关注算法本身使用的空间。

案例:

假设有以下代码实现,需要计算其空间复杂度:

int i, n = 100;
int* a = new int[n];
for(i=0; i<n; ++i)
{
    a[i] = i;
    cout<<a[i]<<endl;
}
delete[] a;

我们可以将计算空间复杂度的步骤拆分为以下几步:

1. 对每个语句分析其空间复杂度,确定所需内存大小。

数据初始化语句  int i, n = 100; 所需内存大小为O(1),即4个字节(int类型);
动态分配数组  int* a = new int[n]; 所需内存大小为O(n),即4*n个字节;
数组元素赋值语句  a[i] = i;  不需要额外的内存空间;
输出语句  cout<<a[i]<<endl;  所需内存空间为O(1),即sizeof(int)+sizeof(endl)字节;
动态释放数组 delete[] a;  不需要额外的内存空间。

2. 合并空间复杂度,得到总的空间复杂度。

可以发现,在代码执行过程中,所需的最大空间是数组的大小,即O(n)级别的空间复杂度。

因此,以上代码的空间复杂度为O(n),也就是说,以输入数据大小为n运行这段代码所需的额外空间随着输入规模n的增大而线性增长。

TIPS:       

在现代社会中,由于计算机的内存普遍都比较大,而算力吃紧,因此各家互联网公司的策略都是牺牲空间复杂度换取时间复杂度,这样虽然会占用用户的内存,但是在时间复杂度上大大提高了效率。

在我们编写代码的时候,经典的时间换空间复杂度的算法是桶排序

#include <iostream>
#include <vector>
using namespace std;

void bucketSort(vector<int>& nums, int max_val) {
    vector<int> bucket(max_val + 1, 0);
    for (int i = 0; i < nums.size(); i++) {
        bucket[nums[i]]++;
    }

    int idx = 0;
    for (int i = 0; i < bucket.size(); i++) {
        while (bucket[i] > 0) {
            nums[idx++] = i;
            bucket[i]--;
        }
    }
}

int main() {
    vector<int> nums = {5, 2, 9, 4, 1, 7, 6, 8, 3};
    int max_val = *max_element(nums.begin(), nums.end());

    bucketSort(nums, max_val);
    for (int i = 0; i < nums.size(); i++) {
        cout << nums[i] << " ";
    }
    return 0;
}

总结:

        时间复杂度和空间复杂度可以说是算法的基石,我们大量的算法的目的都是为了降低时间复杂度或者空间复杂度,因此我们要掌握好这两个知识点,这样才可以为学习算法打好基础。

如果我的内容对你有帮助,请点赞,评论,收藏。创作不易,大家的支持就是我坚持下去的动力!

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

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

相关文章

力扣算法刷题Day38|动态规划:斐波那契数 爬楼梯 使用最小花费爬楼梯

力扣题目&#xff1a;#509. 斐波那契数 刷题时长&#xff1a;参考答案后5min 解题方法&#xff1a;动态规划 复杂度分析 时间O(n)空间O(n) 问题总结 无 本题收获 动规五部曲思路 确定dp数组以及下标的含义&#xff1a;dp[i]的定义为&#xff0c;第i个数的斐波那契数值…

VMware虚拟机彻底卸载详细教程

VMware虚拟机彻底卸载 一、彻底卸载过程1.1 停止VMware服务1.2 结束vmware任务1.3 开始卸载VMware1.4 删除注册表信息1.5 删除安装目录 二、vmware 安装教程三、vmware 使用教程 回到目录   回到末尾 一、彻底卸载过程 卸载之前&#xff0c;需要先关闭VMware相关的后台服务…

软件技巧:7款冷门且十分良心的软件

1、Okular 阅读器 Okular是一款来自KDE的通用文档阅读器&#xff0c;支持众多文档格式&#xff0c;如PDF、Postscript、DjVu、CHM、XPS、ePub、图片格式、漫画格式等&#xff0c;支持Windows、macOS与Linux&#xff0c;是科研学术人士阅读文献的好工具&#xff0c;也是电子书爱…

OWASP 之认证崩溃基础技能

文章目录 一、burp爆破用法1.Attack type爆破方式设置2.payload处理3.请求引擎设置4.攻击结果设置5.grap匹配设置 二、常见端口与利用1、文件共享2、远程连接3、Web应用4、数据库 三、爆破案例经验1、暴力破解攻击产生的5个原因或漏洞2、猜测用户名方法3、猜测密码方法 四、实验…

亚马逊云科技中国峰会:Amazon DeepRacer——载着 AI 梦想向前奔跑

目录 一、Amazon DeepRacer 是什么&#xff1f; 二、Amazon DeepRacer 的前世今生 三、Amazon DeepRacer 深度体验 四、2023亚马逊云科技中国峰会 1.中国峰会总决赛 2.自动驾驶赛车名校邀请赛 3.Girls in Tech Show 4.全球联赛 5.报名链接&#xff1a; 一、Amazon Dee…

C++个人通信录管理系统

背景&#xff1a; 使用C编写一个个人通信录管理系统&#xff0c;来完成作业上的一些需求。 1-提供录入个人信息、修改个人信息&#xff08;姓名和出生日期除外&#xff09;、删除个人信息等编辑功能 2-提供按姓名查询个人信息的功能 3-提供查找在5天之内过生日的人员的信息…

【C++初阶】C++STL详解(二)—— string类的模拟实现

​ ​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;C初阶 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 上一篇博客&#xff1a;【C初阶】…

Internet Relay Chat:mIRC 7.73 Crack

mIRC是一个流行的互联网中继聊天客户端&#xff0c;个人和组织使用它在世界各地的IRC网络上相互交流、共享、玩耍和工作。为互联网社区服务了20多年&#xff0c;mIRC已经发展成为一种强大、可靠和有趣的技术。 Latest News mIRC 7.73 has been released! (June 18th 2023) This…

Linux常用命令——fuser命令

在线Linux命令查询工具 fuser 使用文件或文件结构识别进程 补充说明 fuser命令用于报告进程使用的文件和网络套接字。fuser命令列出了本地进程的进程号&#xff0c;那些本地进程使用file&#xff0c;参数指定的本地或远程文件。对于阻塞特别设备&#xff0c;此命令列出了使…

【C语言复习】第六篇、关于C语言操作符的知识

目录 第一部分、常见操作符 第二部分、每个操作符的具体使用 1、算术操作符 1.1、除法运算符 / 1.2、取模运算符 % 2、移位操作符 2.1、左移 << 2.2、右移 >> 3、位操作符 3.1、按位异或的妙用 3.2、按位与的妙用 4、单目操作符 4.1、逻辑反操…

18-1降维与特征选择——偏最小二乘方法(附matlab程序)

1.简述 降维&#xff1a; 比如现在有100维的变量来表征一个东西&#xff0c;我们觉得太冗余复杂了&#xff0c;想降低到10维。但是我们没有确定的筛选依据&#xff0c;直接使用数学工具来实现降维&#xff0c;就好像丢进了一个黑箱&#xff0c;经过抽象、提炼&#xff0c;得到了…

【深度学习】3-3 神经网络的学习- 导数梯度

导数 导数就是表示某个瞬间的变化量&#xff0c;式子如下&#xff1a; 式子的左边&#xff0c;表示f(x)关于x的导数&#xff0c;即f(x)相对于x的变化程度。式子表示的导数的含义是&#xff0c;x的“微小变化”将导致函数f(x)的值在多大程度上发生变化。其中&#xff0c;表示…

AI绘画是什么?怎样提高AI绘画技巧

大家好&#xff0c;我是权知星球&#xff0c;今天跟大家探讨一下AI绘画是什么&#xff1f;怎样才能提高AI绘画技巧的问题。 随着人工智能技术的迅速发展&#xff0c;AI绘画已成为一项具有前瞻性的技术。在过去几年中&#xff0c;涌现出了许多功能强大的人工智能绘画工具&#x…

荣耀加冕!数据猿斩获三项大奖,彰显技术媒体硬实力!

‍数据智能产业创新服务媒体 ——聚焦数智 改变商业 6月15日及6月16日&#xff0c;由数央网、数央公益联合国内众多媒体共同举办的2023国际绿色零碳节暨ESG领袖峰会、2023国际智造节暨硬科技峰会在北京举行。 该峰会旨在倡导全社会关注气候变化问题&#xff0c;积极采取行动&a…

TBarCode SDK:条码生成:11.15.1 Crack

TBarCode SDK&#xff1a;条码生成软件 TBarCode SDK 提供给 Microsoft Office 用户 和软件开发人员 条码打印。用这种 条码生成软件 您可以优良的品质的创建和打印所有用于工业和商业的 条码符号 。 最好的条码生成软件 TBarCode SDK 表示一个公知的集条形码创建组件集. 优秀…

Python爬虫需要那些步骤 ?

Python爬虫是一种自动化程序&#xff0c;可以通过网络爬取网页上的数据。Python爬虫可以用于各种用途&#xff0c;例如数据挖掘、搜索引擎优化、市场研究等。Python爬虫通常使用第三方库&#xff0c;例如BeautifulSoup、Scrapy、Requests等&#xff0c;这些库可以帮助开发者轻松…

Python面向对象编程2-面向过程的银行账号模拟程序 项目2.1 创建账号与存款

项目总目标&#xff1a;用面向过程思想设计一个简单的银行账号模拟程序。本次将迭代多个程序版本&#xff0c;每个版本都将添加更多功能。虽然这些程序没有达到发布的质量标准&#xff0c;但整个项目的目的是关注于代码如何与一个或多个银行账户的数据进行交互。 分析项目的必要…

<Linux> 基础IO

文章目录 文件操作基本概念当前路径文件打开方式"a""w""r" 文件描述符文件描述符fd是啥1. 为什么fd是从3开始&#xff0c;0&#xff0c;1&#xff0c;2呢&#xff1f;2. fd为什么是0&#xff0c;1&#xff0c;2&#xff0c;3&#xff0c;4&#…

Mockito单元测试基本使用

文章目录 1.为什么需要Mock2.Mockito 中常用方法2.1 Mock 方法2.2 对 Mock 出来的对象进行行为验证和Junit结果断言2.3 测试桩stub2.4 参数匹配器2.5 mock()与spy()2.6 InjectMocks 本文参考&#xff1a; 【码农教程】手把手教你Mockito的使用 - 掘金 (juejin.cn) java - doRet…