时间复杂度
时间复杂度的概念
时间复杂度的定义:在计算机科学上,算法的时间复杂度是一个数学函数,它定量描述了该算法的运行时间。一个算法所花费的时间与其中语句的执行次数成正比,算法中基本操作的执行次数,为算法的时间复杂度。
大O的渐进表示法
public class Test {
//请计算一下func1基本操作执行了多少次
void func1(int N){
int count = 0;
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
count++; //执行了N^2次
}
}
for (int i = 0; i < 2 * N; i++) {
count++; //执行了2N次
}
int M = 10;
while((M--)>0){
count++; //执行力10次
}
}
}
func1 执行的基本操作次数:
F(N) = N2+2N+10
- N = 10,F(N) = 130
- N = 100, F(N) = 10210
- N = 1000,F(N) = 1002010
实际我们计算时间复杂度时,并不要计算精确的执行次数,只需要大概执行次数,所以我们使用大O渐进表示法
大 O 符号:是用于描述函数渐进行为的数学符号
推导大O阶方法
- 用常数1取代运行时间中的所有加法常数
- 在修改后的运行次数函数中,只保留最高阶项
- 如果最高阶项存在且不是1,则去除与这个项相乘的常数,得到的结果就是大O阶
使用大O的渐进表示法以后,func1的时间复杂度为:O(N2)
- N = 10,F(N) = 100
- N = 100, F(N) = 10000
- N = 1000,F(N) = 1000000
通过上面可以发现大O的渐进表示法去掉了那些对结果影响不大的项,简洁明了地表示出了执行次数。另外一些算法的时间复杂度存在最好、平均和最坏情况:
最坏情况:任意输入规模的最大运行次数(上界)
平均情况:任意输入规模的期望运行次数
最好情况:任意输入规模的最小运行次数(下界)
例如:在一个长度为N的数组搜索一个数据x
最好情况:1次找到
最坏情况:N次后找到
平均情况:N/2次找到
在实际中一般情况关注的是算法的最坏运行情况,所以数组中搜索数据时间复杂度为O(N)
常见时间复杂度举例
实例1:
void func2(int N){
int count = 0;
for (int i = 0; i < 2 * N; i++) {
count++; //执行了2N次
}
int M = 10;
while((M--)>0){
count++; //执行力10次
}
System.out.println(count);
}
F(N) = 2N + 10
大O渐进表示法:O(N)
实例2:
void func3(int N, int M){
int count = 0;
for (int i = 0; i < N; i++) {
count++; //执行了N次
}
for (int i = 0; i < M; i++) {
count++; //执行了M次
}
System.out.println(count);
}
F(N,M) = M + N
大O的渐进表示法O(M+N)
实例3:
void func4(int N){
int count = 0;
for (int i = 0; i < 100; i++) {
count++; //执行了100次
}
System.out.println(count);
}
F(N) = 100
大O的渐进表示法:O(1)
实例4:
//计算冒泡排序bubbleSort的时间复杂度
//数组长度为N
void bubbleSort(int[] arrray){
for (int end = arrray.length; end > 0; end--) { //end = N,end = N-1...,end = 1
boolean sorted = true;
for (int i = 1; i < end; i++) { //执行N - 1次,执行N - 2次...,执行1次
if(arrray[i-1] > arrray[i]){
int tmp = arrray[i];
arrray[i] = arrray[i - 1];
arrray[i - 1] = tmp;
sorted = false;
}
}
if(sorted == true){
break;
}
}
}
最好的情况是N次,最坏的情况是:
F(N) = (N-1) + (N-2) + … + 1,该式为等差数列求和,所以:
F(N) = (N-1) + (N-2) + … + 1 = (N-1)*(N-1+1) / 2 = N(N-1) / 2 = N2 / 2 - N / 2
大O的渐进表示法:O(N2)
实例5:
//计算二分查找binarySearch的时间复杂度?
int binarySearch(int[] array, int value){
int begin = 0;
int end = array.length - 1;
while(begin < end){
int mid = begin + (end - begin) / 2;
if(array[mid] > value){
end = mid - 1;
}else if(array[mid] < value){
begin = mid + 1;
}else{
return mid;
}
}
return -1;
}
最好的情况是1次,最坏的情况是:
因为二分查找是折半查找,每次都是排除掉一半不适合的值。
执行第一次 找 1 / 2 * N 处中有没有value
执行第二次 找 1 / 2 * 1 / 2 * N 处有没有value
…
最坏的情况是执行第x次 找(1 / 2)x * N = 1 排除到最后一个,可能找到,也可能没找到
所以执行次数 x =
log
2
N
\log _2N
log2N
所以用大O渐进表示法时间复杂度为:O(
log
2
N
\log_2N
log2N)
实例6:
//计算阶乘递归factorial的时间复杂度?
long factorial(int N){
return N > 2 ? N : factorial(N - 1) * 2;
}
递归的时间复杂度 = 递归的次数 * 每次递归的执行次数
当N = 2,递归一次
所以递归次数为 N - 1
F(N) = (N-1) * 1 = N -1
大O的渐进表示法:O(N)
实例7:
//计算斐波那契递归fibonacci的时间复杂度
int fibonacci(int N){
return N < 2 ? 1 : fibonacci(N-1) + fibonacci(N - 2);
}
空间复杂度
空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度,空间复杂度不是程序占用了多少bytes的空间,因为这个也没多大意义,所以空间复杂度算的是变量的个数。空间复杂度计算规则基本跟时间复杂度相似,也使用大O渐进表示法。
实例1:
//计算bubbleSort的空间复杂度
void bubbleSort(int[] arrray){
for (int end = arrray.length; end > 0; end--) {
boolean sorted = true;
for (int i = 0; i < end; i++) {
if(arrray[i-1] > arrray[i]){
int tmp = arrray[i];
arrray[i] = arrray[i - 1];
arrray[i - 1] = tmp;
sorted = false;
}
}
if(sorted == true){
break;
}
}
}
使用了常数个额外空间,所以空间复杂度为O(1)
实例2:
//计算fibonacci的空间复杂度
long[] fibonacci(int n){
long[] fibArray = new long[n + 1];
fibArray[0] = 1;
fibArray[1] = 1;
for (int i = 2; i <= n; i++) {
fibArray[i] = fibArray[i - 1] + fibArray[i - 2];
}
return fibArray;
}
开辟了N个空间,所以空间复杂度为O(N)
//计算阶乘递归factorial的时间复杂度?
long factorial(int N){
return N > 2 ? N : factorial(N - 1) * 2;
}
递归调用了N- 1次,开辟了N- 1个栈帧,每个栈帧使用了常数个空间,空间复杂度为O(N)