一, 算法的大O表示法
我们在平时看到算法的时候,总会连带看到时间复杂度,空间复杂度之类的概念,对于O(n),O(n2)这种写法也不陌生,但拿我来说,对如何计算复杂度,以及对这种表示的理解其实不能说是很深入的。这种表示方法叫做大O表示法,表示了一个算法的速度有多快,表明的是一个算法在最差的情况下需要的运行步骤次数。
另外空间复杂度由于当前的内存已经变得越来越平价,所以我们更注重的是时间复杂度,一般算法中提到的复杂度也是指时间复杂度。
常见的时间复杂度有:
常数阶:O(1)
对数阶:O(log2n)
线性阶:O(n)
线性对数阶:O(nlog2n)
平方阶:O(n2)
立方阶:O(n3)
k次方阶:O(nk)
指数阶:O(2n)
上述算法复杂度不断增大, 代表算法效率也越低。
我一直有个疑惑,就是这里的对数阶的底数是多少,这里专门了解了一下:
答案就是这个常数是多少根本不重要,写代码考虑的是数据规模n对运行效率的影响,常数部分基本是可以忽略的。
同样的如果不同时间复杂的倍数是个常数,那么其实也可以近似理解为两者是同一量级的时间复杂度。
我们可以看,当底数分别是2和3时,这两个的比值是log2 N/log3 N,运用换底公式后等同于 (ln N/ln 2)/(ln N/ ln 3) = ln3/ln2,ln为自然对数,显然这与变量N无关。
所以不同底数对应的时间复杂度倍数是常数,不会随着底数改变而有不同,因此不同底数对应的时间复杂度可以被抽象为一类问题。
大部分情况下,你可以对时间复杂度判断一个大概。如果你使用了一个循环遍历了你的每一个元素,那时间复杂度就是O(n),如果是循环套循环,那就是O(n2),如果套了三个循环,那就是O(n3).当然这只是一种估算,并且时间复杂度本身是对大数据量的处理情况比较有意义,比如插入排序的时间复杂度理论上是O(n2),而归并排序是O(nlogn),但对于小数据量而言,插入排序往往会更快一点,特别是那些已经有一部分有序的数组。
二、数组
数组在内存中的地址是连续的,在一个数组中,所有的元素类型必须相同。
数组在随机读取时效率非常高,时间复杂度是O(1).
double n[ 10 ]; // n 是一个包含 10 个双精度数的数组
n[10] = 50.0; // 给某个位置赋值
三、链表
链表中的元素可以存储到内存中的任意位置,链表的每个元素中都存放了下一个元素的地址,通过这种方式把链表中的元素串在了一起。
缺点:
链表元素的读取是逐个元素的依次读取,要想拿到最后一个元素的值,必须要从第一个依次拿到最后一个的值。跳跃取值时,效率很低。
优点:
插入一个元素很方便,只需要修改前一个元素指向的地址即可。如果使用数组的话, 要把插入位置之后的元素都向后移动,如果内存不够,还要重新开辟新内存复制过去。
类似的,删除一个元素也很方便,只需要修改前一个元素的指向地址即可,如果是数组,则要把之后的元素都向前移动。
image.png
网友评论