Fighting——Day1

复习<<数据结构与算法——复杂度分析>>

学习数据结构与算法的目的为了提高代码的执行效率及降低代码所占用的存储空间,因此时间复杂度及空间复杂度是评估一个算法的重要因素。而关于这两者的分析统称为复杂度分析。复杂度分析是整个算法学习的精髓,只要掌握了它,数据结构和算法的内容基本上就掌握了一半。

  • 为什么需要复杂度分析?

我们知道,可以直接通过统计、监控,就可以直接得到算法执行的确切时间和占用的内存大小。这种评估算法执行效率的方法又称作事后统计法,既然有这种方法,那我们为什么还要做时间、空间复杂度分析呢?这是由于事后统计法具有很大的局限性:

  1. 测试结果非常依赖测试环境
  2. 测试结果受数据规模的影响很大

因此,我们需要一个不用具体的测试数据来测试,就可以粗略地估计算法的执行效率的方法——复杂度分析。

  • 复杂度分析的作用
  1. 复杂度分析为我们提供一个很好的理论分析方向,并且这与测试环境是无关的,能够帮助我们对算法有一个大致的认识,让我们知道,比如在最坏的情况下程序执行的效率如何,同时也能为我们定义一个统一的标准,我们可以描述一个算法的时间复杂度是O(1),O(logn)等等。
  2. 但是我们要时刻清楚,复杂度分析只是一个理论模型,只能提供粗略的估计分析,我们不能直接根据复杂度判断算法的好坏,比如说算法1的时间复杂度是O(logn),算法2的时间复杂度是O(n),我们就不能直接断定算法1优于算法2,针对不同的环境,不同的数据集合,不同数据量的大小,在实际应用上可能真实的性能会不同
  • 大O复杂度表示法

大O时间复杂度实际上并不具体表示代码真正的执行时间,而是表示代码执行时间随数据规模增长的变化趋势,所以也叫做渐进时间复杂度,简称时间复杂度,同理可知大O空间复杂度的定义。

  • 时间复杂度分析要点
  1. 只关注循环执行次数最多的一段代码
  2. 加法法则:总复杂度等于量级最大的那段代码的复杂度
  3. 乘法法则:嵌套代码的复杂度等于嵌套内外的代码复杂度的乘积
  • 常见的复杂度量级
  1. 常量阶:O(1)
  2. 对数阶:O(logn)
  3. 线性阶:O(n)
  4. 线性对数阶:O(nlogn)
  5. 平方阶:O(n²)、立方阶:O(n³)、...... 、k次方阶:O(n^{k})
  6. 指数阶:O(2^{n})
  7. 阶乘阶:O(n!)
  • 复杂度分析常用的四种情况
  1. 最好情况时间复杂度
  2. 最坏情况时间复杂度
  3. 平均情况时间复杂度
  4. 均摊时间复杂度

复习<<数据结构与算法——数组>>

数组是一种线性表数据结构,它用一组连续的内存空间,来存储一组具有相同类型的数据,下面我们来看一下数组的特性。

  • 根据下标实现随机访问数组元素

我们知道计算机会给每个内存单元分配一个地址,计算机通过地址来访问内存中的数据。当计算机需要随机访问数组中的某个元素时,它会首先通过下面的寻址公式,计算出该元素存储的内存地址:

                                                         a[i]_address = base_address + i * data_type_size

有时候我们会有一种误解认为数组查找的时间复杂度为O(1),其实正确的说法应该是,数组支持随机访问,根据下标随机访问的时间复杂度为O(1)。

  • 低效的"插入"和"删除"操作
  1. 假设数组的长度为n,现在如果我们需要将一个数据插入到数组的第k个位置。为了把第k个位置腾出来给新来的数据,我们需要将第k~n这部分的元素都顺序地往后挪一位。但是当数组本身存储的数据并没有规律,只是当作一个存储数据的集合,在这种情况下,如果要将某个元素插入到第k个位置,为了避免大规模的数据搬移,我们可以直接将第k个数据转移到数组的尾后,再将插入元素存放在第k个位置,这样可以使得数组的插入操作时间复杂度始终保证为O(1),但是我们要知道,这种方法改变了数组当中其他元素的位置。
  2. 与插入数据类似,如果我们要删除数组当中的第k个位置的数据,为了保证内存的连续性,也需要搬移数据,不然数组当中就会出现空洞,内存也就不再连续了。在实际中的某些特殊场景下,我们并不一定非得追求数组中数据的连续性。如果我们将多次删除操作集中在一起执行,删除的效率会提高很多。比如我们需要依次删除数组中的第1、2、3个元素时,我们可以先记录下已经删除的数据。每次的删除操作并不是真正的搬移数据,只是记录数据已经被删除的位置。当数组没有更多空间存储数据时,我们再触发并执行一次真正的删除操作,这样就大大减少了删除操作导致的数据搬移。

总结可知,之所以数组具有低效的"插入"和"删除"特点,是由于数组本身要保持其自身的顺序性和连续性,当抛开这两点不谈时,数组低效的"插入"和"删除"的缺点也就无从谈起。

  • 大小固定

由于数组在创建的一开始需要指明其大小,这样才能够为其申请一块连续的存储空间,因此数组的大小是固定的。当数组内存储数据满后,就要涉及到数组扩容的操作,需要申请一块更大的空间,然后将原数据搬移到新的空间中,这样才能继续插入数据。

  • 数组下标越界问题

在C++中,通过下标访问不存在的元素是一种未定义行为,这会产生很严重的后果。所谓缓冲区溢出(buffer overflow)指的就是这类错误,这也是导致PC及其他设备上应用程序出现安全问题的一个重要原因。并且这类错误是编译器无法识别的,所以就需要我们程序员在编写代码时,注意确保数组的下标合法化。

复习<<数据结构与算法——链表>>

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成,下面我们来看一下链表的特性。

  • 支持纯天然的动态扩容

由于链表本身存储上非连续的特点,因此不需要为其申请指定的一块空间,当插入数据时候,只需动态的为新数据申请空间,然后通过指针将其与链表联系起来即可。

  • 高效的"插入"和"删除"操作

同样的由于链表本身存储上非连续的特点,使得链表对于指定位置结点的插入与删除操作变得十分高效,只需考虑操作位置结点的前后结点,以及操作结点本身这三个结点即可。无需进行大量的数据搬移以维持连续性。

  • O(n)时间复杂度的查找操作

由于链表本身并不是顺序存储在一段连续的空间,因此无法支持像数组那样高效的随机访问特性,当我们在链表当中按值查找某一个结点时,我们只能通过从头往后依次遍历每一个结点,然后判断是否为待查找结点这样的方式来进行数据的查找。 

每天反复练习剑指offer上的五道题

字节跳动 Fighting Day1目标:

  • 剑指offer(一):二维数组中的查找
  • 剑指offer(六):旋转数组中最小数字
  • 剑指offer(十三):调整数组顺序使奇数位于偶数前面
  • 剑指offer(二十八):数组中出现次数超过一半的数字
  • 剑指offer(三十):连续子数组的最大和

                                                                                                                                                                    一起加油跑腿的小徐

                                                                                                                                                                                   2019/5/14

发布了37 篇原创文章 · 获赞 42 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_42570248/article/details/90211369
今日推荐