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

这篇具有很好参考价值的文章主要介绍了【夜深人静学数据结构与算法 | 第七篇】时间复杂度与空间复杂度。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

前言: 

引入: 

时间复杂度:

 案例:

空间复杂度:

案例:

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库中常见算法的时间复杂度

序号 算法 时间复杂度(平均) 最坏时间复杂度 备注
1 std::sort O(nlogn) O(nlogn) 快速排序
2 std::stable_sort O(nlogn) O(nlogn) 归并排序
3 std::partial_sort O(nlogk) O(nlogn)
4 std::nth_element O(n) O(n^2)
5 std::make_heap O(n) O(nlogn) 堆排序
6 std::push_heap O(logn) O(logn) 堆排序
7 std::pop_heap O(logn) O(logn) 堆排序
8 std::unique O(n) O(n) 只保留相邻的元素
9 std::reverse O(n) O(n)
10 std::rotate O(n) O(n)
11 std::merge O(n) O(n) 归并排序
12 std::inplace_merge O(nlogn) O(nlogn) 归并排序
13 std::shuffle O(n) O(n)
14 std::random_shuffle O(n) O(n)
15 std::max_element O(n) O(n)
16 std::min_element O(n) O(n)
17 std::binary_search O(logn) O(logn)
18 std::lower_bound O(logn) O(logn) 二分查找
19 std::upper_bound O(logn) O(logn) 二分查找
20 std::equal_range O(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;
}

总结:

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

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

【夜深人静学数据结构与算法 | 第七篇】时间复杂度与空间复杂度文章来源地址https://www.toymoban.com/news/detail-497988.html

到了这里,关于【夜深人静学数据结构与算法 | 第七篇】时间复杂度与空间复杂度的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【夜深人静学数据结构与算法 | 第三篇】 二叉树

    目录  前言:  二叉树: 二叉树的种类:  二叉树的存储方式: 1. 链式存储 2. 数组存储 二叉树的遍历方式 深度优先遍历 广度优先遍历 总结: 本文将会详细的介绍各种常见的树以及相对应的概念,存储方式,遍历方式。树是经典的数据结构,因此我们虽然不能做到手撕各

    2024年02月11日
    浏览(47)
  • 【夜深人静学数据结构与算法 | 第十篇】动态规划

    目录 前言: 动态规划: 常见应用: 解题步骤:  动态规划的简化步骤: 案例: 509. 斐波那契数 - 力扣(LeetCode) 70. 爬楼梯 - 力扣(LeetCode) 62. 不同路径 - 力扣(LeetCode) 总结:         本文我们将为大家讲解一下动态规划的理论知识,并且会讲解几道力扣的经典例题。

    2024年02月11日
    浏览(50)
  • 【夜深人静学数据结构与算法 | 第九篇】栈与队列

    目录 ​前言: 栈: 栈的实际应用:  队列: 队列的实际应用: 总结:         栈与队列是我们学习的两个经典的数据结构,这两个数据结构应用广泛,在计算机内有很多底层应用,而很多算法也是依靠栈和队列来实现的,因此我们要想学好数据结构与算法,就要学好栈与

    2024年02月15日
    浏览(42)
  • 【夜深人静学习数据结构与算法 | 第十二篇】动态规划——背包问题

      目录  前言:  01背包问题: 二维数组思路: 一维数组思路: 总结:       在前面我们学习动态规划理论知识的时候,我就讲过要介绍一下背包问题,那么今天我们就来讲解一下背包问题。 在这里我们只介绍 01背包 ,至于分组背包和混合背包这种的已经属于竞赛级别的

    2024年02月12日
    浏览(49)
  • 【夜深人静学数据结构与算法 | 第二篇】后缀(逆波兰)表达式

    目录 前言:  中缀表达式:  后缀表达式: 中缀表达式转后缀表达式: 后缀表达式计算结果: 总结:  计算机在计算四则运算的时候,由于括号以及运算优先级的存在,并不能够很好的处理所有的运算,为了处理这种情况,我们引入了后缀表达式来优化算法。         

    2024年02月13日
    浏览(52)
  • 【夜深人静学数据结构与算法 | 第四篇】手撕二叉树遍历

    目录 前言: 二叉树遍历方式: 手撕前中后序遍历(递归)的三大准备 深度优先搜索:  手撕前中后遍历(递归): 手撕前中后序遍历(迭代): 深度优先搜索: 总结:         今天我们将带领大家手撕二叉树的遍历,本篇会分别讲解深度优先搜索法和广度优先有搜索法下

    2024年02月09日
    浏览(46)
  • 夜深人静学32系列10——GPIO中断/NVIC/EXTI/SYSCFG详解,外部中断控制LED

    上期我们学习了GPIO驱动数码管/蜂鸣器/LED和按键等外设,本期我们一起来学习STM32中断的相关内容 当CPU正在处理某个事件的时候,外界发生了紧急事件请求,CPU需要暂停当前的工作,转而去处理这个紧急事件,处理完之后,再次回到之前被中断的地方,继续执行原来的工作,

    2024年01月16日
    浏览(46)
  • 【数据结构与算法】第七章-图【期末复习】

    图:有向图、网,无向图、网。 顶点 边:有向图图称作弧,分弧头弧尾。 依附:边依附点,边和点关联 邻接:点邻接点 度:点关联的边的数目 完全图: 无向: C n 2 C_n^2 C n 2 ​ 条边; 有向: 2 C n 2 2C_n^2 2 C n 2 ​ 条边 连通:若干结点互相可以通信,用手提起一个结点可以顺

    2024年02月01日
    浏览(58)
  • [数据结构 -- 手撕排序算法第七篇] 递归实现归并排序

    目录 1、归并的思想 2、归并排序的思想 2.1 基本思想 2.2 图解分析 3、归并排序递归版本代码实现 3.1 代码解析 3.2 注意事项 3.2.1错误划分:[begin, mid-1],[mid, end] 3.2.2 正确划分:[begin, mid], [mid+1, end] 4、归并排序的测试 5、时间复杂度、空间复杂度分析 5.1 时间复杂度 5.2 空间复杂

    2024年02月16日
    浏览(45)
  • 数据结构与算法分析 第七章 串、数组和广义表 作业讲解

     参考教材: 《数据结构(C语言版 第2版)》 严蔚敏,李冬梅,吴伟民编著,人民邮电出版社,2022年版。 截图未标明出处均为原创或取自《数据结构(C语言版 第2版)》~   本文对应的作业题讲解视频:   数据结构与算法分析作业讲解视频合集 https://www.bilibili.com/video/BV1N

    2024年02月04日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包