赞
踩
目录
时间复杂度,这里会有一个误区,可能有些人会认为时间复杂度描述的是算法运行所耗费的时间长短。然而,从理论上说,因为硬件配置、网络环境等等一系列环境因素,一个算法在执行过程中所耗费的具体的时间长短是无法被预先计算出来的。只有将程度老老实实在机器上跑一遍才能知道具体的时间。
但是这无疑是很耗费资源的,于是人们创造了时间复杂度这样一个概念。因为在一次程序运行的过程中,我们可以近似地认为程序每执行一次耗费的时间都是相等的,即一个算法所花费的时间与其中语句执行次数成正比例关系。于是我们将算法中的基本操作的执行次数,称为算法的时间复杂度。
试着计算一下以下函数的++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;
- }
- }

不难得出,这里的++count语句一共执行了次。
上面算出来的次数是程序精确的执行次数。但是实际应用中,我们只是为了描述一个算法大致的快慢,并不一定需要非常精确的执行次数,于是我们只需要大概的执行次数,这就需要使用大O渐进表示法。
我们平时在很多书籍上看到时间复杂度的时候都看过一些类似O(n),O(n²)的写法,那究竟什么是大O呢?
算法导论中是这样解释的:大O用来表示上界的,当用它作为算法的最坏情况运行时间的上界,就是对任意数据输入的运行时间的上界。
就拿冒泡排序来说,冒泡排序的时间复杂度是O(n²),但是并不是每一次都能完全执行n²的次数。这里的n²是一个最坏情况的表示,实际当中有可能并没有达到n²就已经完成排序。
如何推导一个算法的大O阶有如下方法:
于是,通过以上规则,我们再回到1.1中的func1函数,使用大O渐进法对其进行表示则为:O(n²)
通过以上法则我们可以发现,大O渐进表示法去除了原函数中对于结果影响较小的项,使得结果更加简明。
假如你对数学中的极限比较了解,就会知道这里其实跟无穷大的概念有联系。就拿func1的执行次数函数来看:
在N趋近无限大的时候,N²的增长速度是远远快于2*N的,而常数项更是对结果没有影响。所以我们可以这样认为,大O渐进表示法就是以原函数中增长速度最快的一项来作为衡量标准。
实例1:
void Func2(int N) { int count = 0; for (int k = 0; k < 2 * N ; ++ k) { ++count; } int M = 10; while (M--) { ++count; } printf("%d\n", count); }容易得出,实例1中基本操作执行了2*N+10次,通过推导大O阶渐进表示法的方法可得,时间复杂度为O(N)
实例2:
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(N+M)
实例3:
void Func4(int N) { int count = 0; for (int k = 0; k < 100; ++ k) { ++count; } printf("%d\n", count); }基本操作执行了100次,通过推导大O阶渐进表示法的方法可得,时间复杂度为O(1)
实例4:
// 计算二分查找的时间复杂度 int BinarySearch(int* a, int n, int x) { assert(a); int begin = 0; int end = n-1; // [begin, end]:begin和end是左闭右闭区间,因此有=号 while (begin <= end) { int mid = begin + ((end-begin)>>1); if (a[mid] < x) begin = mid+1; else if (a[mid] > x) end = mid-1; else return mid; } return -1; }这里我们假设总共有n个数据,执行次数最大为x,假设最坏情况下查找完所有数据才找到目标值,则根据二分算法的定义由如下等式:
;
于是有:
;
时间复杂度为O(logN);
(ps:这里同常规数学有一些区别,logN在算法分析中表示是底 数为2,对数为N。有些地方会写成lgN)
实例5:
// 计算阶乘递归Fac的时间复杂度? long long Fac(size_t N) { if(0 == N) return 1; return Fac(N-1)*N; }这里可以发现递归进行了N次,所以时间复杂度是O(N)
空间复杂度也是一个数学表达式,它是用来对算法在运行过程中临时占用存储空间大小的度量。
这里也有一个常见误区,空间复杂度并不是计算程序占用了多少字节的空间,这样做是意义不大的。所以空间复杂度计算的是变量的个数。同时间复杂度一样,空间复杂度也采用大O渐进表示法。
注意:由于函数运行时所需要的栈空间(如存储参数、局部变量等)在预处理期间就已经确定好了,因此空间复杂度主要是通过函数在运行时候申请的额外空间来确定。
实例1:
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; } }算法中使用了常数个额外空间,因此空间复杂度为O(1)
实例2:
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)
实例3:
long long Fac(size_t N) { if(N == 0) return 1; return Fac(N-1)*N; }算法中递归调用了N次,开辟了N个栈帧,每个栈帧中没有额外开辟空间,因此空间复杂度为O(N)
(本篇完)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。