1.概述
对于一个算法来说,评测它的角度通常有两种,时间复杂度和空间复杂度。而时间复杂度和空间复杂度是越低越好。通常时间复杂度和空间复杂度是随着输入n的变化而变化。通常使用大O表示法来表示时间复杂度和空间复杂度,大O表示法是一种粗略的估计。
2.时间复杂度
直接拿实例分析,见如下代码
void test(int n)
{
int i = 0;
int count = 0;
for(i = 0; i < n; i++)
{
for(i = 0; i < n; i++)
{
count++;
}
}
for(i = 0; i < n; i++)
{
for(i = 0; i < 2; i++)
{
count++;
}
}
for(i = 0; i < 20; i++)
{
count++;
}
}
随着输入变量N的变化,count的值会如何变化?
我们可以很容易得到一个这样的表达式:
这就是这个代码的时间复杂度的精确表达式,受输入变量影响的语句 随着N的变化发生执行次数的改变。
我们可以发现,随着N的增大,对代码执行次数的影响貌似第一项(N^2)占了绝大部分。将其前面的系数置为1,我们可以得到这个代码的时间复杂度用大O表示法为:O(n^2)
大O表示法的详细规则可以去百度,这边只做几点简单总结:
1.将表达式转化为多项式,并且只保留最高次幂
2.将最高次幂的系数置为1
3.尽量表达为之和N有关的表达式,例如:O(1) O(logn) O(nlogn) O(n) O(n^2) O(2^n)...........
注:O(1)为常数表达式的时间复杂度,与N输入变量无关
再来看两个简单算法的时间复杂度
冒泡排序
void Bubble_Sort(int* arr, int n)
{
int i = 0;
int j = 0;
for(i = 0;i < n - 1; i++)
{
for(j = 0; j < n - 1 - i; j++)
{
if(arr[j] > arr[j + 1])
{
arr[j] = arr[j+1] ^ arr[j];
arr[j+1] = arr[j] ^ arr[j+1];
arr[j] = arr[j+1] ^ arr[j];//交换两个数
}
}
}
}
试着去看一下关于所输入量N的变化而变化的代码操作次数,也就是在不同的n下操作了多少次,忽略不随着n变化而变化的操作即常数项。
在这个情况下,我们不妨来画张图去好好地理解一下这个代码的逻辑上到底是怎么回事
根据等差数列和我们可以知道总排序次数为(n-1 + 1)(n-1)/2
根据大O表示法的规则最后得到时间复杂度为O(N^2)
二分查找
int Binary_lookup(int *arr, int n, int look_for)
{
int aim = -1;
int left = 0;
int mid = (left+n-1)/2 + 1;
int i = 0;
while(true)
{
for(i = left; i <= mid; i++)
{
if(arr[i] == look_for)
{
aim = i;
}
}
if(aim != -1)
{
break;//如果找到了
}
else if(left == mid)
{
return -1;
}
else
{
left = (left + right)/2;
mid = (mid + n - 1) / 2 + 1;
}
}
return aim;
}
而对于二分查找来说,在这里我们在乎的是它进入了while循环多少次,也就是进入了多少次查找。
接下来依靠画图来仔细理解一下这个查找的过程:
时间复杂度一般都是考虑最坏情况下,最坏的情况就是这边根本没有, 那么这块拥有n个元素的数组被不断2分,最后我们可以得到查找次数x = logn这里的对数以2为底。所以时间复杂度为:O(logN)
3.空间复杂度
空间复杂度相较于时间复杂度较为容易去理解,就是一个程序除了必要的空间占用外,其额外需要的空间随着输入变量N的变化而产生的变化规律,通常也是用大O表示法来表示空间复杂度。
随着内存越来越便宜,空间复杂度的重要性正在逐渐降低,但是其存在的意义仍是十分重大的。
直接看一个例子
int Fib(int num)
{
if(num < 3)
{
return 1;
}
else
return Fib(num - 1) +Fib(num - 2);
}
这是利用递归来求斐波那契数列的算法
在这里他的空间复杂度就是O(N),为什么不是O(2^N) 呢? 我们需牢记一点,空间是可以复用的,接下来是逻辑图