惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

A
About on SuperTechFans
人人都是产品经理
人人都是产品经理
量子位
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 三生石上(FineUI控件)
小众软件
小众软件
月光博客
月光博客
T
Tailwind CSS Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
V
Visual Studio Blog
博客园 - 叶小钗
博客园 - 司徒正美
美团技术团队
博客园_首页
宝玉的分享
宝玉的分享
Apple Machine Learning Research
Apple Machine Learning Research
Hugging Face - Blog
Hugging Face - Blog
WordPress大学
WordPress大学
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
The Cloudflare Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
有赞技术团队
有赞技术团队
D
DataBreaches.Net
Google DeepMind News
Google DeepMind News

青空之蓝

[青空之蓝-2023] - 色彩 | 青空之蓝 [青空之蓝-2022] - 平静 | 青空之蓝 [青空之蓝-2021] - 远望 | 青空之蓝 浅谈垃圾回收 | 青空之蓝 浅谈泛型擦除 | 青空之蓝 浅谈单点登录 | 青空之蓝 使用 Kotlin 编写 Spring 测试 | 青空之蓝 设计模式系列文章 | 青空之蓝 从零实现一个 Java 微框架 - IoC | 青空之蓝 从零实现一个 Java 微框架 - 前言 | 青空之蓝 浅谈 JVM:类加载 | 青空之蓝 浅谈 IO | 青空之蓝 浅谈并发:synchronized & ReentrantLock | 青空之蓝 浅谈并发:CAS & AQS | 青空之蓝 浅谈并发:ThreadLocal | 青空之蓝 浅谈并发:三大特性 | 青空之蓝 浅谈组合注解 & 注解别名 | 青空之蓝 [青空之蓝-2020]-迷茫 | 青空之蓝 Java 系列文章 | 青空之蓝 HTTP 系列文章 | 青空之蓝 浅谈 EatWhatYouKill | 青空之蓝 浅谈可扩展线程池 | 青空之蓝 聊聊写框架 | 青空之蓝 聊聊现状-[2020-09] | 青空之蓝 浅谈并发:锁 | 青空之蓝 浅谈并发:基础 | 青空之蓝 浅谈缓存 | 青空之蓝 无须定义类,Spring 快速注入 Json 参数 | 青空之蓝 浅谈 Proxy 和 Aop | 青空之蓝 从零实现一个 PHP 微框架 - 初始化请求 | 青空之蓝
算法复杂度分析(1) | 青空之蓝
Otstar Lin · 2019-08-28 · via 青空之蓝

为什么需要算法复杂度分析,实际运行一遍然后收集运行的信息和统计不就可以了?这总事后分析的方法确实没有错,但是当环境发生变化,数据量发生变化,或者数据模型改变了,程序的执行效率就有可能发生巨大的改变,所以我们需要一种粗略的不需要具体测试数据的估算程序执行效率的方法,而这种方法就是复杂度分析。

大 O 复杂度表示法

大 O 表示法是一种特殊的表示法,指出了算法的速度有多快或者多慢。谁在乎呢?实际上,你经常要使用别人编写的算法,在这种情况下,知道这些算法的速度大有裨益。

这里我们拿一段代码来进行解释:

int fun(int n) {
    int num = 0;
    for (int i = 1; i <= n; i++) {
        sum = sum + i;
    }
    return sum;
}
int fun(int n) {
    int num = 0;
    for (int i = 1; i <= n; i++) {
        sum = sum + i;
    }
    return sum;
}

这段代码是用来求数列和的,我们把每一句代码执行的时间记为 1u,然后默认每句基础代码执行的时间都是一样的,从上面的代码我们可以看出,int num = 0;int i = 1;各执行了 1 次,sum = sum + i;i++各执行了 n 次,return部分我们不看,那么这个函数所执行的总时间就应该是(2n+2)*1u,我们把总时间记为 T(n)那么,可以看出执行的总时间和 n 成正比关系,即和执行次数成正比关系,而这种规律我们可以总结成一个公式即:

T(n) = O[f(n)]

而上一个公式就可以表示成T(n) = O(2n+2),当 n 到大一定程度的时候我们就可以把+2 和 n 的系数 2 给忽略掉,这时候的T(n) = O(n),这就是我们常说的 O(n)时间复杂度或 O(n)空间复杂度的由来。

时间复杂度分析

那么当代码越来越长,越来越庞大是时候难道还这样一步步分析吗?当然不是,当我们对算法或者程序进行分析的时候我们只需要关注以下几点即可:

1. 只关注循环次数最多的代码

还是拿上面一段代码来分析。

int fun(int n) {
    int num = 0;
    for (int i = 1; i <= n; i++) {
        sum = sum + i;
    }
    return sum;
}
int fun(int n) {
    int num = 0;
    for (int i = 1; i <= n; i++) {
        sum = sum + i;
    }
    return sum;
}

从代码中我们可以看出,只有一个循环 n 次的 for 循环,而其他部分的代码只执行了一次,这时我们就可以直接忽略掉那些只执行一次的代码,只关注执行最多的代码,即 for 循环中的代码,所以这个函数的时间复杂度就是 O(n)。

2. 加法法则:只关注执行次数最多的代码的时间复杂度

int fun(int n) {
    for (int i = 0; i < 10; i++) {
        System.out.println("a");
    }
    for (int i = 0; i < n; i++) {
        System.out.println("a");
    }
    for (int i = 0; i < n*10; i++) {
        System.out.println("a");
    }
    for (int i = 0; i < n*n; i++) {
        System.out.println("a");
    }
}
int fun(int n) {
    for (int i = 0; i < 10; i++) {
        System.out.println("a");
    }
    for (int i = 0; i < n; i++) {
        System.out.println("a");
    }
    for (int i = 0; i < n*10; i++) {
        System.out.println("a");
    }
    for (int i = 0; i < n*n; i++) {
        System.out.println("a");
    }
}

从上面的代码我们可以看到 4 个 for 循环这时候应该如何判断呢?首先我们先找出它们各自循环了几次,第一个执行了 10 次,第二个执行了 n 次,第三个执行了 10n 次,第四个执行了 n2 次,这时候我们只需要关注执行最多的那个循环的复杂度即可,即第四次(我们均默认 n 很大),所以这段函数的时间复杂度为 O(n2)。

3. 乘法法则:嵌套代码的时间复杂度等于嵌套内外的时间复杂度的乘积

int fun(int n) {
    for (int i = 0; i < n; i++) {
        cal(n);
    }
}

int cal(int n) {
    for (int i = 0; i < n; i++) {
        System.out.println("a");
    }
}
int fun(int n) {
    for (int i = 0; i < n; i++) {
        cal(n);
    }
}

int cal(int n) {
    for (int i = 0; i < n; i++) {
        System.out.println("a");
    }
}

从上方代码看出,fun 函数中的 for 循环每次循环都需要调用一次 cal 函数,而我们可以很轻松的判断出 cal 函数的时间复杂度是 O(n),而 fun 一共需要调用 n 次 cal 函数,即 O(n) * O(n),即 O(n*n),简化下就是 O(n2)。

到这里我们就基本讲完了时间复杂度的分析方法了,下面我们就来看以下几种常见 的算法时间复杂度。

番外:常见的时间复杂度

1. O(1)

这个时间复杂度并不是表示只有一句基础代码,而是代表常数的时间复杂度,即使你有 100 句基础代码也不能表示为 O(100),而应该表示为 O(1)。

2. O(logn), O(nlogn)

对数阶时间复杂度非常常见,同时也是最难分析的一种时间复杂度。 我这里就拿算法图解这本书中的一张图来说明:

来自《算法图解》

我们要得到一张 16 格的纸,如果采用对半折的方式就只需要 4 次就能完成,即 4 = log216 若把 4 换成 T(n),16 换成 n,我们就可以得到 T(n) = O(log2n),然后我们忽略掉常数的底数,就变成了 O(logn),并不是只有 2 才能忽略掉底数,所有的常数底数都可以被忽略。

3. O(m+n)、O(m*n)

或许你有看过这种复杂度,或许没有看过,这种其实非常简单,我们通过下面的例子来进行分析:

int fun(int m, int n) {
    for (int i = 0; i < m; i++) {
        System.out.println("a");
    }
    for (int i = 0; i < n; i++) {
        System.out.println("a");
    }
}
int fun(int m, int n) {
    for (int i = 0; i < m; i++) {
        System.out.println("a");
    }
    for (int i = 0; i < n; i++) {
        System.out.println("a");
    }
}

当我们看到这个函数的时候是不是就应该通过加法法则排除掉一个呢?然后你会发现若不知道 m 和 n 的大小关系根本无法对其进行判断,也就无法排除掉其中的一项,这时候就应该保留两个未知数,即 O(m + n),另外的 O(m * n)同理。

空间复杂度分析

空间复杂度的分析十分的简单,我们直接通过一个例子来进行分析吧:

int fun(int n) {
    int[] arr = new int[n];
    return arr;
}
int fun(int n) {
    int[] arr = new int[n];
    return arr;
}

这段例子非常简单,就是创建了一个大小为 n 的数组,所以这段代码的时间复杂度就是 O(n)。

结语

复杂度的分析其实不难,只要多练习,多分析就能熟练的掌握它,还有最好时间复杂度,最坏时间复杂度,以及平均时间复杂度等一些分析我留着下一篇在说吧,零零散散的写了一篇文章,还是挺累的,完全手打了 2300+个字,其实是懒 (逃,( ̄ y▽, ̄)╭ 。

算法复杂度分析(1)

https://blog.ixk.me/post/algorithm-complexity-analysis-1
  • 许可协议

    BY-NC-SA

  • 本文作者

    Otstar Lin

  • 发布于

    2019/08/28

转载或引用本文时请遵守许可协议,注明出处、不得用于商业用途!

MySQL事务隔离一年来的经验总结