数据结构与算法——3.时间复杂度分析1(概述)

news2024/9/29 18:07:05

前面我们已经介绍了,研究算法的最终目的是如何花费更少的时间,如何占用更少的内存去完成相同的需求,并且也通过案例演示了不同算法之间时间耗费和空间耗费上的差异,但我们并不能将时间占用和空间占用量化。因此,接下来我们要学习有关算法时间耗费和算法空间耗费的描述与分析。有关算法时间耗费分析,我们称之为算法的时间复杂度分析。

1.时间复杂度分析方法

我们要计算算法耗费时间情况,首先我们得度量算法的执行时间,那么如何度量呢?

1.1事后分析估算方法

比较容易想到的方法就是我们把算法执行若干次,然后拿个计时器在旁边计时,这种事后统计的方法看上去的确不错,并且也并非要我们真的拿个计算器在旁边计算,因为计算机都提供了计时的功能。这种统计方法主要是通过设计好的测试程序和测试数据,利用计算机计时器对不同的算法编制的程序的运行时间进行比较,从而确定算法效率的高低,但是这种方法有很大的缺陷︰必须依据算法实现编制好的测试程序,通常要花费大量时间和精力,测试完了如果发现测试的是非常糟糕的算法,那么之前所做的事情就全部白费了,并且不同的测试环境(硬件环境)的差别导致测试的结果差异也很大。

 如下例所示:

 public static void main(String[] args) {
        long start = System.currentTimeMillis();
        int sum = 0;
        int n = 100;
        for (int i = 1; i <= n; i++) {
            sum +=i;
        }
        System.out.println("sum="+sum);
        long end = System.currentTimeMillis();
        System.out.println(end-start);
    }

如何理解该方法的缺陷?
举例说明:比如我们用来三天的时间写了一个测试某个确定算法的程序,然后运行它,测试这个算法的时间,发现测试时间很长,那在实际中这个算法肯定就不能使用啊,那也就是说这个算法是不好的,是有缺陷不能用的,那么你写的这个测试程序也是不能用的,也就是说你白花了三天时间。这就是一个极大的缺陷。

1.2事前分析估算方法

在计算机程序编写之前,依据统计方法对算法进行估算,经过总结,我们发现一个高级语言编写的程序,程序在计算机上运行消耗的时间取决于下列因素:

  1. 算法采用的策略和方案;
  2. 编译产生的代码质量;(不可人为干预)
  3. 问题的输入规模(所谓的问题输入规模就是输入量是多少);
  4. 机器执行指令的速度;(不可人为干预)

由此可见,抛开那些与计算机硬件、软件有关的因素,一个程序的运行时间依赖于算法的好坏和问题的输入规模。如果算法固定,那么该算法的执行时间就只和问题的输入规模有关系了。

2.案例说明

2.1案例一

题目:计算1到100的和

方法一:


    //如果输入量n为1,则需要计算1次
    //如果输入量n为1亿,则需要计算1亿次
public static void main(String[] args) {
        int sum = 0;  //执行1次
        int n = 100;  //执行1次
        for (int i = 1; i <= n; i++) {//执行n+1次
            sum +=i;  //执行n次
        }
        System.out.println("sum="+sum);
    }

方法二:

    //如果输入量n为1,则需要计算1次
    //如果输入量n为1亿,则需要计算1亿次
    public static void main(String[] args) {
        int sum = 0;  //执行1次
        int n = 100;  //执行1次
        sum = (n+1)*n/2; //执行1次
        System.out.println("sum="+sum);
    }

分析:

方法一,当输入规模为n时,方法一执行了1+1+(n+1)+n=2n+3次

方法二,当输入规模为n时,方法二执行了1+1+1=3次

定量具体分析来看,很明显,方法二的执行次数是要远少于方法一的。

下面,我们来深入分析一次这两段代码:

方法一,这个算法求和的核心代码是那个for循环,如果我们把这个for循环看成是一个整体,不考虑什么判断条件啊,什么次数啊,只考虑这个循环里面的内容,然后再忽略其他的一些简要的一次就执行的代码,那么方法一的执行次数就简化为n次

方法二,同样的道理,我们简化那么简单的一次就行的代码语句,重点关注算法的核心语句的执行次数,那么方法二的执行次数就简化为1次

再对比一下,两个方法运行时间的差距就是n与1的差距。

注意:这里我们用了简化思维,直接去抓取每个算法的核心部分,并分析这个核心部分执行的次数

问题:

为什么循环判断条件在方法一中执行了n+1次,看起来是个不小的数字,但是可以忽略呢?

答:看2.2案例二

2.2案例二

题目:计算100个1+100个2+100个3+,,,,,,+100个100的结果

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

上面这个例子中,如果我们要精确的研究循环的条件执行了多少次,是一件很麻烦的事情,并且,由于真正计算和的代码是内循环的循环体,所以,在研究算法的效率时,我们只考虑核心代码的执行次数,这样可以简化分析。

下面给出2.1中问题的四个答案,可以酌情选择思考

答案1:精确循环次数很麻烦

第一个for循环的条件判断执行了100次,第二个for循环的条件判断执行了10000次,但是请思考下面的几种情况:如果有5层循环,每层次数不一样,让你求第4层循环的判断次数,是不是很麻烦?如果上题中,n=1234,求第二层循环的判断次数是不是也很麻烦?所以,我们忽略循环的判断次数。

答案2:只关心得到结果的最核心部分

上面的所有循环都是为了sum+=i 这句服务的,并且这个算法的最核心部分也是sum+=i,最后的结果也是由这一句得到的,所以,我们只关心这一句。

答案3:当做整体看

因为你每执行一次sum+=i 这句,那就比如执行了前面的for循环的条件判断啊,这二者是一个整体的,是密不可分的,所以就忽略了for的条件判断,只关心那一句

答案4:老师讲的!

我们研究算法复杂度,侧重的是当输入规模不断增大时,算法的增长量的一个抽象(规律),而不是精确地定位需要执行多少次,因为如果是这样的话,我们又得考虑回编译期优化等问题,容易主次跌倒。
我们不关心编写程序所用的语言是什么,也不关心这些程序将跑在什么样的计算机上,我们只关心它所实现的算法。这样,不计那些循环索引的递增和循环终止的条件、变量声明、打印结果等操作,最终在分析程序的运行时间时,最重要的是把程序看做是独立于程序设计语言的算法或一系列步骤。我们分析一个算法的运行时间,最重要的就是把核心操作的次数和输入规模关联起来

3.函数渐进增长

概念:

给定两个函数f(n)和g(n)如果存在一个整数N,使得对于所有的n>N,f(n)总是比g(n)大,那么我们说f(n)的增长渐近快于(gn)。概念似乎有点艰涩难懂,那接下来我们做几个测试。

3.1测试1

假设四个算法的输入规模都是n :

  1. 算法A1要做2n+3次操作,可以这么理解︰先执行n次循环,执行完毕后,再有一个n次循环,最后有3次运算
  2. 算法A2要做2n次操作
  3. 算法B1要做3n+1次操作,可以这个理解∶先执行n次循环,再执行一个n次循环,再执行一个n次循环,最后有1次运算
  4. 算法B2要做3n次操作

那么,上述算法,哪一个更快一些呢?

通过数据表格,比较算法A1和算法B1 ∶

  • 当输入规模n=1时,A1需要执行5次,B1需要执行4次,所以A1的效率比B1的效率低;
  • 当输入规模n=2时,A1需要执行7次,B1需要执行7次,所以A1的效率和B1的效率一样;
  • 当输入规模n>2时,A1需要的执行次数一直比B1需要执行的次数少,所以A1的效率比B1的效率高

所以我们可以得出结论︰

                当输入规模n>2时,算法A1的渐近增长小于算法B1的渐近增长

通过观察折线图,我们发现,随着输入规模的增大,算法A1和算法A2逐渐重叠到一块,算法B1和

算法B2逐渐重叠到一块,所以我们得出结论︰

                随着输入规模的增大,算法的常数操作可以忽略不计

3.2测试2

假设四个算法的输入规模都是n :

  1. 算法C1需要做4n+8次操作
  2. 算法C2需要做n次操作
  3. 算法D1需要做2n^2次操作
  4. 算法D2需要做n^2次操作

那么上述算法,哪个更快一些?

 

通过数据表格,对比算法C1和算法D1 :

  • 当输入规模n<=3时,算法C1执行次数多于算法D1,因此算法C1效率低一些;
  • 当输入规模n>3时,算法C1执行次数少于算法D1,因此,算法D2效率低一些,所以,总体上,算法C1要优于算法D1

通过折线图,对比对比算法C1和C2:

        随着输入规模的增大,算法C1和算法C2几乎重叠

通过折线图,对比算法C系列和算法D系列:

        随着输入规模的增大,即使去除n^2前面的常数因子,D系列的次数要远远高于C系列。

因此,可以得出结论:

        随着输入规模的增大,与最高次项相乘的常数可以忽略
 

3.3测试3

假设四个算法的输入规模都是n:

  • 算法E1:2n^2+3n+1;
  • 算法E2:n^2
  • 算法F1:2n^3+3n+1
  • 算法F2:n^3

那么上述算法,哪个更快一些?

通过数据表格,对比算法E1和算法F1 :

  • 当n=1时,算法E1和算法F1的执行次数一样;
  • 当n>1时,算法E1的执行次数远远小于算法F1的执行次数;

        所以算法E1总体上是由于算法F1的。

通过折线图我们会看到,算法F系列随着n的增长会变得特块,算法E系列随着n的增长相比较算法F

来说,变得比较慢,所以可以得出结论:

        最高次项的指数大的,随着n的增长,结果也会变得增长特别快

3.4测试4

假设五个算法的输入规模都是n :

  • 算法G :n^3;
  • 算法H:n^2;
  • 算法I:n;
  • 算法J:logn;
  • 算法K:1;

那么上述算法,哪个效率更高呢?

通过观察数据表格和折线图,很容易可以得出结论:

        算法函数中n最高次幂越小,算法效率越高

3.5小结

总上所述,在我们比较算法随着输入规模的增长量时,可以有以下规则:

  1. 算法函数中的常数可以忽略
  2. 算法函数中最高次幂的常数因子可以忽略
  3. 算法函数中是高次幂越小,算法效率越高

4.大O计法

4.1具体定义

定义:

在进行算法分析时,语句总的执行次数T(n)是关于问题规模n的函数,进而分析T(n)随着n的变化情

况并确定T(n)的量级。算法的时间复杂度,就是算法的时间量度,记作:T(n)=O(f(n))。它表示随

着问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐近时间复杂度,简

称时间复杂度,其中f(n)是问题规模n的某个函数。

        在这里,我们需要明确一个事情:执行次数=执行时间

        用大写O()来体现算法时间复杂度的记法,我们称之为大O记法。一般情况下,随着输入规模n

的增大,T(n)增长最慢的算法为最优算法。

4.2案例分析

下面用大O表示法来表示一些求和算法的时间复杂度

算法一:

    public static void main(String[] args) {
        int sum = 0;//执行1次
        int n = 100;//执行1次
        sum = (n+1)*n/2;//执行1次
        System.out.println("sum="+sum);
    }

算法二:

    public static void main(String[] args) {
        int sum = 0;//执行1次
        int n = 100;//执行1次
        for (int i = 1; i <= n; i++) {
                sum+=i;//执行n次
        }
        System.out.println("sum="+sum);
    }

算法三:

 public static void main(String[] args) {
        int sum = 0;//执行1次
        int n = 100;//执行1次
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <=n ; j++) {
                sum+=i;//执行n^2次
            }
        }
        System.out.println("sum="+sum);
    }

如果忽略判断条件的执行次数和输出语句的执行次数,那么当输入规模为n时,以上算法执行的次

数分别为:

算法一:3次

算法二:n+3次

算法三:n^2+2次

如果用大o记法表示上述每个算法的时间复杂度,应该如何表示呢?基于我们对函数渐近增长的分

析,推导大O阶的表示法有以下几个规则可以使用:

  1. 用常数1取代运行时间中的所有加法常数
  2. 在修改后的运行次数中,只保留高阶项
  3. 如果最高阶项存在,且常数因子不为1,则去除与这个项相乘的常数

所以,上述算法的大O计法分别为:

        算法一:O(1)

        算法二:O(n)

        算法三:O(n^2)

5.小结

这篇文章,首先我们给出了算法的时间复杂度的分析方法,引导我们如何去分析一个算法的时间复杂度,然后讲了一系列的函数渐进增长,通过具体的实例总结了一些结论,最后我们在这些结论的基础上提出来大O计法,然后结合大O计法和那些结论,我们实际的分析了一些求和算法的时间复杂度。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/375129.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【经验总结】10年的嵌入式开发老手,到底是如何快速学习和使用RT-Thread的?

【经验总结】一位近10年的嵌入式开发老手&#xff0c;到底是如何快速学习和使用RT-Thread的&#xff1f; RT-Thread绝对可以称得上国内优秀且排名靠前的操作系统&#xff0c;在嵌入式IoT领域一直享有盛名。近些年&#xff0c;物联网产业的大热&#xff0c;更是直接将RT-Thread这…

Redis | 安装Redis和启动Redis服务

目录 一、Redis简介 1.1 简介 二、Redis安装 2.1 Windows安装Redis 2.2 Linux安装Redis 三、Redis服务启动和停止 3.1 Windows启动Redis服务 3.2 Linux启动Redis服务 四、Redis设置密码远程连接 4.1 为Redis登陆设置密码 4.2 设置Redis允许远程连接 五、Redis常…

STM32CubeMX按键模块化 点灯

本文代码使用 HAL 库。 文章目录前言一、按键原理图二、CubeMX 创建工程三、代码讲解&#xff1a;1. GPIO的输入HAL库函数&#xff1a;2. 消抖&#xff1a;3. 详细代码四&#xff0c;实验现象&#xff1a;总结前言 我们继续讲解 stm32 f103&#xff0c;这篇文章将详细 为大家讲…

哪个品牌蓝牙耳机性价比高?性价比高的平价蓝牙耳机推荐

现如今&#xff0c;随着蓝牙技术的进步&#xff0c;蓝牙耳机在人们日常生活中的便捷性更胜从前。越来越多的蓝牙耳机品牌被大众看见、认可。那么&#xff0c;哪个品牌的蓝牙耳机性价比高&#xff1f;接下来&#xff0c;我给大家推荐几款性价比高的平价蓝牙耳机&#xff0c;一起…

Idea启动遇到 Web server failed to start. Port 8080 was already in use. 报错

Idea启动遇到问题-记录 报错英文提示&#xff1a; APPLICATION FAILED TO START Description: Web server failed to start. Port 8080 was already in use. Action: Identify and stop the process that’s listening on port 8080 or configure this application to liste…

《C++模板进阶》

致前行的人&#xff1a; 要努力&#xff0c;但不要着急&#xff0c;繁花锦簇&#xff0c;硕果累累都需要过程&#xff01; 目录 前言&#xff1a; 1.非类型模板参数 1.1.概念&#xff1a; 1.2.使用注意事项 2.模板特化 2.1函数模板特化 2.2类模板特化 3.模板的分离编译 3.1什么…

【手撕面试题】JavaScript(高频知识点二)

目录 面试官&#xff1a;请你谈谈JS的this指向问题 面试官&#xff1a;说一说call apply bind的作用和区别&#xff1f; 面试官&#xff1a;请你谈谈对事件委托的理解 面试官&#xff1a;说一说promise是什么与使用方法&#xff1f; 面试官&#xff1a;说一说跨域是什么&a…

Python 之 Pandas 文件操作和读取 CSV 参数详解

文章目录一、Pandas 读取文件二、CSV 文件读取1. 基本参数2. 通用解析参数3. 空值处理相关参数4. 时间处理相关参数5. 分块读入相关参数一、Pandas 读取文件 当使用 Pandas 做数据分析的时&#xff0c;需要读取事先准备好的数据集&#xff0c;这是做数据分析的第一步。Panda 提…

Cocoa-presentViewController

presentViewController:animator: 将一个viewController以动画方式显示出来 当VCA模态的弹出了VCB&#xff0c;那么VCA就是presenting view controller&#xff0c;VCB就是presented view controller presentViewController 相较于addSubView 直接作为subView就是不会出现一…

VUE的安装和创建

安装node.js 进入node官网进行下载&#xff0c;然后一直下一步。 测试是否安装成功&#xff1a; 命令提示窗下执行&#xff1a;npm -v 若出现版本号&#xff0c;则安装成功。 安装npm源&#xff1a; npm config set registry http://registry.npm.taobao.org 查看&#xff1a;…

C/C++网络编程笔记

https://www.bilibili.com/video/BV11Z4y157RY/?vd_sourced0030c72c95e04a14c5614c1c0e6159b这个视频里面通过简单的例子&#xff0c;讲了socket&#xff0c;对于小白来说还比较友好&#xff0c;我这里做个笔记。让网络通信跑起来我只有本科时候学过一点点C基础&#xff0c;但…

taobao.top.secret.bill.detail( 服务商的商家解密账单详情查询 )

&#xffe5;免费必须用户授权 服务商的商家解密账单详情查询&#xff0c;仅对90天内的账单提供SLA保障。 公共参数 请求地址: HTTP地址 http://gw.api.taobao.com/router/rest 公共请求参数: 公共响应参数: 请求参数 响应参数 点击获取key和secret 请求示例 TaobaoClient…

【LVGL】学习笔记--(3)界面切换以及显示优化

一 界面切换利用lvgl框架绘制GUI免不了需要实现多个页面的切换&#xff0c;毕竟把所有功能和接口都放在一页上有些不太优雅&#xff0c;而且对于嵌入式硬件的小屏幕也有些过于困难。因此这里就需要实现多个页面&#xff08;或者说lvgl里的screen&#xff09;及其互相切换。实现…

初识机器学习

监督学习与无监督学习supervised learning&#xff1a;监督学习&#xff0c;给出的训练集中有输入也有输出&#xff08;标签&#xff09;&#xff08;也可以说既有特征又有目标&#xff09;&#xff0c;在此基础上让计算机进行学习。学习后通过测试集测试给相应的事物打上标签。…

聚观早报|知名品牌3月暂别中国市场;金山办公22年营收38.85亿元

今日要闻&#xff1a;知名品牌3月31日起暂别中国市场&#xff1b;英特尔中国开源技术委员会宣布成立&#xff1b;金山办公2022年营收38.85亿元&#xff1b;美国推特公司进行第八轮裁员&#xff1b;Meta 官宣深入 AI 大战&#xff01; 知名品牌3月31日起暂别中国市场 近日&#…

中级嵌入式系统设计师2015下半年上午试题及答案解析

中级嵌入式系统设计师2015下半年上午试题 单项选择题 1、CPU是在______结束时响应DMA请求的。 A.一条指令执行 B.一段程序 C.一个时钟周期 D.一个总线周期 2、虚拟存储体系由______两级存储器构成。 A.主存-辅存 B.寄存器-Cache C.寄存器-主存

CHAPTER 2 CentOS的日志系统(日志工具)

日志工具2.1 rsyslogd(syslogd)2.1.1 介绍2.1.2 语法2.1.3 配置文件syslog.conf2.1.4 syslog.conf的配置规则2.1.5 示例2.2 logrotate2.2.1 介绍2.2.2 配置文件2.2.3 示例一2.2.4 示例二2.3 dmesg2.3.1 命令简介2.3.2 使用示例2.4 关于重启/死机的日志2.4.1 last2.4.2 日志查看…

【面试一:|和||、和区别】

相同点&#xff1a; ||和&&都是逻辑运算符&#xff0c;而|和&是位运算符。位运算符的优先级要比逻辑运算符的优先级高。 &和&&的区别 &和&&都可以用作逻辑与的运算符&#xff0c;表示逻辑与&#xff08;and&#xff09;&#xff0c;当运…

尚医通(二十五)就医提醒和预约统计

目录一、就医提醒1、搭建定时任务模块二、后台管理系统-预约统计功能1、开发每天预约数据接口2、封装远程调用接口3、搭建统计分析模块4、整合统计功能前端一、就医提醒 我们通过定时任务&#xff0c;每天8点执行&#xff0c;提醒就诊 1、搭建定时任务模块 &#xff08;1&…

Python IDE:对于 Python 初学者来说,最好的 IDE 是什么?

Python 是科技界最简单、使用最广泛的编程语言之一。它是一种高级通用编程语言&#xff0c;强调代码可读性并使用面向对象的方法。Python可以用来完成很多任务&#xff0c;包括网站开发、软件开发、 自动化 和数据分析 专业开发人员使用Python开发各种流行的软件程序&#xff0…