1. 如何衡量代码质量?
计算机两个核心的功能就是计算和存储,所以我们衡量代码也是从这两个角度,一个是计算时耗费的时间,一个是计算时使用的存储空间(这里指使用的内存大小)。
比较直观的方法就是,运行一下,跑一跑就知道了。占用了多少内存,耗费了多少时间,统计一下,既直观又准确。这种方法不是不可以,只是受运行环境和数据规模影响比较大,参考性很难统一。
所以,我们引入了大O表示法,可以在不运行的情况下,对代码做一个预估。下面我们说一下时间复杂度:
首先我们要假设每一行代码操作的时间是相等的,为一个单位时间。第二,大O表示法表示的是随着数据规模的增长,代码运行时间增长的趋势。第三,由于大O表示法关注的是趋势,当数据规模很大的时候,常数项的影响很小,所以我们通常忽略大O表示法公式的常数项。
所以,我们可以发现,大O表示法只是一个估计的趋势,供我们参考,不能代表真正的运行时间。
举个例子:
public int doubleSum(int n) {
int sum = 0;
for (int i = 0; i < n; i++) {
sum += i;
}
for (int i = 0; i < n; i++) {
sum += i;
}
return sum;
}
在这段代码中,我们可以看到有两个for循环,执行2n次,所以整个代码执行2+2n个单位时间,我们表示为O(2+2n),当n很大的时候,两个2就显得微不足道了,所以这段代码的时间复杂度是O(n)。
这里我们发现了一个技巧,常数项最后都要被忽略掉,所以,我们在分析的时候,就可以只关注代码中的循环语句了,而且,不管程序中出现了几次并列的循环,我们都看成是一次。
2. 常见的时间复杂度
1) o(1)
我们前面说过,大O表示法是估计的趋势,所以,O(1)就是没有增长的趋势,也就是不管数据规模怎么变大,执行的时间都不变。
2) o(n)
刚才我们举过栗子了,一个n次的循环,时间复杂度就是O(n)。
3) o(logn)
i=1;
while (i <= n) {
i = i * 2;
}
这段代码,根据高中数学知识,我们可以知道这段代码会运行logn次。
4) o(n^2)
这个也很好理解,一个n次的循环,时间复杂度是o(n)。那么,一个嵌套循环,时间复杂度就是o(N^2)了。
3. 加法和乘法
下面的例子,指向时间是n+n^2
个单位时间,随着数据规模增大,n影响很小,我们忽略。这段代码的时间复杂度为O(n^2)。这就是加法法则,总复杂度等于量级最大的代码的复杂度。
public int sum(int n) {
int sum = 0;
for (int i = 0; i < n; i++) {
sum += i;
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
sum += i;
}
}
return sum;
}
乘法法则是针对嵌套的情况而言的,嵌套代码的复杂度等于嵌套内外代码复杂度的乘积。这也是O(n^2)复杂度的由来。