1.时间复杂度
1.为什么要引入时间复杂度?
因为在运行程序时,环境不同(电脑配置,版本等不同),则具体的运行时间就不同,为了排除硬件条件的影响,我们引入时间复杂度,时间复杂度计算的是某个程序基础操作的执行次数,这也很容易理解,基础操作执行的次数越多,程序运行的自然就慢,通过计算时间复杂度我们便可知晓这个程序的运行效率
int M=100,N=60;
int count=0;
public void forsth(){
for(int i=0;i<N;i++){
for(int j=0;j<M;j++){
count++;
//基础操作为count++
//那么时间复杂度就是算count++执行了多少次
//时间复杂度为O(M*N)
}
}
}
2.时间复杂度的表示
时间复杂度只是用来估算一个程序的用时
因此,当我们写出一个程序基础操作的次数的表达式时,我们只取影响最大的项(最高项),且系数取1(即若求出基础操作需要执行为2N次,那么就表示为O(N))
int M=100,N=60;
int count=0;
public void forsth(){
for(int i=0;i<N;i++){
count++;
for(int j=0;j<M;j++){
count++;
//这个程序中,基础操作count++就执行了M*N+N次
}
}
}
执行了M*N+N次,显然M*N是一个二次项,N是一个一次项,故舍去N
时间复杂度,表示为O(M*N)
另外,时间复杂度是一个悲观的预期
public void findFive(int [] nums){
for(int i=0;i<nums.length;i++){
if(nums[i]==5){
System.out.println(i);
return;
}
}
}
//如果这个数组是int[] nums={4,2,6,4,3,2,5};
//那么就需要找7次
//而如果数组是int[] nums={5,4,2,6,4,3,2};
//那么就只需要找一次
//我们规定在不确定需要多少次基础操作时,取最大值,即最差的情况
//如果这个数组里有N个数,以这样的方法找数组中的5,它的时间复杂度为O(N)
3.通过计算时间复杂度感受一下二分查找的牛逼之处
在有序数组中查找一个数的朴素方法
public void findNum(int [] nums,int num){
for(int i=0;i<nums.length;i++){
if(nums[i]==num){
System.out.println(i);
}
}
//时间复杂度为O(N)
//从头到尾找遍整个数组,判断是不是要找的数字
}
二分查找
二分查找思想:
每次排除剩余部分的一半,直到最后只剩满足条件的那一个
ex:小张从图书馆借了一堆书,在出图书馆的时候报警器响了,小张打算一本一本试试到底是哪本书造成了报警,但是保安轻蔑一笑,先是把那一堆书分成了两堆,让他们分别通过报警器,再把让报警器响的那堆书再分成两堆,再分别通过警报.....直到找到那本让警报器报警的书
二分查找代码示例:
public static int binarySearch(int[] arr, int target) {
int left = 0;
int right = arr.length - 1;
while (left <= right) {
int mid = (left + right) / 2;
// 如果找到目标值,则返回索引
if (arr[mid] == target) {
return mid;
}
// 如果目标值比中间值小,说明在左半部分
else if (arr[mid] > target) {
right = mid - 1;
}
// 否则目标值比中间值大,说明在右半部分
else {
left = mid + 1;
}
}
// 目标值不在数组中,返回 -1
return -1;
}
二分查找的时间复杂度如何计算?
二分查找的基本操作:找到中间数,与所找数进行比较(把书分堆)
有N本书,被分了x次后,成了一本书----N/(2^x)=1,则x=log2 N,时间复杂度为O(log N)
这样的时间复杂度是个什么概念?
如果需要在1000个数中找一个数,普通方法可能要找1000次
但是二分查找法只需要10次
更夸张的是,当要在1000000个数中找一个数,二分法只要20次,普通方法要1000000次
在1000000000个数中找一个数,二分法只要30次,普通方法要1000000000次
但是二分法的使用很受限制
二分查找的使用条件:有序,只查找一个
所以面对无序的数组,它需要先排序,排序的代价是很大的,下面我们顺便看一下冒泡排序和快速排序的时间复杂度
冒泡排序
冒泡排序的思想:
冒泡法的基本操作:相邻数大小的比较
如果一共有n个数,那么冒泡法的时间复杂度为n-1,n-2,n-3,n-4......1的和,即n*(n-1)/2,O(N^2)
快速排序
快速排序的思想:设基准,分左右
通过与基准的比较,基准数的位置是可以完全确定的,同时又可以让原本完全无序的数列稍有顺序
因此,会比通过相邻数对比,确定一个数的位置的冒泡排序法稍有改进
2.空间复杂度
相比时间复杂度,对空间复杂度人们往往没有那么追求低空间复杂度
根据摩尔定律,每隔18个月,硬件性能就增加一倍,所以现在的内存条件较高,因此为了时间复杂度而牺牲一点空间复杂度倒也是没什么的
空间复杂度计算的是程序运行时所需要的额外空间,计算的是额外的变量的个数
// 计算阶乘递归Fac的空间复杂度?
long long Fac(size_t N)
//最初定义的空间是存放一个int数的空间,即存放参数N
{
if(N == 0)
return 1;
return Fac(N-1)*N;
//方法的调用需要创建一个栈帧
//递归算法
}
因此它的空间复杂度为O(N)
def fibonacci(n):
if n <= 0:
return 0
elif n == 1:
return 1
else:
return fibonacci(n-1) + fibonacci(n-2)
那么你能自己算算上面这个求斐波那契数列的程序的空间复杂度吗?
你是不是觉得应该是O(2^N)?其实不然哈哈哈哈
空间是可以重复利用,不累计的时间是一去不复返的,累计的
也就是说,Fac(N-1)开辟了空间之后,在再次调用Fac(N-1)时,就用的还是这一片空间
所以空间复杂度其实是O(N)