本文是计划花一些时间学习《代码随想录》时做一个记录,水平有限,可能理解有误,内容摘抄总结自Carl大佬的 代码随想录算法性能分析 感谢大佬开源的面向算法初学者的好资料
认识时间复杂度
时间复杂度是在数学上对代码的执行情况的一个估计,建立在默认单元运算在计算机中所消耗的时间相同,便可以通过计算算法的单元运算数量来进行消耗时间的估算,算法在问题规模n增大的时候,其单元运算数量渐进函数f(n),那么时间复杂度则为O(n)
- 算法的真正的执行情况受到数据的很大影响
- 算法的大O的意思是算法在数学上的复杂度上界
- 对于具体的数据不同,大致可分为一般情况,最差情况和最好情况的时间复杂度,我们根据具体情况不同分析,但是一般情况时间复杂度是最关心的
- 在小数据量时,由于时间复杂度计算忽略的常数项,会有高时间复杂度实际比时间复杂度低的算法更快的情况
- 时间复杂度中的log是无所谓底数的,因为可以通过换底公式产生一个常数项而被省略掉
运行超时错误
计算机通过读指令执行的方式来进行每一次计算,执行每一次的速度取决于CPU的主频,每一个单元运算需要经过差不多的几个机器周期来执行,当总运行时间超过了题目允许的时间后就会发生运行超时错误。
想要估计大概允许多少个操作,可以写一个简单的程序来试一试,但考虑到各种原因,通常得到的并不准确
- 实际上各种单元运算消耗的时间并不一样
- 实际上计算机对访存的算法和优化不同也导致了计算机实际进行的取指令执行的行为也不一样
- 程序受到其他程序的抢占也使得真实时间不准确
但是得到的实际访问时间还是有参考意义的,通过这个估计的时间,我们大体上可以确定我们至少需要一个什么样的时间复杂度的算法。
递归调用 中,对中间结果进行保存,在需要的时候直接用,通过多余的存储空间的方式,减少重复计算的时间。
空间复杂度
与时间复杂度类似,空间复杂度也使用大O来表示,表示随着事件规模增长,算法在应用过程中占用内存空间的单位数量大小
递归调用 中,空间复杂度的计算通常使用递归深度*每次递归的空间复杂度
代码的内存消耗
- C/C++ 内存的申请和释放由用户自己管理
- Java 依赖JVM对内存管理,不了解的话有可能造成内存泄露、
- python 语言依赖私有堆进行管理
以C语言为例子
- 栈区(Stack) :由编译器自动分配释放,存放函数的参数值,局部变量的值等,其操作方式类似于数据结构中的栈。
- 堆区(Heap) :一般由程序员分配释放,若程序员不释放,程序结束时可能由OS收回
- 未初始化数据区(Uninitialized Data): 存放未初始化的全局变量和静态变量
- 初始化数据区(Initialized Data):存放已经初始化的全局变量和静态变量
- 程序代码区(Text):存放函数体的二进制代码
其中堆区的内存需要程序员自己来进行回收,这就是C可能造成内存泄漏的来源
如何计算自己的程序会占用多少内存
首先要了解自己语言定义的数据类型的大小
32位编译器与64位编译器之前指针的存储大小不同,因为64位需要更多的位来保证其能够寻址到正确的内存空间
内存对齐
这是面试中面试官非常喜欢问到的问题,就是:为什么会有内存对齐?
主要是两个原因
-
平台原因:不是所有的硬件平台都能访问任意内存地址上的任意数据,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。为了同一个程序可以在多平台运行,需要内存对齐。
-
硬件原因:经过内存对齐后,CPU访问内存的速度大大提升。
CPU读取内存不是一次读取单个字节,而是一块一块的来读取内存,块的大小可以是2,4,8,16个字节,具体取多少个字节取决于硬件。
例如:
吐槽 :我们真的在编程考虑空间复杂赌读的时候还会考虑内存对齐吗,感觉不会有太大的变化,感觉空间相对时间来说还是更便宜