Data Structure & Algorithm

189 阅读5分钟

算法题总结:

线性表

时间复杂度

程序关键步骤需要执行的次数 如一个函数为sum = n(n+1)/2,他的时间复杂度为O(1),而不是O(n2n^2),因为这个方法关键语句永远只执行一次

常见的时间复杂度有:

O(1) O(lognlogn) O(n) O(n2n^2) O(n3n^3) O(2n2^n) O(n!n!)

注意:

  1. 只看最高复杂度的运算,低阶的忽略
  2. 复杂度前面的常熟系数可以忽略,如O(n)和O(2n)都可以归为O(n)

常见算法时间复杂度随着输入N的变化曲线

复杂度分析的难点:递归——递归函数的复杂度可以借助把函数的执行过程用树状标示,成为递归树。常用的递归算法的时间复杂度通过主定理来推导,其结果为:

补充一句,对于二叉树的遍历时间复杂度可以这么理解,无论是哪种顺序的遍历方式,每个二叉树的节点都会被访问且仅仅被访问一次,所以时间复杂度为O(n),同理可得图的遍历、深度优先搜索、广度优先搜索时间复杂度为O(n)

空间复杂度

空间复杂度的分析相对简单,可以通过下面的思考过程推导:

  1. 如果程序中使用了数组,则其长度n就是程序的空间复杂度,如果是二维数组,则其长度n2n^2就是程序的空间复杂度
  2. 如果程序中使用了递归,则递归函数的调用深度就是程序的空间复杂度
  3. 如果程序中即出现了递归又出现了数组,那么两者的最大值就是程序的空间复杂度

实践指导

面试过程中,处理一个算法问题时,我们需要有这样的解决问题的框架:

  1. 完全理解问题的含义
  2. 想出所有的解决方案
  3. 比较每个方案的时间和空间复杂度
  4. 给出目前的最优解

实际开发中,编写一个程序时,我们需要这样的习惯:

  1. 构思功能时,能够大致计算出一个程序的时间和空间复杂度
  2. 尽可能地用最简洁的时间和空间复杂度完成一个程序

常见的数据结构操作时间&空间复杂度

线性表

顺序表

优点:尾部插入效率高,支持随机访问。

缺点:中间插入或删除效率低

典型应用:Arraylist 面试问题:

  • ArrayList的大小是如何自动增加的? 添加元素时如果没有指定元素下标,会自动在尾部增加元素,否则会涉及大量元素的成批移动 增加元素时会判断当前容量是否足够,不够的话会自动扩容成两倍
  • 什么情况下你会使用ArrayList? 如果数据需要被排序,或者经常被增删,不要用Arraylist,当对数据对操作是顺序查找为主时候,或者单独在尾部增加时候,建议使用。
  • 在索引中ArrayList的增加或者删除某个对象的运行过程?效率很低吗?解释一下为什么? 是的,因为涉及大量元素的移动
  • ArrayList如何顺序删除节点? 使用迭代器,如果用for循环需要从尾部开始删除,否则会出现下标异常 arrayList的遍历方法 迭代器,for循环,foreach

链表

直接上总结

跳表

一种删除、插入、查询时间复杂度都是O(lognlogn)的数据结构,跳表最大的优势就是实现简单、方便拓展、效率更高,因此在一些热门项目中用来替换平衡树,如Redit(服务器缓存的),LevelDB等

跳表的实现结构

跳表的时间复杂度计算: 一共有k级索引,最高的第k级索引有两个结点,即n/2k2^k = 2那么k = log2nlog2n - 1, 时间复杂度就是以跳表的索引高度表示,O(lognlogn). 在插入和删除的过程中,涉及整个索引结构的改变,其时间复杂度也是O(lognlogn)。由此可见跳表的维护成本比较高。

跳表的空间复杂度计算: 假设每两个结点抽取一个数据,构成索引,那么链表所有元素所占空间为:n+n/2+n/4+...+2,最终结果是收敛的也是O(n),虽然和原始链表空间复杂度是一个量级,但实际中肯定要多不少。

跳表中为了提高增删元素的效率,借鉴了升维的思想。算法中,升维和空间时间互换是常用的思路。

树和图的最大区别是节点之间有没有形成环,链表就是特殊化的树,树就是特殊化的图

二叉搜索树:

二叉搜索树的查询、操作都是O(lognlogn)的时间复杂度

二叉搜索树的中序遍历是増序的

递归是不是效率比较低 这个说法不严谨,因为递归用得不好导致程序的时间复杂度指数级别上升才导致效率低,如果结合上中间结果的缓存,加上合理规划递归的深度,时间复杂度是可控的,且递归在计算机中实现无非是处理器给程序多开栈空间,现代CPU和编译器对于递归、特别是尾递归的优化很出色,我们可以直接认为递归和循环的执行效率是一致的。

解决算法问题有两个确定方案 1、升维 2、时间空间互换