算法的时间复杂度和空间复杂度
Posted 正义的伙伴啊
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法的时间复杂度和空间复杂度相关的知识,希望对你有一定的参考价值。
算法的时间复杂度和空间复杂度
算法的效率
如何衡量一个算法的好坏呢?
一个算法如果写的十分的短,是不是就非常的好呢?
例如斐波那契数列:
long long Fib(int N)
{
if (N < 3)
return 1;
return Fib(N - 1) + Fib(N - 2);
}
实际上斐波那契数列的函数是最铸币的算法,随着N不断增大(大概到30),计算机就有可能计算不出来了。那么我们要用什么方式去衡量一个算法的好坏,所以我们引进了时间复杂度和空间复杂度。 时间复杂度主要衡量一个算法的运行快慢,而空间复杂度主要衡量一个算法运行所占用空间
时间复杂度
时间复杂度的概念
衡量一个算法的运行时间快慢也就是时间复杂度,那么计量的单位是什么?如果用我们常用的时间单位例如:秒、毫秒。这就存在一个问题,不同机器由于配置不同时间会不相同,而且要想知道时间就必须上机跑代码很麻烦。所以我们把算法花费的时间与其中语句执行次数成正比,算法中的基本操作执行个数,为算法的时间复杂度。
void fun(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);
}
count在这个函数中我们很容易知道执行了F(N)=N^2+2*N+10
其实我们在计算精确的执行次数,而只需要大概执行次数,这里我们使用大O的渐进表示法
大O的渐进表示法
大O符号(Big O notation):用于描述函数渐进行为的数学符号。
推导大O阶方法:
- 用常数1取代运行时间中的所有加法常数
- 在修改后的运行次数函数中,只保留最高阶项
- 如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶
所以函数FUN的复杂度为:O(N^2)
常见的时间复杂度举例
例一:
void fun(int N, int M)
{
int count = 0;
for (int k = 0; k < M; ++k)
{
++count;
}
for (int k = 0; k < N; ++k)
{
++count;
}
}
很明显这里的时间复杂度是O(M+N)
其实如果M远远大于N则时间复杂度为O(M)
如果N远远大于M则时间复杂度为O(N)
例二:
void fun(int N, int M)
{
int count = 0;
for (int k = 0; k < 1000; ++k)
{
++count;
}
}
时间复杂度为O(1000)
但是如果为常数的话,时间复杂度可以直接写为O(1)
例三:冒泡排序
void Bubblesort(int *a, int n)
{
for (int i = 0; i < n-1 ; i++)
{
for (int j = 0; j < n - 1 - i; j++)
{
if (a[j] < a[j + 1])
{
int temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
}
}
}
}
由这个程序我们可以知道F(N)=N-1+N-2+.....+1=(N^2-N)/2
例四:二分查找
int BinarySearch(int* a, int n, int x)
{
assert(a);
int left = 0;
int right = n - 1;
while (left != right)
{
int mid = (left + right) / 2;
if (x > a[mid])
{
left = mid + 1;
}
else if (x < a[mid])
{
right = mid - 1;
}
else
{
return mid+1;
}
return -1;
}
}
这个算法对于每次查找,找不到就会将范围缩小二倍,数组缩小到0,所以时间复杂度自然也就是O(lgN)
注:这里lgN实际上是指log以2为底N的对数
实例Ⅴ:递归阶乘
long long fun(size_t N)
{
if (0 == N)
return 1;
return fun(N - 1) * N;
}
这个也非常简单return的 值为fun(N-1)*fun(N-2)........fun(0)
一共是N+1个,所以中间的代码也就执行了N+1次,所以时间复杂度是O(N)
实例Ⅶ:斐波那契数列
long long Fib(size_t N)
{
if (N < 3)
{
return 1;
}
return Fib(N - 1) + Fib(N - 2);
}
这里时间复杂度像一颗树一样展开,每一个节点会有两个分支,所以是一个等比数列,从1一直到2^(n-2)次方的和2^(n-1)-1
,但实际上要比这个小一点但是量级都是2^N
,故时间复杂度为O(2^N)
空间复杂度
空间复杂度也是一个数学表达式,是对一个算法在运行过程中临时占用储存空间大小的度量
空间复杂度不是程序占用了多少bytes空间,因为这个也没太大意义,所以空间复杂度算的是变量的个数。也用O()来表示。
注意:函数运行所需要的栈空间在编译的时候就已经确认好了,因此空间复杂度主要通过函数在运行时显示申请的空间来确定。
实例一:冒泡排序
void Bubblesort(int *a, int n)
{
for (int i = 0; i < n-1 ; i++)
{
for (int j = 0; j < n - 1 - i; j++)
{
if (a[j] < a[j + 1])
{
int temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
}
}
}
}
实际上这里开辟的空间只有i,j,temp三个所以空间复杂度为O(1)
但是我在初学的时候一直十分不解,i,j分别被定义了n次,那么空间复杂度不应该是O(N)吗?
实际上这里i,j在出了作用域之后就会被销毁,空间是可以重复利用的,所以实际上就开辟了常数个空间。
例二:
void fun(int k)
{
int *p=(int *)malloc(n*sizeof(int));
}
这个函数就是纯纯的开辟了n个空间
例三:函数递归
long long F(size_t N)
{
if (N == 0)
return 1;
return Fac(N - 1) * N;
}
首先答案是O(N)
这里如果对函数栈帧不是很了解的话,这个原理也就不会知道。函数栈帧可以简单的理解为:函数每被调用一次,都会在栈区上开辟一块空间,所以F(N)会调用F(N-1),F(N-1)会调用F(N-2)…顾同时会调用N个F,每个F都会开辟一块空间,所以空间复杂度为:O(N)
示例四:斐波那契数列
long long Fib(size_t N)
{
if (N < 3)
{
return 1;
}
return Fib(N - 1) + Fib(N - 2);
}
通过上面的实例我们就可以知道斐波那契数列实际上开辟了N个Fib函数空间,只是这些函数空间会被重复利用,总共被运行2^n-1次,所以空间复杂度为O(N)
以上是关于算法的时间复杂度和空间复杂度的主要内容,如果未能解决你的问题,请参考以下文章