如何衡量一个算法的好坏
一个程序计算的快慢与算法效率息息相关, 算法效率从两方面来看, 一种是时间效率, 一种是空间效率, 前者衡量一个算法的运行速度, 后者衡量一个算法所需要的空间, 在计算机发展早期, 计算机存储容量很小, 很在乎空间复杂度, 如今计算机存储容量达到很高的高度, 如今重点关注算法的时间复杂度.
大O的渐进表示法
时间复杂度和空间复杂度的表示使用大O的渐进表示法
基本格式: O() <--- 括号里面写计算出来的数学函数
表示方法
1> 用 1 表示运行中的常数
2> 只保留最高阶项
3> 去掉最高阶项的系数
大O的渐进表示法去掉了对结果影响不大的项, 表达更简洁
时间复杂度
定义: 算法的时间复杂度是一个数学函数, 描述了该算法的运行时间,理论上是将每个算法的都在电脑上跑过之后计算得出花费了多少时间, 但上述方法实际不成立, 所以用算法中基本操作的执行次数作为算法的时间复杂度.
在一个算法中时间复杂度存在最好, 平均 和 最坏情况
比如: 在N个数据中查找数字 X, 可能第 1 次找到, 也可能 N 次找到, 平均情况是 N/2;
我们关注的是最坏情况:所以时间复杂度为: O(N)
举出几个例子:
//count++了 10 次, 只有常数项的话, 时间复杂度为: O(1)
void func3(int n) {
int count = 0; //这次忽略不计了
for(int i = 0; i < 10; i++) {
count++;
}
}
//count++了 (2*n) 次, 将最高阶项系数省略, 时间复杂度为 O(N)
void func2(int n) {
int count = 0; //这次忽略不计了
for (int i = 0; i < 2*n; i++) {
count++;
}
}
//count++了 (n^2+2*n+10)次, 由大O()的渐进表示法的表示规则中,
//我们保留最高阶的表达式, 只保留n^2即可, 所以时间复杂度为 O(N^2)
void func1(int n) {
int count = 0; //这次忽略不计了
for(int i = 0; i < n; i++) {
for(int j = 0; j < n; j++) {
count++;
}
}
}
//该方法是一个冒泡排序, 设array数组长度为 n , 最坏情况下总共比较 (n-1) 次,
//第一次比较了 (n-1)个, 第二次比较了 (n-2)次, 以此类推
//最后一次比较了1次, 这是个等差数列, 总共进行了多少次比较, 1+2+...+(n-1),
//等差数列公式得出(n*(n-1))/2, 咱们的时间复杂度表达式为
//O(N^2)
void func4(int[] array) {
for(int i = 0; i < array.length-1; i++) {
boolean flg = true;
for(int j = 0; j < array.length-1-i; j++) {
if(array[j] > array[j+1]) {
int temp = array[j];
array[j] = array[j+1];
array[j+1] = temp;
flg = false;
}
}
if(flg) {
break;
}
}
}
//对于二分查找的算法来说,每次查找都是对半平分这个数组,
//假设这个数组长度为n, 最坏情况下, 我们花了 x 次找到这个数据,
//那么可以得到这样的表达式: 2^x = n; 由此得出 x = log(n), 这个底数为2;
//得到时间复杂度为: O(log(N))
int func5(int[] array, int value) {
int begin = 0;
int end = array.length-1;
while(begin<=end) {
int mid = (begin+end)/2;
if(value > array[mid]) {
begin = mid+1;
}else if (value < array[mid]) {
end = mid-1;
}else {
return mid;
}
}
return -1;
}
每一次递归都在栈上开辟一块空间
递归的时间复杂度 = 递归的次数 * 每次递归后执行的次数
//这个是求阶乘的一个算法
//有递归的公式可以得出时间复杂度为: O(N)
long func6(int n) {
return n < 2 ? n : func6(n-1) * n;
}
//这个求斐波那契数列的算法的时间复杂度为 O(2^N)
int func7(int n) {
return n < 2 ? n : func7(n-1) + func7(n-2);
}
我们来看这个代码为啥是 O(N^2)
有这两张图可以得出我们每次递归的次数依次递增, 共递归了 n 次
将他们依次相加得到: 2^0 + 2^1 + 2^2 +.....+ 2^(n-1) , 使用等比数列求和公式得到:
(2^0 - 2*2^(n-1))/ (1 - 2)
该算法的时间复杂度为: O(2^N)
空间复杂度
空间复杂度是一个算法在运行过程中临时占用存储空间大小的度量, 不是程序占用了多少 bytes 的空间.
举个例子:
//该方法只是中的变量可以申请空间, 如 int i , boolean flg 等, 这些只是常数, 记作 1
//可能有人疑问, i 加入循环了10000次呢
//在这个循环面每次申请空间都是那一处, 只是上面的数据不同了
//所以空间复杂度为: O(1)
void func4(int[] array) {
for(int i = 0; i < array.length-1; i++) {
boolean flg = true;
for(int j = 0; j < array.length-1-i; j++) {
if(array[j] > array[j+1]) {
int temp = array[j];
array[j] = array[j+1];
array[j+1] = temp;
flg = false;
}
}
if(flg) {
break;
}
}
}
//求第n个斐波那契数字
//在这个过程中申请了一个非常长的数组, 所以空间复杂度为: O(N)
long[] fibonacci(int n) {
long[] fibArray = new long[n+1];
fibArray[0] = 0;
fibArray[1] = 1;
for(int i = 2; i <= n; i++) {
fibArray[i] = fibArray[i-1] + fibArray[i-2];
}
return fibArray;
}
//这个求斐波那契数列第n个数
//每一次递归在栈上开辟了一个空间
//所以空间复杂度为: O(N)
int func7(int n) {
return n < 2 ? n : func7(n-1) + func7(n-2);
}
小结:
见到比较多的复杂度: O(logN) O(N) O(N*logN) O(N^2) ---> 依次增大
理解复杂度表达的含义