时间复杂度与空间复杂度
时间复杂度和空间复杂度的定义:
- 一个算法的好坏的衡量标准要从时间复杂度和空间复杂度两个方面来衡量;时间复杂度主要来衡量一个算法运行的速度的快慢,而时间复杂度是衡量一个算法运行时所需要的额外空间。
- 时间复杂度概念: 算法的时间复杂度是一个函数,定量的描述了算法的运行时间(即一个算法执行所需要消耗的时间)。一个算法的执行时间与其中执行语句的次数成正比例,算法的基本操作的执行数为算法的时间复杂度。
- 空间复杂度概念: 空间复杂度也是一个数学表达式,它描述的是一个算法在执行的时候所需要开辟的临时占用的存储空间的大小的度量。
概念介绍完了,我们举个实例来计算一下时间复杂度吧:
// 请计算一下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;
}
分析:
在第一个嵌套的for循环中,++count的执行次数为:N*N次;
在第二个for循环中,++count的执行次数为:N次。
在第三个while循环中,++count的执行次数为:10次。
综上,++count的执行次数为:N^2+N+10次
假设N=10;N^2+N+10=120
假设N=100;N^2+N+10=10110
假设N=10000;N^2+N+10=100010010
注意:我们本在算时间复杂度的时候,我们只需要求出大概的执行次数,用大O渐进法表示。
可以看出:Fun1函数的时间复杂度为:O(N*N);
大O渐进法
大O渐进法是描述函数渐进的数学符号。
计算时间复杂度的实例
- 用常数1取代运行时间中的所有加法常数。即:O(1)
void Func4(int N)
{
int count = 0;
for (int k = 0; k < 100; ++ k)
{
++count;
}
printf("%d\n", count);
}
执行100次,执行的复杂度是常数,所以表示为:O(1);
- 在修改后的运行次数函数中,只保留最高阶项。
void Func3(int N, int M)
{
int count = 0;
for (int k = 0; k < M; ++ k)
{
++count;
}
for (int k = 0; k < N ; ++ k)
{
++count;
}
printf("%d\n", count);
}
分析:
若M与N的大小相近,时间复杂度可以表示为:O(M)或O(N);
若M>>N的值,时间复杂度可以表示为:O(M);
若N>>M的值,时间复杂度可以表示为: O(N);
- 如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶。
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;
}
上面我们分析了count的执行次数为:N^2 +N+10;
所以按照最高项的大O阶表示为:O(N^2)。
- 当以个算法的输入不同,时间复杂度不同,时间复杂度要做悲观预期,看最坏的情况
const char * strchr ( const char * str, int character ){
if(character==*str)
return str;
else
str++;
}
分析:
最好情况:当character为查找的第一个的时候,执行次数为1,时间复杂度为:O(1);
平均情况:当character为查找的中间的一个的时候,执行次数为:N/2,时间复杂度为:O(N/2);
最坏情况:当character为查找的最后一个的时候,执行次数为:N,时间复杂度为:O(n);
所以本函数的时间复杂度为:O(N)。
- 实例:
void BubbleSort(int* a, int n)
{
assert(a);
for (size_t end = n; end > 0; --end)
{
int exchange = 0;
for (size_t i = 1; i < end; ++i)
{
if (a[i-1] > a[i])
{
Swap(&a[i-1], &a[i]);
exchange = 1;
}
}
if (exchange == 0)
break;
}
}
分析:
本代码为冒泡法排序:第一个数比较:N-1次,第二个数比较:N-2次,一次类推,N-1个数比较1次。
所以时间复杂度为:O(N-1+N-2+N-3+…+3+2+1)=O(N*(N-1)/2)=O(N^2)
- 实例:
int BinarySearch(int* a, int n, int x)
{
assert(a);
int begin = 0;
int end = n-1;
while (begin < end)
{
int mid = begin + ((end-begin)>>1);
if (a[mid] < x)
begin = mid+1;
else if (a[mid] > x)
end = mid;
else
return mid;
}
return -1;
}
分析:
在二分查找的过程中,最好的情况就是第一个就能找的到,时间复杂度为:O(1),最怀的情况就是找不到。在找不到的时候为:
2^x=N
x=logN;
所以时间复杂度为:O(logN)
接下来我门继续分析一下递归函数的时间复杂度:
long long Fac(size_t N)
{
if(0 == N)
return 1;
return Fac(N-1)*N;
}
执行过程为:
NFac(N-1);
N(N-1)Fac(N-2);
N(N-1)(N-2)Fac(N-3);
……
N(N-1)(N-2)……32*1;
所以时间复杂度为:O(N)
long long Fib(size_t N)
{
if(N < 3)
return 1;
return Fib(N-1) + Fib(N-2);
}
可知在计算的过程中:1+2^1 + 2^2 +23+24+……+2^N-X;
注意减去X的原因是右边的那一枝会比左枝提前结束。
所以时间复杂度为:O(2^N);
计算空间复杂度的实例
- 实例1:
// 计算BubbleSort的空间复杂度?
void BubbleSort(int* a, int n)
{
assert(a);
for (size_t end = n; end > 0; --end)
{
int exchange = 0;
for (size_t i = 1; i < end; ++i)
{
if (a[i-1] > a[i])
{
Swap(&a[i-1], &a[i]);
exchange = 1;
}
}
if (exchange == 0)
break;
}
}
long long* Fibonacci(size_t n)
{
if(n==0)
return NULL;
long long * fibArray = (long long *)malloc((n+1) * sizeof(long long));
fibArray[0] = 0;
fibArray[1] = 1;
for (int i = 2; i <= n ; ++i)
{
fibArray[i] = fibArray[i - 1] + fibArray [i - 2];
}
return fibArray;
}
申请了N个元素的数组的内存,其时间复杂度为:O(N),时间复杂度为:O(N)。
long long Fac(size_t N)
{
if(N == 0)
return 1;
return Fac(N-1)*N;
}
可以看出空间复杂度就是递归的深度:空间复杂度为:O(N);
long long Fib(size_t N)
{
if(N < 3)
return 1;
return Fib(N-1) + Fib(N-2);
}
同样根据上面的分析:它的深度也为N,所以其空间复杂度为:O(N)。