算法在编写成可执行程序的时候,main函数使用这个算法,需要调用一定的空间,消耗一定的时间。算法的效率就是通过空间和时间这两个维度来评判的。
时间复杂度:衡量一个算法的运行速度
空间复杂度:衡量一个算法运行需要开辟的额外空间
那么我们今天先来看看时间复杂度!
时间复杂度 算法的时间复杂度是一个函数,它定量描述了该算法的运行时间。算法中基本操作的执行次数,为算法的时间复杂度
时间复杂度是一个近似值,并不是实际运行的时间
实际上代码的运行时间,和机器的内存、cpu性能和平台都有关系,同一个代码在不同的机器上运行时间都不一样,如果只以纯粹的时间来考核,很难分析
找到某条基本语句与问题规模n之间的数学表达式,就算出了该算法的时间复杂度
void test(int n){ int count =0; for(int i=0;i
f ( n ) = n 2 + 2 ∗ n + 10 f(n)=n^2+2*n+10 f(n)=n2+2∗n+10
n = 10 f(n) = 130
n = 100 f(n) = 10210
n = 1000 f(n) = 1002010
你可能会简单地认为,f(n)的结果就是我们的时间复杂度。其实并不然,我们并不需要一个精确的运行次数,只需要知道程序运行次数的量级就行了
这里我们使用大o渐进表示法来表示时间复杂度(空间复杂度同理)
2.1大o的渐进表示法 大o符号(big o notation):是用于描述函数渐进行为的数学符号
推导大o阶方法:
(1)用常数1取代运行时间中的所有加法常数。
(2)在修改后的运行次数函数中,只保留最高阶项。
(3)如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大o阶
使用这种方法后,test1函数的时间复杂度为
o ( n 2 ) o(n^2) o(n2)
对于test1函数,在计算的时候,我们省略了最后的+10,保留了最高阶数n2,即得出了它的时间复杂度
如果最高阶数前面有系数,如2n,系数也将被省略
因为当n的数值很大的时候,后面的那些值对我们总运行次数的影响已经非常小了。大o的渐进表示法去掉了那些对结果影响不大的项,简洁明了的表示出了执行次数
2.2多种情况取最坏 一些算法的时间复杂度会有最好、最坏和平均的情况
最好情况:任意输入规模的最小运行次数(下界)
平均情况:期望的运行次数
最坏情况:任意输入规模的最大运行次数(上界)
举个例子,当我们编写一个在数组中查找数值的算法时,它可能会出现这几种情况:
最好情况:1次就找到
平均情况:n/2次
最坏情况:n次
在实际中的一般情况,我们关注的是算法的最坏运行情况,所以数组中搜索数据时间复杂度为o(n)
2.3常见时间复杂度的计算 no.1
void func1(int n){ int count = 0; for (int k = 0; k < 2 * n ; ++ k) { ++count; } int m = 10; while (m--) { ++count; } printf(%d, count);} 这里出现了两个循环,分别是2n次和10次。前面提到了大o渐进法只保留最高阶数并省略系数,所以它的时间复杂度是o(n)
no.2
void func2(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, count);} 这里出现了次数为n和m的两层循环:
当m和n差不多大的时候,时间复杂度可以理解为o(m)或o(n)
当m远远大于n时,时间复杂度为o(m)
一般情况取o(m+n)
no.3 常数阶
void func3(int n){ int count = 0; for (int k = 0; k 0; --end) { int exchange = 0; for (size_t i = 1; i a[i]) { swap(&a[i-1], &a[i]); exchange = 1; } } if (exchange == 0) break; }} 冒泡排序是一个非常经典的代码,其思路就是遍历整个数组,如果待排序数字大于它的下一位,则交换这两个数字
n个数字的数组需要n-1次排序才能完成
每一次排序都需要遍历一次数组
这样算来,冒泡排序的循环次数就是两个n,即为o(n2)
能否通过循环层级判断?
细心的你可能会发现,上述代码中出现了两层循环,那是不是可以通过循环层级来判断时间复杂度呢?
并不能!
for(int i=0;i 我们要准确分析算法的思路,并不能简单地通过循环层级来判断时间复杂度
no.6 二分查找
int binarysearch(int* a, int n, int x){ assert(a); int begin = 0; int end = n-1; while (begin >1);//使用位移操作符来模拟/2,防止begin和end相加后超出int范围 if (a[mid] x) end = mid; else return mid; } return -1;} 二分查找的思路这里不再赘述
假设我们找了x次,每一次查找都会使查找范围减半
n/2/2/2/2 ……
最后我们可以得出2条公式
2 x = n 2^x=n 2x=n
x = l o g 2 n x=log_2n x=log2n
最好情况:o(1)
最坏情况:o(logn)
通过时间复杂度的对比,我们就能看出二分查找的优势在那里了
可以看到,当数据很大的时候,o(logn)的算法执行次数比o(n)少了特别多!!(来自bt-7274的肯定)
no.7 计算n!
// 计算阶乘递归fac的时间复杂度?long long fac(size_t n){ if(0 == n) return 1; return fac(n-1)*n;} 对于这个阶乘的递归函数而言,每次函数调用是o(1),时间复杂度主要看递归次数
对于数字n而言,递归需要n次,时间复杂度是o(n)
递归算法时间复杂度计算
如果每次函数调用是o(1),看递归次数
每次函数调用不是o(1),那么就看他递归调用中次数的累加
no.8 斐波那契数列
// 计算斐波那契递归fib的时间复杂度?long long fib(size_t n){ if(n < 3) return 1; return fib(n-1) + fib(n-2);}
每次递归,次数都会增加,总的来说是以2^x的量级增加的(x代表行数)
1 + 2 + 4 + 8 + … … + 2 x − 2 1+2+4+8+……+2^{x-2} 1+2+4+8+……+2x−2
这里一共有x-1项,用等比数列的求和公式得出,结果为2x-1
所以最后得出的时间复杂度为o(2n)
需要注意的是,当递归调用到底部时,有一些调用会较早退出,这部分位于金字塔的右下角
由于时间复杂度只是一个估算值,这一部分缺失的调用次数对总运行次数的影响不大,故忽略掉
no.9
void fun(int n) { int i=l; while(i<=n) i=i*2;} 此函数有一个循环,但是循环没有被执行n次,i每次都是2倍进行递增,所以循环只会被执行log2n次
no.10
给定一个整数sum,从有n个有序元素的数组中寻找元素a,b,使得a+b的结果最接近sum,最快的平均时间复杂度是?
a. o(n)//√b. o(n^2)c. o(nlogn)d. o(logn) 数组元素有序,所以a,b两个数可以分别从开始和结尾处开始搜,根据首尾元素的和是否大于sum,决定搜索的移动,整个数组被搜索一遍,就可以得到结果,所以最好时间复杂度为n
三星依旧很给力,新配置120Hz刷新率+1.08亿+骁龙865
Galaxy S7硬件成本曝光 仅为255美元
e络盟供货OrangeCrab开源FPGA开发板
工业电视监控系统和激光对射周界防范系统的应用分析
汽车与手机融合的帷幕现已拉开 智能终端效应越发明显
一文彻底了解时间复杂度
高压电缆绝缘料国产化进程加速, 国产高压电缆绝缘料促进电缆行业质量提升
通用和LG共同投资的电池公司你怎么看?
通过生成式模型驱动开发车辆中的电子E/E系统问题
超小型可穿戴医疗保健设计平台
联合曲线的异同对持币者的影响及其应用分析
微软内部对亚洲研究院的未来持有不同看法
采用PC、FPGA功能模块实现DAB发射系统编码器的设计与应用
OPPO在海外市场遭受挫折,欧洲市场面临激烈的竞争
采用DSP+FPGA构架实现高速图形帧存的设计方法
概述MEMS传感器市场的创新和发展
STM32开发的BMP180气压传感器程序源码
光计算的主要优势
与“OEM”共舞气质最搭的互联网公司,语音将会成车内交互的基础
英伟达以超过70亿美元的价格收购以色列芯片制造商Mellanox!