初探时间复杂度

算法的复杂度分为时间复杂度和空间复杂度,一般说该程序的复杂度默认指时间复杂度。
先学习一下时间复杂度:

  • 为什么要引入时间复杂度?
  • 什么是时间复杂度?
  • 如何去计算一个算法的时间复杂度?

为什么要引入时间复杂度?

来看一段非常简单的代码

public static void main(String[] args) {
    int n = 100;
    for (int i = 0; i < n; i++) {
        System.out.println("Test");
    }
}

Q:说一下这段代码会运行多长时间?
A:这个…,得在计算机上跑一下才可以知道吧。
Q:嗯嗯,对的,那如果我改变n的大小为10000,你能够预测它的运行时间吗?
A:这个,要不再测一次。
Q:我们要善于发现事物的内在规律。
A:这个程序的内在规律是什么呢?
Q:你要预测当n=10000的时候,该程序会运行多长时间,你首先要知道程序的运行时间都花在哪里了?
Q:当电脑运行这段代码的时候,执行任何一条语句都需要花费时间,这是时间花费的主要地方。
Q:你看一下刚才的程序都有哪些语句?

int n = 100;执行了一个时间单元
int i = 0;执行了一个时间单元
i < n;执行了n + 1个时间单元(最后一次还要比较)
i++;执行了n个时间单元
System.out.println("Test");执行了n个时间单元

Q:你看看,这个程序有这么几个地方消耗了时间
Q:那么一共花费了3n + 3个时间单元,可以看出,程序消耗的时间和你的n成线性关系
A:这不是数学吗?
Q:是的,现在我用T(n)表示这个程序运行了多长时间,那么这个程序运行的时间就可以写成T(n) = 3n + 3;
A:哦,我懂了,要预测程序的时间,我可以把运行时间函数求出来
Q:嗯嗯,对的
A:这个函数表达式看起来挺复杂的
Q:我们常常会对这个函数进行简化,使得它既简单又不失函数的主要特性
A:哦?怎么简化

什么是时间复杂度?

Q:我们一般只关心随着问题规模n趋于无穷时函数中对函数结果影响最大的项,也就是最高次项,
比如,T(n) = 3n + 3,当n非常大的时候常数3和n的系数3对函数结果的影响就很小了,所以一般我们会保留最高次项并忽略该项的系数。
Q:比如:T(n) = n + 1,忽略常数项后 T(n) = n;
T(n) = n + n^2,忽略低阶项后 T(n) = n^2;
T(n) = 3n,忽略最高阶的系数后 T(n) = n;
A:这个忽略低阶项是什么意思
Q:所谓低阶项,简单地说就是当n非常大的时候,这个项相对于另外一个项很少,可以忽略,比如n相对n^2,n就是低阶项。
A:噢噢,那么我怎么判断哪个是高阶项,哪个是低阶项呢?
Q:具体要用数学知识,对你而言,你只需记住下面的大小关系就行了,到时候按照这个进行忽略(相对较小的忽略)
O(1) < O(log n) < O(n) < O(nlog n) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n)
A:简化完了之后呢?
Q:简化完后的函数可以近似代表原来函数的总体趋势
Q:简化后的式子被称为这个程序算法的时间复杂度,记作O(f(n)),f(n)就是简化后的式子,比如说刚开始讨论的T(n) = 3n + 3,简化后 T(n) ~ f(n) = n,那我们记为O(n)。
A:噢噢,原来时间复杂度可以表示某个算法的运行时间趋势,大致地度量算法效率的好坏,那我该
如何算这个时间复杂度呢?

如何算这个时间复杂度呢?

Q:从前面可以总结出计算算法时间复杂度大O的方法。
Q:一,得出运行时间函数
二,对函数进行简化
(1)用常数1来取代运行时间中所有加法常数
(2)修改后的函数中,只保留最高阶项
(3)如果最高阶项存在且不是1,则忽略这个项的系数

Q:举个例子更容易理解,先来看看这样一段代码

int n = 100;
n = n + 1;
System.out.println(n);

Q:很显然,T(n) = 3,对这个函数进行简化,用常数1取代常数3,然后取代后的函数没有最高阶项,那么这个算法的时间复杂度就是O(1)。
A:每次都要把时间函数算出来,好复杂,有没有简便一点的方法
Q:这个真可以耍点小聪明,一般来说,最内层执行次数最多的语句就决定了整个算法的趋势。
for (int i = 0; i < n; i++) {
System.out.println(“Test”);
}
Q:你只要看最内层的语句执行次数的规律就可以了,这个内层打印语句随着问题规模n的增加会呈线性增加,直接就可以判定复杂度为O(n)。
A:这个方法真棒,那么按照这个方法就很容易得出下面这个嵌套的两层for循环的时间复杂度为O(n^2)。

for (int i = 0; i < n; i++) {
    for (int j = 0; j < n; j++) {
        System.out.println("平方阶");
    }
}

Q:嗯嗯,对的,最内层的语句随着n的增加会呈二次函数的规律执行,代表了这个算法执行时间的一个趋势。
A:我听说有一个神奇的函数叫对数函数,它随着自变量的增大,因变量增长的很慢,这个应该很受欢迎吧。
Q:嗯嗯,对的,对数函数的趋势显然比线性函数好。

int sum = 1;
while (sum < n) {
    sum = sum * 2;
}

Q:你看,这段代码的复杂度就是对数的,O(log n)
A:这个怎么分析呢
Q:和之前的分析方法一样,我们看执行次数最多的内层代码

sum = sum * 2

Q:每循环一次,sum就给自身乘以2,乘了多少次跳出循环呢?不知道,那么就设为x吧,那么2^x = n,解出 x = log n,这说明随着n的增加,最消耗时间的内层语句是呈对数变化的。
A:噢噢,我懂了,原来如此。
Q:复杂度的内容很多,你只要理解它的含义以及会分析简单的算法就行了,以后会遇到更复杂的。
A:好的,今天收获太大了,嘎嘎嘎!

猜你喜欢

转载自blog.csdn.net/yp1125/article/details/80695817