做算法题,很重要的一点就是,需要分析算法的时间按复杂度和空间复杂度。
这里看一下对于时间复杂度和空间复杂度的理解
1. 时间复杂度(time complexity)
1.1 语句频度T(n): 算法执行需要的时间,理论上只能通过上机来测试出来,但是我们不可能对所有的算法都来上机测试。我们只需要知道哪个算法用时比较多即可。一个算法花费的时间与算法中基本操作语句的执行次数成正比,哪个算法中语句执行次数多,哪个算法用时就长。而一个算法中语句执行的次数叫做语句频度,计作 T(n)。
总结一下,就是说我们用 T(n) 来算法执行的时间。
1.2 时间复杂度: 在T(n)中,n 叫做问题的规模,当 n 不断变化时,语句频度 T(n) 也会变化。我们想知道T(n) 的变化规律,于是便引入了时间复杂度的概念。
在衡量时间复杂度时候,我们通常用 O(x) 的概念。这里需要知道量级的概念:
如果
这是时候,我们称作 f(n) 是 T(n) 同数量级函数,就计作 T(n)=O(f(n)),也就把 O(f(n)) 叫做算法的 渐进时间复杂度,简称 时间复杂度。
需要明白的是,语句频度T(n) 不同,时间复杂度也是可以相同的,直观理解就是说看最高次项。比如 T(n)=n^2+5n+6 和 T(n)=3n^2+n+3,时间复杂度都是
1.3 常见时间复杂度: 常数阶,对数阶,线性阶,线性对数阶,平方阶,立方阶,k次方阶,指数阶 。随着问题规模 n 的不断增大,上述时间复杂度不断增大,算法执行效率越低。
1.4 平均时间复杂度和最坏时间复杂度:
这里只需要明白,最坏时间复杂度是用来确定上界的。而且一般情况下,讨论的时间复杂度都是最坏时间复杂度。
1.5 如何求时间复杂度:
这里就每一种情况我们来给出例子和分析。
(1) 常数阶 ----算法的执行时间不随着问题规模 n 的增长而增长。即使算法中有千万条语句,最终的执行时间也不过是一个较大的常数。
public static void main(String[] args) {
int x = 91;
int y = 100;
while (y > 0) {
if (x > 100) {
x = x - 10;
y--;
} else {
x++;
}
}
}
这段代码,总共循环了 1100次,但是可以看到,跟问题规模n没有关系。所以是一个常数阶的算法。
(2)对数阶---- 这里需要推导一下。
int i = 1, n = 100;
while(i < n){
i = i * 2
}
假设这里的运行次数是T(n),,则可以求出来 ,就是线性对数阶。
(3) 线性对数阶---- 这里的证明使用归纳法
快排代码
可以看到,快排就是线性对数阶的时间复杂度。除此之外,还有归并排序和堆排序。
(4)线性阶 -----就是单层循环。
for(int i=0;i<nums.lengt;i++){
nums[i]++;
}
(5) 平方阶 ------ 就是双层循环
for(int i=0;i<nums.lengt;i++){
for(int j=i+1;j<nums.lengt;j++){
nums[i] = nums[j];
}
(6) 立方阶 ------ 就是三层循环
同上
(7) k次方阶------ k层循环
同上
(8) 指数阶 ------
2. 空间复杂度 (space complexity)
空间复杂度,是说程序运行时临时占用的空间的大小。同上面说过的时间复杂度,计作 。
一个算法除了需要存储本事所使用的指令、常数、变量和输入数据外,还需要一些对数据进行操作的工作单元和存储一些计算所需的辅助空间。总的来说,算法执行时所需的存储空间包括以下两个部分。
- 固定部分------这部分空间的大小与输入/输出的数据个数、数值无关。主要包括指令空间(代码空间)、数据空间(常量、简单变量)等所占的空间。属于静态空间。
- 可变空间------主要包括动态分配的空间,以及递归栈所需的空间等。这部分空间大小与算法有关。
举一个栗子
public void reserse(int[] a, int[] b) {
int n = a.length;
for (int i = 0; i < n; i++) {
b[i] = a[n - 1 - i];
}
}
这部分代码中,函数 reverse() 运行时,要分配的内存空间包括:引用a、引用b、局部变量n、局部变量i。
因此有 ,所以有空间复杂度
3. 总结
总的来说,我们更加关注算法的时间按复杂度。而时间按复杂度与两个因素有关:算法中的嵌套循环层数 + 最内层循环的次数
一般来说,指数的时间复杂度是不被接受的,只能在 n 比较小的时候来用,其他的多项式时间复杂度是可以接受的。