01.算法性能

在开始二刷前,先走出舒适区,硬啃一波性能分析。尽可能用人话说清楚复杂度究竟是个啥玩意。

时间复杂度

什么是时间复杂度

顾名思义,时间复杂度(也作渐进时间复杂度)就是描述算法运行时间的函数。通常会用操作单元数量来代表程序消耗的时间,默认CPU的每个单元运行消耗的时间都是相同的。

举个例子,假设算法问题的规模为n,那么操作单元数量便用函数f(n)来表示,随着数据规模n的增大,算法执行时间的增长率和f(n)的增长率相同,记为O(f(n))。乂,这里引入了一个新的字母O,这是啥呢?

什么是大O

大O用来表示上界,上界就是算法最坏情况的运行时间,即任意数据输入的运行时间最大为大O。

俗话说的好,抛开输入数据形式来考虑运行时间就是耍流氓。拿插入排序来说,在数据有序的情况下运行时间是O(n),但当数据是逆序,插入排序的运行时间就是O(n²),因此插入排序的时间复杂度是O(n²) 。这不是我说的,是算法导论规定的(

这时候杠精就会说,乂,快速排序的时间复杂度是O(nlogn),但是在数据已经有序的情况下,它的运行时间不是是O(n²) 嘛?确实,严格从大O的定义来讲,快速排序的时间复杂度应该是O(n²)。业内的默认规定大O代表的就是一般情况。毕竟你都有序了,还排它干嘛呢

不同数据规模的差异

时间复杂度定义为O(f(n))。从数学上考虑f(n),当它含有常数项且数据规模很小时,甚至用O(n²)的算法比用O(n)的更合适。但是,经常有题解会说:”忽略常数项之后,O(n)优于O(n²)“,这是为啥呢?

这又涉及到大O的定义,大O就是数据量级非常大的情况下所表现出的时间复杂度,此时常数项系数已不起决定性作用。

给出常见的比较:O(1) < O(logn) < O(n) < O(nlogn) < O(n²) < O(n³) < O(2^{\mathrm{n}}) < O(n!)

顺便提一嘴,学过数学的都知道,f(n)可以简化,例如后面的式子中如果包含前面的式子,那也可以忽略。

O(logn)以什么数为底?

这时候,学数学的同学要问了:乂,平时说某算法的时间复杂度是logn的,那么一定是以2为底n的对数么?

这个同学数学学的相当好,在数学中,是不存在没有底数的对数表达式的。但是在编程的世界里,有一种忽略底数的描述。举个例子,假如有两个算法,它们的时间复杂度分别是\log _2n和\log _{10}n。根据高中数学, log _2n= \log _210 * \log _{10}\log _210是一个常数,大O中常数项可忽略。由此可见,大O中,任何底数其实都是一个玩意,所以可以将其忽略。

超时测试实验结果

任何开发计算机程序员的软件工程师都应该能够估计这个程序的运行时间是一秒钟还是一年。对于计算机硬件要至少有大概的概念,这也是为啥力扣的提示中提示各种数据范围和时间限制,要会根据这些提示估计程序的时间复杂度大概是多少,再采取符合要求的算法。

直接给出大致的实验结果,数字代表对应时间复杂度的算法每秒能运行的次数:(一定不是我懒得自己敲一遍)O(n):5*10^8、O(n²):2.25*10^4、O(n\logn):2*10^7

空间复杂度

和时间类似,空间复杂度也可以用O(f(n))来表示。采用此种办法可以对程序运行中需要多少内存进行估计。

常见问题

第一个常识是——空间复杂度是考虑程序运行时占用内存的大小,而不是可执行文件的大小。

第二个常识是——很多因素会影响程序真正使用的内存,所以空间复杂度仅仅是个大体的预估

第三个常识是——O(1)、O(n)等空间复杂度都好理解,而O(\logn)的空间复杂度在递归算法中出现。

Java的内存管理

Java依赖内置虚拟机来做内存管理,因此不需要程序员去考虑内存泄漏的问题。

顺便引入一下内存对齐的概念,在Java八股中很少提到,了解一下。跨平台的编程语言都需要做内存对齐,其实并不仅限于C语言,当然内存对齐还有助于大幅度提高运行速度。

CPU读取内存不是一次读取单个字节,而是一块一块的来读取内存,块的大小可以是2,4,8,16个字节,具体取多少个字节取决于硬件。在存储数据时,如果划分的是四字节,而实际数据只占用一字节,那么多余的三字节就被浪费了。但是在读取过程中,每次读对应的地址位置即可,而不需要读多个,再裁剪、拼贴。所谓的内存对齐,就是牺牲空间,换取时间

递归分析

接下来,就用最难分析的递归算法为例,总结一下算法性能的分析方法。

求x的n次方

譬如,现在你在面试阿里云,面试官出了一道题:求x的n次方。这还不简单,秒写出如下代码:

int result = 1;  // 注意 任何数的0次方等于1
for (int i = 0; i < n; i++) result = result * x;
return result;

此时,面试官问,有无效率更高的算法呢?这时候马上想到了递归,开写!

int function(int x, int n) {
    if (n == 0) return 1; // return 1 同样是因为0次方是等于1的
    return function(x, n - 1) * x;
}

面试官微微一笑,问:此代码时间复杂度是多少呢?递归嘛,复杂度肯定是O(\logn),你这么答,遂寄。

递归算法的时间复杂度本质上是要看: 递归的次数 * 每次递归中的操作次数。每次n-1,递归了n次时间复杂度是O(n),每次进行了一个乘法操作,乘法操作的时间复杂度是一个常数项1,所以这份代码的时间复杂度是 n × 1 = O(n)。

正确的标准答案如下,利用了等比数列的求和公式,抽取出了递归操作:

int function(int x, int n) {
    if (n == 0) return 1;
    if (n == 1) return x;
    int t = function(x, n / 2);//若将此式子放入判断语句中,则复杂度仍为O(n)
    if (n % 2 == 1) {
        return t * t * x;
    }
    return t * t;
}

仅仅有一个递归调用,且每次都是n/2 ,所以一共调用了log以2为底n的对数次。这么答,面试官应该是很满意的,但是初见的同学会感觉非常抽象。接下来用图表详细说明。

参数n的大小 递归调用k次
2 2
4 3
8 4

显然有如下公式:2^{k-1}=n,转换形式之后可得:k=\log _2n+1。又因为每次递归都是一次乘法操作,它的时间复杂度即为O(\logn)。

斐波那契数列

动态规划刷的第一道题就是此题,代码基本都能背出来了:

int fibonacci(int i) {
       if(i <= 0) return 0;
       if(i == 1) return 1;
       return fibonacci(i-1) + fibonacci(i-2);
}

非常简单的式子,但是每次递归有1次加法,有2个递归调用(这里采用2叉树分析)。其复杂度为O(2^n),很明显太大了。

那就减少一下递归的调用次数呗:

int fibonacci(int first, int second, int n) {
    if (n <= 0) return 0;
    if (n < 3) {
        return 1;
    } else if (n == 3) {
        return first + second;
    } else {
        return fibonacci(second, first + second, n - 1);
    }

用first和second来记录当前相加的两个数值,此时就不用两次递归了。只是递归了n次,所以时间复杂度是O(n)。递归算法的空间复杂度 = 每次递归的空间复杂度 * 递归深度。从代码中可以看出每次递归所需要的空间大小都是一样的,它需要的空间是一个常量,并不会随着n的变化而变化,每次递归的空间复杂度就是O(1)。

递归第n个斐波那契数的话,递归调用栈的深度就是n(又是二叉树的知识,画图!)。每次递归的空间复杂度是O(1), 调用栈深度为n,所以这段递归代码的空间复杂度就是O(n)。

总结

对于时间和空间复杂度有了最基本的了解,以后每刷一题,都要学会自己分析其性能。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,284评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,115评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,614评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,671评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,699评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,562评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,309评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,223评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,668评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,859评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,981评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,705评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,310评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,904评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,023评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,146评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,933评论 2 355

推荐阅读更多精彩内容