时间复杂度和空间复杂度是我们算法效率的度量方法。
时间复杂度我们用大O表示法,比如O(1),O(n),O(logn),O(n2)等,那么这个是怎么计算出来的呢。
简单来说就是看某段代码的执行次数。
注意:
1.如果是常数级别的都是O(1),这里的常数是指的我们已经很确定这段代码执行多少次,不存在变化了。
2.指数级的时候取最高阶,比如n2+n,那么是O(n2);
3.常数我们通常忽略。
O(1):
int a = 1; for(int i = 0 ;i < 1000;i++){ a = a + 1; }
这段代码的时间复杂度就是O(1),不管他执行多少次,已经十分确定它是某一个值了
O(n):
for(int i = 0 ;i < n;i++){ a = a + 1; }
这段代码n是个未知数,因此它的时间复杂度是O(n)
O(logn):
int i = 1; while( i <= n){ i = i * 3; }
这段代码实际的执行次数,就是求3x=n,求出x=log3n(3为底n的对数),忽略掉常数3,时间复杂度表示为O(logn)
O(n2):
for(i = 0 ; i < n;i++){ for(int j = 0 ; j < n ;j ++){ a = a +1; } }
for(i = 0 ; i <= n;i++){ for(int j = i ; j <= n ;j ++){ a = a +1; } }
第一段代码很好计算直接就是O(n2);
第二段代码
i=n 运行1次
i=n-1 运行2次
i=n-2 运行3次
......
i=1 运行n次
明显是个等差数列,总次数就是等差数列求和sn=n(n+1)/2=(n2+n)/2,忽略掉常数和低阶,那么就是O(n2)
空间复杂度
其实对于空间复杂度,我们经常都会用空间去换时间,比如一个List<Student>,要根据Student的ID去list里面取元素,这种情况下我们并不知道index,每次都需要遍历判断,那么每次取元素的时间复杂度都是O(n);我们就可以将ID作为key,把List转换成Map,每次从map中取就是O(1)了,但是这里多出来了一个Map,会占用更多内存空间。这就是用空间换时间。一般我们说的复杂度都是指的时间复杂度。
算法之美案例:判断一个数是否是2的幂
算法1:
while(n > 1){ if (n % 2 == 0) { return true; } n = n / 2; } return false;
无限除以2再取模判断是否等于0,如果最后取模2等于0,那么说明是2的次幂。时间复杂度O(logn)
算法2:
if (n <= 0) { return false; } return (n & (n-1)) == 0;
2的二进制表示:10 1的二进制:1
4的二进制表示:100 3的二进制:11
8的二进制表示:1000 7的二进制:111
利用&运算,如果n是2的次幂,那么n&(n-1)刚好等于0,这个算法时间复杂度O(1)。
明显算法2的性能是比算法1的性能好的。
时间复杂度优劣排行:O(1)>O(logn)>O(n)>O(nlogn)>O(n^2)>O(n^x)