几条简单规则:
一、“有限次操作”往往时间复杂度都是O(1)
例如,交换两个数a,b的值,
public void swap(int a,int b){
int temp;
temp = a;
a = b;
b = temp;
}
通过一个中间变量,进行了三次操作,交换了其值,swap时间复杂度O(1)。其中所谓的有限次操作,是指不随数据量的增加,操作次数增加。
二、for循环中的循环往往是O(n)
例如,在n个数中寻找最大值
public int max(int[] arr){
int temp = arr[0];
for(int i =0;i<arr.length;i++){
if(arr[i]>temp){
temp = arr[i];
}
}
return temp;
}
通过一个for循环,将数组进行遍历,每次遍历,都执行有限次操作,然后一共遍历N次,和数组长度即for循环循环次数呈线性关系。
三、“树的高度”的时间复杂度往往是O(lgn)
树的总结点数是n,那么树的高度就是 lg n
在一棵包含n个元素的二分查找树上进行二分查找,时间复杂度为O(lg n)
对一个包含n个元素的堆顶元素弹出后,调整成一个新的堆,其时间复杂度也是O(lg n)
组合规则
通过简单规则的时间复杂度,来求解组合规则的时间复杂度。
例如,n个数冒泡排序
public void bubbleSort(int [] num){
for(int i =0;i<num.length;i++){
for(int j=0;j<num.length-i-1;j++){
if(num[j] >num[j+1]){
num[j] = num[j]+num[j+1];
num[j+1] = num[j]-num[j+1];
num[j] = num[j] - num[j+1];
}
}
}
}
冒泡排序可以看作是三个规则的组合:
1、外层循环 2、 内层循环、3、最内层的交换操作
所以时间复杂度应该是 O(n)*O(n) *O(1) = O(n2)
对于递归的算法
例如:计算1-n的和
public int sum(int n){
//非递归算法
int re = 0;
for(int i = 0; i<n;i++){
re+=i;
}
return re;
}时间复杂度 O(n)
public int sum(int n){
//递归算法
if (n = 1) return 1;
return n+sum(n-1);
}
我们用f(n)表示数据量为n时,算法的计算次数
当n=1时,sum函数只会计算一次,f(n) = 1
当n!=1时,f(n)=f(n-1)+1,然后不断地往下展开,
f(n-1)=f(n-2)+1
f(n-2)=f(n-3)+1
.
.
f(2)=f(1)+1
f(1) =1;左边相加,右边相加,f(n)+f(n-1)…+f(1)=f(n-1)+1+f(n-2)+1+…f(1)+1+1
也就是f(n) = n
案例2,二分查找的时间复杂度
int BS(int[] arr, int low, int high, int target){
if (low>high) return -1;
mid = (low+high)/2;
if (arr[mid]== target) return mid;
if (arr[mid]> target)
return BS(arr, low, mid-1, target);
else
return BS(arr, mid+1, high, target);
}
二分查找,单纯从递归算法来分析,怎能知道其时间复杂度是O(lg(n))呢?
仍用f(n)来表示数据量为n时,算法的计算次数:
当n=1时,bs函数只计算1次
画外音:不用纠结是1次还是1.5次,还是2.7次,反正是一个常数次。
即:
f(1)=1
在n很大时,二分会进行一次比较,然后进行左侧或者右侧的递归,以减少一半的数据量:
f(n)的计算次数,等于f(n/2)的计算次数,再加1次计算
画外音:计算arr[mid]>target,再减少一半数据量迭代
即:
f(n)=f(n/2)+1【式子B】
【式子B】不断的展开,
f(n)=f(n/2)+1
f(n/2)=f(n/4)+1
f(n/4)=f(n/8)+1
…
f(n/2(m-1))=f(n/2m)+1
上面共m个等式,左侧和右侧分别相加:
f(n)+f(n/2)+…+f(n/2^(m-1))
=[f(n/2)+1]+[f(n/4)+1]+…+[f(n/2^m)]+[1]
即得到:
f(n)=f(n/2^m)+m
再配合【式子A】:
f(1)=1
即,n/2^m=1时, f(n/2^m)=1, 此时m=lg(n), 这一步,这是分析这个算法的关键。
将m=lg(n)带入,得到:
f(n)=1+lg(n)
案例三,快速排序的时间复杂度
仍用f(n)来表示数据量为n时,算法的计算次数,很容易知道:
当n=1时,quick_sort函数只计算1次
f(1)=1【式子A】
在n很大时:
第一步,先做一次partition;
第二步,左半区递归;
第三步,右半区递归;
即:
f(n)=n+f(n/2)+f(n/2)=n+2*f(n/2)【式子B】
画外音:
(1)partition本质是一个for,计算次数是n;
(2)二分查找只需要递归一个半区,而快速排序左半区和右半区都要递归,这一点在分治法与减治法一章节已经详细讲述过;
【式子B】不断的展开,
f(n)=n+2*f(n/2)
f(n/2)=n/2+2*f(n/4)
f(n/4)=n/4+2*f(n/8)
…
f(n/2(m-1))=n/2(m-1)+2f(n/2^m)
上面共m个等式,逐步带入,于是得到:
f(n)=n+2*f(n/2)
=n+2*[n/2+2f(n/4)]=2n+4f(n/4)
=2n+4*[n/4+2*f(n/8)]=3n+8f(n/8)
=…
=m*n+2m*f(n/2m)
再配合【式子A】:
f(1)=1
即,n/2^m=1时, f(n/2^m)=1, 此时m=lg(n), 这一步,这是分析这个算法的关键。
将m=lg(n)带入,得到:
f(n)=lg(n)*n+2^(lg(n))f(1)=nlg(n)+n
故,快速排序的时间复杂度是n*lg(n)。
for循环的时间复杂度往往是O(n)
树的高度的时间复杂度往往是O(lg(n))
二分查找的时间复杂度是O(lg(n)),
快速排序的时间复杂度n(lg(n))
递归求解,未来再问时间复杂度,通杀*