分为时间复杂度和空间复杂度
写在前面:
数据规模:个人理解:指运行该代码块运算出的数据的数量级
如果要想在1s之内解决问题:
O(n^2)的算法可以处理大约10^4级别的数据
O(n)的算法可以处理大约10^8级别的数据
O(n*log*n)的算法可以处理大约10^7级别的数据 //log10^7~7 log210^7~20
定义:忽略常数,低阶和系数的复杂度分析法,
代码每行运行的时间用unit-time表示,所有代码的执行时间 T(n) 与每行代码的执行次数 n 成正比。
总结为一个公式:T(n)=O(f(n))
ps:T(n) 表示代码执行的时间;n 表示数据规模的大小;f(n) 表示每行代码执行的次数总和。 O表示代码的执行时间 T(n) 与 f(n) 表达式成正比。
int x = 1; /*需要1*unit-time*/ int y = 2; /*需要1*unit-time*/ for(int i = 0; i < n; i++) { x *= i; }/*循环了n次,需要n*unit-time*/ for(i = 0; i < n; i++) { for(int j = 0; j < n; j++) { y *= j; } } /*双重循环,故需要n*n*unit-time*/
则对于上述代码,根据大o复杂度表示法:O(1)+O(1)+O(n)+O(n²)→O(n²)
tips:
(1)而大 O复杂度并不是表示实际执行的时间,而是表示代码时间随数据规模变大的增长趋势
(2)而当n趋于无穷大时,依然是抓大头的思想,取最大量级的(即对于一段代码,只关注循环次数最多的一段代码,即为该段代码执行次数的核心量级)
(3)加法法则:总复杂度等于量级最大的那段代码的复杂度(因为对于一段代码,可能会有多个量级的复杂度)
(4)乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积(例如双重循环嵌套的形式,时间复杂度即为O(n²))
for(i = 0; i < n; i++) { for(int j = 0; j < n; j++) { y *= j; } } /*双重循环,故需要n*n*unit-time*/
几种常见的时间复杂度分析:
(1)O(1):这是常量级的表示方式,而不是指只执行了一行代码
(2)O(logn)、O(nlogn):
O(logn)
x = 1; while(x < n) { x *= 2; }
因为根据定义,复杂度表示该块代码被执行的次数,则2的x次方 = n;所以x = log2(n),复杂度即为O(log2(n));
tips:实际上,不管是以 2 为底、以 3 为底,还是以 10 为底,我们可以把所有对数阶的时间复杂度都记为 O(logn)。我们知道,对数之间是可以互相转换的,log3n 就等于 log3(2) * log2(n),所以 O(log3n) = O(C * log2n),其中 C=log32 作为一个系数可以忽略,所以O(log2n) 就等于 O(log3n)。因此,在对数阶时间复杂度的表示方法里,我们忽略对数的“底”,统一表示为 O(logn)。
O(nlogn)
归并排序、快速排序的时间复杂度都是
即为n²的那种形式
(3)O(m+n)、O(m*n)
f(n,m) { int i = 0; int j = 0; int sum1 = 0, sum2 = 0; for(; i < n; i++) { sum1 += i; } for(; j < m; j++) { sum2 += j; } return sum1 + sum2; }
因为m,n的大小关系并不缺定,不能像前面的法则那样是省略掉其中一个。所以我们需要将加法规则改为:T1(m) + T2(n) = O(f(m) + g(n))。但是乘法法则继续有效:T1(m)*T2(n) = O(f(m) * f(n))。
(4)线性阶:O(n)例如:一重循环;
(5)最好和最坏时间复杂度
最好情况时间复杂度就是,在最理想的情况下,执行这段代码的时间复杂度。就像我们刚刚讲到的,在最理想的情况下,要查找的变量 x 正好是数组的第一个元素,这个时候对应的时间复杂度就是最好情况时间复杂度。
最坏情况时间复杂度就是,在最糟糕的情况下,执行这段代码的时间复杂度。就像刚举的那个例子,如果数组中没有要查找的变量 x,我们需要把整个数组都遍历一遍才行,所以这种最糟糕情况下对应的时间复杂度就是最坏情况时间复杂度。
example
int i; for(i = 0; i < n; i++) { if(i > -1)/*最好情况:O(1)*/ { return i; } else/*最坏情况:O(n)*/ { i *= 2; } }
时间复杂度的分析方法:
1、时间复杂度就是函数中基本操作所执行的次数 2、一般默认的是最坏时间复杂度,即分析最坏情况下所能执行的次数 3、忽略掉常数项 4、关注运行时间的增长趋势,关注函数式中增长最快的表达式,忽略系数 5、计算时间复杂度是估算随着n的增长函数执行次数的增长趋势 6、递归算法的时间复杂度为:递归总次数 * 每次递归中基本操作所执行的次数
求解算法的时间复杂度的具体步骤:
⑴ 找出算法中的基本语句; 算法中执行次数最多的那条语句就是基本语句,通常是最内层循环的循环体。 ⑵ 计算基本语句的执行次数的数量级; 只需计算基本语句执行次数的数量级,这就意味着只要保证基本语句执行次数的函数中的最高次幂正确即可,可以忽略所有低次幂和最高次幂的系数。这样能够简化算法分析,并且使注意力集中在最重要的一点上:增长率。 ⑶ 用大Ο记号表示算法的时间性能。 将基本语句执行次数的数量级放入大Ο记号中
写在前面:
一个算法在计算机上占用的内存包括:程序代码所占用的空间,输入输出数据所占用的空间,辅助变量所占用的空间这三个方面,程序代码所占用的空间取决于算法本身的长短,输入输出数据所占用的空间取决于要解决的问题,是通过参数表调用函数传递而来,只有辅助变量是算法运行过程中临时占用的存储空间,与空间复杂度相关
定义:
算法的空间复杂度S(n)定义为该算法所耗费空间的数量级。(即:对一个算法运行过程中占用的临时内存大小的量度)表示为 S(n)=O(f(n))
tips:
(1)算法的空间复杂度并不是计算实际占用的空间,而是计算整个算法的辅助空间单元的个数,与问题的规模没有关系。 (2)S(n)=O(f(n)) 若算法执行时所需要的辅助空间相对于输入数据量n而言是一个常数,则称这个算法的辅助空间为O(1); (3)递归算法的空间复杂度:递归深度N*每次递归所要的辅助空间, 如果每次递归所需的辅助空间是常数,则递归的空间复杂度是 O(N).
(4)通常来说,只要算法不涉及到动态分配的空间,以及递归、栈所需的空间,空间复杂度通常为0(1);
几个类别:
1.对于分支结构来说,它的时间复杂度是O(1)
int i = 0, j = 1; int temp = 0; if(i > j) temp = i; else temp = j;
2.二分法:O(1)
二分可以被用于查找 有序数组 中的某一个特定元素,其工作原理是查找一个 既定值,target 从 数组, arr 的 中点,mid 开始查找,判断 a r r [ m i d ] arr[mid]arr[mid] 的值,存在以下三种情况:
如果 t a r g e t = a r r [ m i d ] target = arr[mid]target=arr[mid],则找到 target,直接返回 mid 如果 t a r g e t > a r r [ m i d ] target > arr[mid]target>arr[mid],则代表 target 坐落于 [ 0... m i d ) [0...mid)[0...mid) 区间 如果 t a r g e t < a r r [ m i d ] target < arr[mid]targettarget = 1;
arr[7]={1 4 10 12 17 18 21 25}
max = arr.length - 1;
min = arr[0];
mid = (0 + 7) / 2 = 3.5 →3;
for 0 to 3;
arr[3] = 12 > 1;
for 0 to (0 + 2) / 2 →1
arr[1] = 4 > 1
for 0 to (0 + 1) / 2 →0
arr[0] = 1 == 1
return mid;