举一个例子:
对于一个斐波那契数列我们看一看他的代码:
long long Fib(int N)
{
if(N < 3)
return 1;
return Fib(N-1) + Fib(N-2);
}
这个代码看起来是非常舒服的,非常的简洁。
但是衡量代码的算法效率是从时间和空间两个角度去决定的。
记住两句话:时间一去不复返是具有累加效果的空间是可以重复使用的就像酒店的房间,可以重复给不同的人使用,内存空间也是这样的。
基本概念:算法在编写成可执行程序后,运行时需要耗费时间资源和空间(内存)资源 。因此衡量一个算法的好坏,一般是从时间和空间两个维度来衡量的,即时间复杂度和空间复杂度。==时间复杂度主要衡量一个算法的运行快慢,而空间复杂度主要衡量一个算法运行所需要的额外空间。==在计算机发展的早期,计算机的存储容量很小。所以对空间复杂度很是在乎。但是经过计算机行业的迅速发展,计算机的存储容量已经达到了很高的程度。所以我们如今已经不需要再特别关注一个算法的空间复杂度。
概念:代码执行所需要的时间,是一个函数的值。这个函数定量的描述了算法的运行时间。那么这个具体的时间在不同的环境下肯定执行的时间是不一样的导致我们的时间复杂度无法以一个具体的时间或者时间范围去表示出来。但是我们知道一个代码的运行时间和语句执行次数成正比,那么我们就可以类比算法中的基本操作次数作为代码的时间复杂度。
总结:找到问题规模N和执行次数之间的关系式,就可以通过式子表示具体的执行次数。
// 请计算一下Func1中++count语句总共执行了多少次?
void Func1(int N)
{
int count = 0;
for (int i = 0; i < N ; ++ i)
{
for (int j = 0; j < N ; ++ j)
{
++count;
}
}
for (int k = 0; k < 2 * N ; ++ k)
{
++count;
}
int M = 10;
while (M--)
{
++count;
}
printf("%d",count);
}
F(N)=N^2+2N+10;
具体的执行次数;我们的空间复杂度。
F(N)=N^2+2N+10;这个数值比较精确当我们的N越来越大这些常数和一次就没有那么重要,只需要大概的执行次数,这就是大O的渐近表示方法。
具体操作:
1.用常数1取代所有的加法常数。
2.只保留最高价项。
3.对于最高价项是一次的就去掉相乘的乘的常数。
总结:不确定的时间复杂度取最坏的;
总结:log^n是简写:并且这个是最坏的情况。left==right区间只有一个数值了。
总结:1. 实例1基本操作执行了2N+10次,通过推导大O阶方法知道,时间复杂度为 O(N)
2. 实例2基本操作执行了M+N次,有两个未知数M和N,时间复杂度为 O(N+M)
3. 3. 实例3基本操作执行了10次,通过推导大O阶方法,时间复杂度为 O(1)
4. 实例4基本操作执行最好1次,最坏N次,时间复杂度一般看最坏,时间复杂度为 O(N)
5. 实例5基本操作执行最好N次,最坏执行了(N*(N+1)/2次,通过推导大O阶方法+时间复杂度一般看最
坏,时间复杂度为 O(N^2)
6. 实例6基本操作执行最好1次,最坏O(logN)次,时间复杂度为 O(logN) ps:logN在算法分析中表示是底
数为2,对数为N。有些地方会写成lgN。(建议通过折纸查找的方式讲解logN是怎么计算出来的)
7. 实例7通过计算分析发现基本操作递归了N次,时间复杂度为O(N)。
8. 实例8通过计算分析发现基本操作递归了2N次,时间复杂度为O(2N)
空间复杂度也是一个数学表达式,是对一个算法在运行过程中临时占用存储空间大小的量度 。
空间复杂度不是程序占用了多少bytes的空间,因为这个也没太大意义,所以空间复杂度算的是变量的个数。
空间复杂度计算规则基本跟实践复杂度类似,也使用大O渐进表示法。
注意:函数运行时所需要的栈空间(存储参数、局部变量、一些寄存器信息等)在编译期间已经确定好了,因此空间复杂度主要通过函数在运行时候显式申请的额外空间来确定。
总结:1. 实例1使用了常数个额外空间,所以空间复杂度为 O(1)
2. 实例2动态开辟了N个空间,空间复杂度为 O(N)
3. 实例3递归调用了N次,开辟了N个栈帧,每个栈帧使用了常数个空间。空间复杂度为O(N)
注意事项:
1.如果k>数组长度那么旋转字符的效果就和
2.旋转k%n的值的效果相同!
1.最后一个记录下来,前面的n-1个数值n-1赋值到最后一个,以此类推。直到第一个位置的值赋值到第二个。
2.tmp赋值到第一个位置
3.完成了一次旋转k=1的。
k>1执行多次这样的操作。
时间复杂度:O(k*N)
void rotate(int* nums, int numsSize, int k){
k%=numsSize;
while(k--)
{
int tmp=nums[numsSize-1];
//数值一定是从前面向后面移动
for(int i=numsSize-2;i>=0;i--)
{
nums[i+1]=nums[i];
}
nums[0]=tmp;
}
}
以空间换时间
0.创建一个空间,用来拷贝。
1.把后面k个拷贝到前面去,前面n-k个拷贝到后面。
2.整体再拷贝回去,(注意空间释放)
void rotate(int* nums, int numsSize, int k)
{
k%=numsSize;
//开辟一个空间
int n=numsSize;
int * pa=(int *)malloc(sizeof(int)*n);
//使用memset拷贝
memcpy(pa,nums+(n-k),sizeof(int)*k);
memcpy(pa+k,nums,sizeof(int)*(n-k));
memcpy(nums,pa,sizeof(int)*n);
//完成,释放空间
free(pa);
pa=NULL;
}
总结:空间复杂度不是是O(1)空间复杂度是O(N)?
在原来的数组上去操作:
1.后k个进行逆置
2.前面n-k个进行逆置
3.整体逆置
//交换函数
void swap(int* a, int* b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
//逆序函数
void turn(int* nums, int left, int right)
{
while (left < right)
{
swap(&nums[left], &nums[right]);
left++;
right--;
}
}
void rotate(int* nums, int numsSize, int k) {
//k有可能比较大超过了numsize相当于要取一个余数.
k %= numsSize;
//右边k个进行逆置
turn(nums, numsSize - k, numsSize - 1);
//左边n-k个逆置
turn(nums, 0, numsSize - k - 1);
//整体逆置
turn(nums, 0, numsSize - 1);
}
总结:
时间复杂度F(N)=3N—》O(N)
空间复杂度:O(1)没有额外开辟空间。
综上所述:思路三是这个题目的最好的一个方法!