- 算法效率
- 时间复杂度
- 时间复杂度的概念
- 大O 的渐进表示法
- 让我们通过代码,来了解它
- 当我们看到func1方法时,首先,要找到运行次数最多的语句
- 在实际中我们计算时间复杂度时,我们其实并不一定要计算精确的执行次数,而只需要大概执行次数,那么这里 我们使用大O的渐进表示法。
- 推导大O阶方法:
- Func1的时间复杂度为 O(N^2).
- 通过上面我们会发现大O的渐进表示法去掉了那些对结果影响不大的项,简洁明了的表示出了执行次数。
- 另外有些算法的时间复杂度存在最好、平均和最坏情况:
- 在实际中一般情况关注的是算法的最坏运行情况,
- 举个例子:
- 再来看几个代码案例
- 案例1
- 案例 2
- 案例3
- 大家在分析时间复杂度时,一定要结合思想,不能光看代码。
- 接下来,我们来分析 几个复杂 的 时间复杂度 的 程序
- 冒泡排序
- 二分查找
- 图1
- 递归
- 时间的复杂度 = 递归的次数 * 每次递归执行的次数
- 现在我们来看下面的程序(阶乘)
- 计算斐波那契递归fibonacci的时间复杂度?
- 图2
- 图3
- 空间复杂度
- 冒泡排序 的 空间复杂度
- 计算fibonacci的空间复杂度
- 计算阶乘递归Factorial的时间复杂度?
- 想要熟练的分析时间和空间的复杂度还是需要多刷题,多练习。
- 本文结束
算法效率分析分为两种:第一种是时间效率,第二种是空间效率。时间效率被称为时间复杂度,而空间效率被
称作空间复杂度。 时间复杂度主要衡量的是一个算法的运行速度,而空间复杂度主要衡量一个算法所需要的额
外空间,在计算机发展的早期,计算机的存储容量很小。所以对空间复杂度很是在乎。但是经过计算机行业的
迅速发展,计算机的存储容量已经达到了很高的程度。所以我们如今已经不需要再特别关注一个算法的空间复
杂度。
 
时间复杂度 时间复杂度的概念 时间复杂度的定义:在计算机科学中,算法的时间复杂度是一个函数,它定量描述了该算法的运行时间。一个
算法执行所耗费的时间,从理论上说,是不能算出来的,只有你把你的程序放在机器上跑起来,才能知道。但
是我们需要每个算法都上机测试吗?是可以都上机测试,但是这很麻烦,且不现实。所以才有了时间复杂度这个分析方
式。一个算法所花费的时间与其中语句的执行次数成正比例,算法中的基本操作的执行次数,为算法的时间复
杂度。
大O 的渐进表示法 让我们通过代码,来了解它 当我们看到func1方法时,首先,要找到运行次数最多的语句
public class TimeComplexityAndSpaceComplexity {
public static void func1(int N){
int count = 0;
for (int i = 0; i < N ; i++) {
for (int j = 0; j < N ; j++) {
count++;// 第一个就是 这个 count++;,它执行了多少次?
// 两个for嵌套,两个for循环N次,最外围的for循环,每遍历一个数据,嵌套在内部的for循环,就要循环 N 次。
//即 count++; 语句 被循环执行 N*N 次,
// 小技巧,找执行次数最多的语句,你就看哪里有循环就行了。
}
}
for (int k = 0; k < 2 * N ; k++) {
count++;// 这里的 count++; 被执行了 2*N
}
int M = 10;
while ((M--) > 0) {
count++;// 这里count++; 被执行了 10次
}
System.out.println(count);
}
}
经过我们分析,这句代码在程序中 一共被执行了 F(N) = N^2 + 2N + 10 在座的各位,跟着我思考一个问题: 如果我们的 N 越来越大 比如: N = 10 F(N) = 10^2 + 20 + 10 == 100 + 30 == 130 N = 100 F(N) = 100^2 + 200 + 10 == 10000 + 210 == 10210 N = 1000 F(N) = 1000^2 + 2000 + 10 == 1000000 + 2010 == 1002010 --- ---- --- ---- ---- --- 按照这样想法,后面的 2N + 10,随着N增大,就显得微不足道。
在实际中我们计算时间复杂度时,我们其实并不一定要计算精确的执行次数,而只需要大概执行次数,那么这里 我们使用大O的渐进表示法。
大O符号(Big O notation):是用于描述函数渐进行为的数学符号。
推导大O阶方法:
以func1的时间复杂度为例: F(N) = N^2 + 2N + 10
1、用常数1取代运行时间中的所有加法常数。{F(N)= N^2 +2N +1}
2、在修改后的运行次数函数中,只保留最高阶项。{F(N)=N^2}
3、如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶
假设 使用完方法 1 和 2,把该做的都做了,还剩 3* N^2,
此时,按照方法3的规则去操作(去掉3*),最终的时间复杂度为 O(N^2),
即分析这个代码的时间复杂度,在使用大O的渐进表示法以后
Func1的时间复杂度为 O(N^2).
通过上面我们会发现大O的渐进表示法去掉了那些对结果影响不大的项,简洁明了的表示出了执行次数。 另外有些算法的时间复杂度存在最好、平均和最坏情况:
最坏情况:任意输入规模的最大运行次数(上界)
平均情况:任意输入规模的期望运行次数(根据代码的情况给出平均时间复杂度)
最好情况:任意输入规模的最小运行次数(下界)
例如:在一个长度为N数组中搜索一个数据x
最好情况:1次找到
最坏情况:N次找到
平均情况:N/2次找到(可以这么理解,在中间的位置找到的。)
&ensp;
在实际中一般情况关注的是算法的最坏运行情况, 举个例子:假设数组中有N个元素,现在我们要在数组中查找一个元素,最坏的情况,该元素处在数组末尾, 也就是说 需要遍历整个数组元素。 故:数组中搜索数据时间复杂度为O(N)
再来看几个代码案例 案例1
public class TimeComplexityAndSpaceComplexity {
public static void func(int N){
int count = 0;
for (int k = 0; k < 2 * N ; k++) {
count++;// 2*N
}
int M = 10;
while ((M--) > 0) {
count++;// 10
}
System.out.println(count);
}
}
时间复杂度为 F(N) = 2*N + 10.使用大0渐进法来表示时间复杂度为 O(N)
案例 2
public class TimeComplexityAndSpaceComplexity {
public static void func(int N,int M) {
int count = 0;
for (int k = 0; k < M; k++) {
count++;// M
}
for (int k = 0; k < N; k++) {
count++;// N
}
System.out.println(count);
}
}
时间复杂度为 F(N) = N + M.使用大0渐进法来表示时间复杂度为 O(N + M)
&ensp;
案例3public class TimeComplexityAndSpaceComplexity {
public static void func(int N) {
int count = 0;
for (int k = 0; k < 100; k++) {
count++;// 100
}
System.out.println(count);
}
}
时间复杂度为 F(N) = 100.使用大0渐进法来表示时间复杂度为 O(1)
大家在分析时间复杂度时,一定要结合思想,不能光看代码。
接下来,我们来分析 几个复杂 的 时间复杂度 的 程序 冒泡排序
public class TimeComplexityAndSpaceComplexity {
public void bubbleSort(int[] array) {
for (int end = array.length; end > 0; end--) {// 执行 length 次
boolean sorted = true;// 暂不考虑优化(数组已经是有序的, 也就是说 程序在遍历一次判断一下,就结束了)
// 最好情况:时间复杂度为 O(N)
for (int i = 1; i < end; i++) { //length -1 次
if (array[i -1]> array[i]) {
Swap(array, i - 1, i);
sorted = false;
}
}
if (sorted == true) {
break;
}
}
}
}
时间复杂度是考虑最坏情况(每一个元素都需排序),不考虑优化情况和平均情况:
两个for循环嵌套length * (length - 1) == N(N-1) = N^2 - N == N^2
故 冒泡排序的空间复杂度为 O(N^2)
二分查找
public class TimeComplexityAndSpaceComplexity {
int binarySearch(int[] array, int value) {
int left = 0;
int right = array.length - 1;
while (left <= right) {
int mid = (right + left) / 2;
if (array[mid] < value) {
left = mid + 1;
} else if (array[mid] > value) {
right = mid - 1;
} else {
return mid;
}
}
return -1;
}
} 图1
图1
递归 时间的复杂度 = 递归的次数 * 每次递归执行的次数
比如说:我这个递归,递归了N次(N)。每次下去(N) 就是 一个循环,循环是循环了 N 次 那么递归的时间的复杂度 为 N * N^2 = N^3现在我们来看下面的程序(阶乘)
public class TimeComplexityAndSpaceComplexity {
long factorial(int N) {
return N < 2 ? N : factorial(N-1) * N;
}
}
虽然我们不知道该程序的递归次数,但是程序每次递归下去的时候,遇到是三目运算符。
三目运算符 不是循环,所以 每次递归的次数为1次
再来看看 程序,如果我们求的是 4 的阶乘,也就是 N==4,
N<2,那么只有 N==1时,返回 1(终止递归),然后在看后面 factorial(N-1)
每次递归减一,那么 4,3,2,1 一共 4次
也就是说 递归的次数 为 N 次,
所以 时间的复杂度 = 递归的次数 * 每次递归执行的次数 == N * 1 == N
即 O(N)
计算斐波那契递归fibonacci的时间复杂度?
public class TimeComplexityAndSpaceComplexity {
int fibonacci(int N) {
return N < 2 ? N : fibonacci(N-1)+fibonacci(N-2);
}
}
还是一样,每次进来就是一个 三目运算符,每次递归执行的次数为1, 再来看 递归的次数 假设我们想求第四个斐波那契数,那么就需要我们去计算 第3 和 第4 个斐波那契数 因为斐波那契数,从第三位数开始,该数 等于 自身前两个数之和。 那么 第3个斐波那契数 ,需要 第1和第2个斐波那契数 第2个斐波那契数 需要 第1个斐波那契数,(因为N<2,是终止条件) 至于,第1个斐波那契数,就不需要再计算了(终止了) 第4个斐波那契数,需要 第3 和 第2个斐波那契数 第3个斐波那契数需要 第2 和 第1 个斐波那契数,第2个斐波那契数,需要第1个斐波那契数,(因为N<2,是终止条件) 至于,第2个斐波那契数,需要第一个斐波那契数 (因为N<2,是终止条件) 见 图 2 斐波那契数的 时间复杂度 见图3图2 图3
空间复杂度
空间复杂度是对一个算法在运行过程中 临时 占用存储空间大小的量度 。空间复杂度不是程序占用了多少bytes的空间 因为这个也没太大意义,所以空间复杂度算的是变量的个数。 空间复杂度计算规则基本跟实践复杂度,类似,也使用大O渐进表示法。冒泡排序 的 空间复杂度
public class TimeComplexityAndSpaceComplexity {
public void bubbleSort(int[] array) {
for (int end = array.length; end > 0; end--) {
boolean sorted = true;
for (int i = 1; i < end; i++) {
if (array[i - 1] > array[i]) {
Swap(array, i - 1, i);
sorted = false;
}
}
if (sorted == true) {
break;
}
}
}
}
冒泡排序的整个运行的过程,没有说随着问题的规模,我们定义的变量也在增多。
也就说 冒泡排序的空间复杂度为 O(1)
有的人可能会说 sorted 在每次循环的时候,都会创建。
但是请注意 sorted 变量只有1个,你不能说我们循环10次,定义了10个 sorted
每次循环结束 变量空间是会被回收的,循环开始再创建一个。
也就是说 sorted 自始至终都是一个。
至于数组,不算。注意空间复杂度解释的题干中的 “临时” ,你可以理解为另外消耗的,或者说括号里的变量
这么说吧,数组是冒泡排序必须品,而 sorted 只是临时创建的,为了辅助这个程序的运行
所以”临时“的变量个数,只有sorted一个
故 该冒泡排序的空间复杂度为 O(1)
计算fibonacci的空间复杂度
public class TimeComplexityAndSpaceComplexity {
int[] fibonacci(int n) {// 计算斐波那契数的第N项
long[] fibArray = new long[n + 1];
n如果越大,数据就越大,你的结果要存入这个数组里(这里的n+1,是因为下标,自己品)
下面的数组元素,最后都是要存入数组的,也就是说 下面生成的临时变量,都是存入数组的
即元素个数,就是 临时变量的个数,即求斐波那契数 的 空间复杂度 为 O(N+1)
再根据大O渐进法,空间复杂度的最终结果: O(N)
fibArray[0] = 0;
fibArray[1] = 1;
for (int i = 2; i <= n ; i++) {
fibArray[i] = fibArray[i - 1] + fibArray [i - 2];
}
return fibArray;
}
}
计算阶乘递归Factorial的时间复杂度?
public class TimeComplexityAndSpaceComplexity {
long factorial(int N) {
return N < 2 ? N : factorial(N-1)*N;
}
}
递归的空间复杂度有点不一样。
每递归一次,函数就要在栈上开辟一块内存
也就是说递归一次,空间复杂度为 O(1)
递归N次,空间复杂度为 O(N)
即 阶乘递归函数的 空间复杂度为 O(N)
想要熟练的分析时间和空间的复杂度还是需要多刷题,多练习。
本文结束


