基础知识点
一个算法的时间复杂度,指算法运行的时间。
假设数据输入规模是n,算法的复杂度可以表示为f(n)的函数
接受上界不接受下界
大O记号(不唯一)(一般找最邻近它的那个)
假设f(n)和g(n)的定义域是非负整数,存在两个正整数c和n0,使得n>n0的时候,f(n)≤c*g(n),则f(n)=O(g(n))。可见O(g(n))可以表示算法运行时间的上界。O(g(n))表示的函数集合的函数是阶数不超过g(n)的函数。
例如:f(n)=2*n+2=O(n)
证明:当n>3的时候,2*n +2<3n,所以可选n0=3,c=3,则n>n0的时候,f(n)<c*(n),所以f(n)=O(n)。
现在再证明f(n)=2*n+2=O(n^2)
证明:当n>2的时候,2*n+2<2*n^2,所以可选n0=2,c=2,则n>n0的时候,f(n)<c*(n^2),所以f(n)=O(n^2)。
同理可证f(n)=O(n^a),a>1
Ω记号(第二十四个希腊字母欧米伽大写Ω,小写ω)(不唯一)
Ω记号与大O记号相反,他可以表示算法运行时间的下界。Ω(g(n))表示的函数集合的函数是所有阶数超过g(n)的函数。
例如:f(n)=2*n^2+3*n+2=Ω(n^2)
证明:当n>4的时候,2*n^2+3*n+2>n^2,所以可选n0=4,c=1,则n>n0的时候,f(n)>c*(n^2),所以f(n)=Ω(n^2)。
同理可证f(n)=Ω(n),f(n)=Ω(1)
Θ记号(读法:sei ta)
小o记号
f(n)=o(g(n))当且仅当f(n)=O(g(n))且f(n)≠Ω(g(n))。也就是说小o记号可以表示时间复杂度的上界,但是一定不等于下界。
常见的时间复杂度介绍
常见的时间复杂度如下表所示
执行次数函数 阶 非正式术语 12 O(1) 常数阶 2n+3 O(n) 线性阶 3n²+2n+1 O(n²) 平方阶 5logn+20 O(logn)(以2为底数) 对数阶 2n+3nlogn+19 O(nlogn) nlogn阶 6n³+2n²+3n+4 O(n³) 立方阶 2ⁿ O(2ⁿ) 指数阶
常用的时间复杂度所耗费的时间从大到小依次是:O(1) < O(logn) <O(n) < O(nlogn) < O(n²) < O(n³) < O(2ⁿ) < O(n!) <O(nⁿ)
我们前面以及谈到了O(1)常数阶、O(logn)对数阶、O(n)线性阶、O(n²)平方阶等,至于O(nlogn)等都类似分析,而像O(n³),过大的n都会使得结果变得不现实。同样指数阶O(2ⁿ)和阶乘阶O(n!)等除非是很小的n值,否则哪怕n只是100,都是噩梦般的运行时间。
推导大O阶方法
- 用常数1取代运行时间中的所有加法常数。
- 在修改后的运行次数函数中,只保留最高阶项。
- 如果最高阶项存在且不是1,则去除与这个项相乘的常数。得到的结果就是大O阶
常数阶
int sum = 0, n = 100; /*执行一次*/
sum = (1+n)*n/2; /*执行一次*/
printf("%d",sum); /*执行一次*/
- 这个算法的运行次数函数是f(n)=3。根据我们推导大O阶的方法,第一步就是把常数项3改为1。在保留最高阶项时发现,它根本没有最高阶项,所以这个算法的时间复杂度为O(1)。
- 我们又可以试想一下,如果这个算法当中的语句sum=(1+n)*n/2 有10句。事实上无论n为多少,这两段代码就是3次和12次执行的差异。这种与问题的大小无关(n的多少),执行时间恒定的算法,我们称之为具有O(1)的时间复杂度,又叫常数阶。
- 注意:不管这个常数是多少,我们都记作O(1),而不能是O(3)、O(12)等其他融合数字,这是初学者常常犯的错误。
线性阶
int i;
for(i = 0; i < n; i++)
{
/*时间复杂度为O(1)的程序步骤序列 */
}
- 它的循环的时间复杂度为O(n),因为循环体中的代码需要执行C*n次。(C为任意常数)
对数阶
int count = 1;
while( count < n )
{
count = count * 2;#对数阶的关键语句
/* 时间复杂度为O(1)的程序步骤序列 */
}
- 由于每次count乘以2之后,就距离n更近了一分。也就是说,有多少个2相乘后大于n,则会退出循环。由2^x= n 得到 x = logn 。
所以这个循环的时间复杂度为 O(logn)。
平方阶
int i , j;
for( i = 0; i < n; i++)
{
for(j = 0; j<n; j++)
{
/* 时间复杂度为O(1)的程序步骤序列 */
}
}
- 它的内循环刚才我们已经分析过,时间复杂度为O(n)。 而对于外层的循环,不过是内部这个时间复杂度为O(n)的语句,再循环n次。所以这段代码的时间复杂度为O(n²)
如果外循环的循环次数改为了m,时间复杂度就变为O(m X n)
int i,j;
for(i= 0; i < m; i++)
{
for(j = 0; j < n; j++)
{
/* 时间复杂度为O(1)的程序步骤序列 */
}
}
- 循环的时间复杂度等于循环体的复杂度乘以该循环运行的次数
这个嵌套的辅助度等于多少了,注意内层循环的起始条件
int i,j;
for(i = 0; i < n; i++)
{
for(j = i; j < n; j++) /* 注意 j = i 而不是0 */
{
/* 时间复杂度为O(1)的程序步骤序列 */
}
}
由于当i = 0 时,内循环执行了n次,当i=1时,执行了n-1次,…当i = n-1时,执行了1次。所以总的执行次数为:
-
用我们推导大O阶的方法,
第一条,没有加法常数不予考虑;第二条,只保留最高阶项,因此保留n^2/2;第三条,去除这个项相乘的常数,也就是去除1/2,最终这段代码的时间复杂度为O(n²)。
文章参考: