目录
1)概念
2)时间复杂度
3)大O的渐进表示法
4)分类
5)易错
6)求递归的时间复杂度
7)空间复杂度
8)补充
9)复杂度算法题
10)常见的时间复杂度及图像
(一)概念
衡量一个算法的好坏,一般是从时间和空间两个维度来衡量的,即时间复杂度和空间复杂度。
时间复杂度:衡量一个算法的运行快慢。
空间复杂度:衡量一个算法的运行所需要的额外空间。
(二)时间复杂度
时间复杂度是一个粗估值,并不是一个精确值。
算法的时间复杂度是一个函数式T(N),就像数学中函数式一样,例:f(x)=x^2+x+1。
时间复杂度可以反应程序的时间效率。
而程序的时间效率=每条语句运行时间x运行次数,其中每条语句的运行时间是由编译环境、运行环境来决定的(不确定的),是有差异的(差异小),而运行次数是确定的,为此我们可以去掉每条语句的运行时间,仅根据运行次数即可。
只需要计算程序能代表增长量级的大概执行次数,复杂度的表示通常使用大O的渐进表示法。
例1:
void func5(int n)
{
int cnt = 1;
while (cnt < n)
{
cnt *= 2;
}
}
当n=2时,执⾏次数为1
当n=4时,执⾏次数为2
当n=16时,执⾏次数为4
假设执⾏次数为x ,则2^x=n
因此执⾏次数:x = log n
(三)大O的渐进表示法
大O符号:用于描述函数渐进行为的数学符号
规则:
1.在T(N)中只保留最高阶项,去掉那些低阶项,原因是当N不断变大时,低阶项对结果的影响越来越小,当N无穷大时,就可忽略不计了。
2.去掉最高项的非1的常数项数,当N不断变大时,这个系数对结果的影响越来越小,当N无穷大时,就可忽略不计了。
3.当T(N)中没有与N相关的项时,即只有常数项时,用常数1取代所有加法常数,即只要是常数都可以忽略不计(对人来说,某个数字很大,但对于机器来说都比较小),即只要是常数项,都写成O(1)。此时的1不代表运行次数只有一次,而是代表常数项。
例:T(N)=2x^2+x+1, 根据规则第1条和第2条,则时间复杂度为O(n^2)
例:T(N)=10000,根据规则第3条,则时间复杂度为O(1)
(四)分类
最坏情况:任意输入规模的最大运行次数(上界)。
平均情况:任意输入规模的期望运行次数。
最好情况:任意输入规模的最小运行次数(下界)。
注意:大O的渐进表示法在实际中一般情况关注的是算法的上界,也就是最坏运行情况。平均情况的时间复杂度一般来说都是与最坏情况的时间复杂度相同的。
(五)易错
问:为什么课件中和书籍中log n,lg n的表示法都是正确的呢?
答:当n接近无穷大时,底数的大小对结果的影响不大。因此,一般情况下不管底数是多少都可以省略不写,既可以表示为log n(建议使用log n)。
(六)求递归的时间复杂度
方法:递归的时间复杂度=单次递归的时间复杂度*递归次数(思想:从局部到整体)。
// 计算阶乘递归Fac的时间复杂度?
//调⽤⼀次Fac函数的时间复杂度为O(1)
//⽽在Fac函数中,存在n次递归调⽤Fac函数
long long Fac(size_t N)
{
if(0 == N)
return 1;
return Fac(N-1)*N;
}
//阶乘递归的时间复杂度为:O(n)=O(1*n)
(七)空间复杂度
表示方法:大O渐进表示法。
对象:空间复杂度不是程序占⽤了多少bytes的空间,因为常规情况每个对象⼤⼩差异不会很⼤,所以空间复杂度算的是变量的个数。
注意:函数运行时所需要的栈空间(存储参数,局部变量,一些寄存器信息等)在编译期间已经确定好的了,因此空间复杂度主要通过函数在运行时候显示申请的额外空间来确定。
(八)补充
1.一半多采用空间换时间的方法(某些题对时间有所限制)。
2.时间复杂度越小,时间效率越好,运行出结果更快;反之,则越低,越慢。
3.对于人来说一个数字特别的大,但对于机器来说是小的,例:数字100000000.
(九)复杂度算法题
例:轮转数组
思路一:时间复杂度O(n^2),循环k次将数组所有元素向后移动一位(代码不通过,原因:时间复杂度高,当数组元素个数比较多时,效率低,超过给定的时间限制,而代码本身并没有问题)。
思路二:时间复杂度O(n),空间复杂度O(n), 空间换时间,申请新数组空间,先将后k个数据放到数组中,再将剩下的数据挪到新数组中(核心代码:newArr[(i+k)%numsize]=nums[i])
思路三:空间复杂度O(1),前n-k个逆置:4 3 2 1 5 6 7,后k个逆置:4 3 2 1 7 6 5,整体逆置:5 6 7 1 2 3 4 。(本例:数组中的元素为1 2 3 4 5 6 7,其中k为3)
思路一代码:
void rotate(int* nums, int numsSize, int k) {
while(k--)
{
int end = nums[numsSize-1];
for(int i = numsSize - 1;i > 0 ;i--)
{
nums[i] = nums[i-1];
}
nums[0] = end;
}
}
思路二代码:
void rotate(int* nums, int numsSize, int k)
{
int newArr[numsSize];
for (int i = 0; i < numsSize; ++i)
{
newArr[(i + k) % numsSize] = nums[i];
}
for (int i = 0; i < numsSize; ++i)
{
nums[i] = newArr[i];
}
}
思路三代码:
void reverse(int* nums,int begin,int end)
{
while(begin<end){
int tmp = nums[begin];
nums[begin] = nums[end];
nums[end] = tmp;
begin++;
end--;
}
}
void rotate(int* nums, int numsSize, int k)
{
k = k%numsSize;
reverse(nums,0,numsSize-k-1);
reverse(nums,numsSize-k,numsSize-1);
reverse(nums,0,numsSize-1);
}