数据结构之时间复杂度和空间复杂度的相关计算

news2024/11/16 3:44:04

找往期文章包括但不限于本期文章中不懂的知识点:

个人主页:我要学编程(ಥ_ಥ)-CSDN博客

所属专栏:数据结构(Java版)

目录

时间复杂度 

概念

大O的渐进表示法

相关练习 

例1:

例2:

例3:

例4:

例5:

例6: 

例7: 

例8:

空间复杂度

概念:

相关练习:

例1:

例2:

例3: 


接下来,将开始数据结构的学习了。

我们如果衡量一个算法的好坏呢?这个算法到底怎么样呢?

有的小伙伴可能会说:直接把这个代码拷贝到编译器中,看看运行时间是多少,不就行了嘛。的确这个方法在同样的情况下确实可以。比如说:同样是计算斐波那契数列的第 n 项。下面有两份代码,我们就可以把它们分别给到编译器,让其运行看时间是多少?

public class Test {
    public static int fib(int n) {
        // 递归求
        if (n < 2) {
            return 1;
        }
        return fib(n-1) + fib(n-2);
    }
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        System.out.println(fib(n));
    }
}

public class Test {
    public static int fib(int n) {
        // 迭代求
        int c = 0;
        int a = 1;
        int b = 1;
        if (n < 2) {
            return b;
        }
        for (int i = 2; i <= n; i++) {
            c = a + b;
            a = b;
            b = c;
        }
        return c;
    }
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        System.out.println(fib(n));
    }
}

由上可见:用迭代求斐波那契数列的第 n 项比用递归求效率更高,也就是说迭代的算法思想在这里的应用更好。

但是如果每一个代码都用编译器去跑,那就有点浪费时间了,并且还不一定准确。因为电脑的处理器不一样,效率肯定也是不一样的。因此,就提出了用时间复杂度的概念和空间复杂度的概念来重新作为这个标准。 下面就来介绍这两个概念。

时间复杂度 

概念

什么是时间复杂度?时间复杂度主要衡量的是一个算法的运行速度,而空间复杂度主要衡量一个算法所需要的额外空间。在计算机发展的早期,计算机的存储容量很小。所以对空间复杂度很是在乎。但是经过计算机行业的迅速发展,计算机的存储容量已经达到了很高的程度。所以我们如今已经不需要再特别关注一个算法的空间复杂度。

大O的渐进表示法

概念我们已经知道了,那么接下来就该了解,时间复杂度的计算了。时间复杂度就是通过代码的执行次数来确定的。实际中我们计算时间复杂度时,我们其实并不一定要计算精确的执行次数,而只需要大概执行次数,那么这里我们使用大O的渐进表示法。

当推导出了代码的执行次数之后,就需要用到下面的规则,来简化得到最终的表达式。

规则:

1、用常数1取代运行时间中的所有加法常数。

2、在修改后的运行次数函数中,只保留最高阶项。

3、如果 最高阶项的系数 不是1,则把这个系数变成1。得到的结果就是大O阶。

相关练习 

求下列代码的时间复杂度。

例1:

    void func1(int N) {
        int count = 0;
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < N; j++) {
                count++;
            }
        }
        for (int k = 0; k < 2 * N; k++) {
            count++;
        }
        int M = 10;
        while ((M--) > 0) {
            count++;
        }
        System.out.println(count);
    }

所以最终的执行次数是: N^2 + 2N + 10 。这个结果肯定是符合上面的化简规则的。

首先,根据规则1,把10变成了1,N^2 + 2N + 1 ;

其次,根据规则2,只保留最高阶项,N^2 ;

最后,去掉最高阶项的系数。因为这里的最高阶项的系数是1,因此就不变:O(N) 。

这里也可以证明一下这个规则:

因为这个N的取值是不固定的,如果这个N的取值是100的话,那么这个10对最终结果的影响不是很大。因此可以用1来代替。既然N的取值可以是100,那也就是10000,甚至更大。我们在数学中学过随着X的增大,X^2 与 2X的差值同样是越来越大。因此 2X 也是可以舍去的。因此最终就只剩下了最高阶项,同样其系数对齐的影响同样不大。

通过上面我们会发现大O的渐进表示法去掉了那些对结果影响不大的项,简洁明了的表示出了执行次数。另外有些算法的时间复杂度存在最好、平均和最坏情况: 最坏情况:任意输入规模的最大运行次数(上界) ;平均情况:任意输入规模的期望运行次数 ;最好情况:任意输入规模的最小运行次数(下界) 。

例如:在一个长度为N数组中搜索一个数据 x 最好情况:1次找到 ;最坏情况:N次找到。平均情况:N/2次找到。

在实际中一般情况关注的是算法的最坏运行情况,所以数组中搜索数据时间复杂度为O(N)。

例2:

    void func2(int N) {
        int count = 0;
        for (int k = 0; k < 2 * N ; k++) {
            count++;
        }
        int M = 10;
        while ((M--) > 0) {
            count++;
        }
        System.out.println(count);
    }

例3:

    void func3(int N, int M) {
        int count = 0;
        for (int k = 0; k < M; k++) {
            count++;
        }
        for (int k = 0; k < N ; k++) {
            count++;
        }
        System.out.println(count);
    }

注意:时间复杂度计算的是执行次数最多的,但是这里的M和N并未表明具体值,因此都不能省略。

例4:

    void func4(int N) {
        int count = 0;
        for (int k = 0; k < 100; k++) {
            count++;
        }
        System.out.println(count);
    }

注意:这里的 N 其实就是用来迷惑我们的。 

例5:

    void Swap(int[] array, int i , int j) {
        int tmp = array[i];
        array[i] = array[j];
        array[j] = tmp;
    }

    void bubbleSort(int[] array) {
        for (int end = array.length; end > 0; end--) {
            boolean sorted = true;
            for (int i = 1; i < end; i++) {
                if (array[i - 1] > array[i]) {
                    Swap(array, i - 1, i);
                    sorted = false;
                }
            }
            // 如果 sorted 为true,就说明这组数据已经是有序的了
            if (sorted) {
                break;
            }
        }
    }

变型:刚刚我们求的是最坏的情况,现在我们来求最好的情况。

首先,得想一下什么时候冒泡排序的情况最好,既然前面,我们在算最坏的情况是是每一个都要执行,也就是说数组中元素的顺序是刚好和我们要排的序是相反的,因此每一项都得重新排序。那么最好的情况也就可以分析的出来了,就是当这个数组中元素的顺序刚好和我们要排的序是相同的,也就是说这个数组已经是有序的了。怎么知道有序呢?就是通过这个 sorted 来判断的,如果是true,就说明已经有序;否则,就是无序。要知道有序,肯定得把这个数组遍历一遍才行。那么执行的次数就是N次,时间复杂度就是:O(N) = N 。

例6: 

    int binarySearch(int[] array, int value) {
        int begin = 0;
        int end = array.length - 1;
        while (begin <= end) {
            int mid = begin + ((end-begin) / 2);
            if (array[mid] < value)
                begin = mid + 1;
            else if (array[mid] > value)
                end = mid - 1;
            else
                return mid;
        }
        return -1;
    }

上面是一个二分查找的代码,和我们前面的代码有点不一样。这个是 while循环,没有明确表明循环内部代码的执行次数,而 for循环明确表明了会执行多少次。同样计算时间复杂度是按照最坏的情况来分析的,二分查找,什么情况最坏呢? 就是当我们找到了只剩下最后一个元素的时候,这个时候已经没有其他元素了,只有这个元素了或者找不到。只要比较一下,就可以了。

至于为什么在元素个数为1时就可以停止查找,这是因为二分查找的核心思想是通过不断缩小查找区间来定位目标值。当区间缩小到只包含一个元素时,这个元素要么就是我们要找的目标,要么就说明目标不存在于数组中(如果是在查找过程中没有提前终止的话)。在这种情况下,我们不需要也不应该再继续“分半”查找,因为没有更小的区间可以探索了,直接判断这最后一个元素是否为目标即可。因此当只剩下一个元素的时候,不需要再查找了,只需要比较就行了。而比较是在查找的代码次数中,因此不需要再+1了。

数组元素个数为 N

设查找的次数为 X 时,此时元素个数为 1,

那么可得出:N /  2 ^​ X = 1

⇒ N= 2^X

log2 ​N=log2​  2^X = X

X = log2​ N

因此查找的次数就是x,那么对应的时间复杂度也就是:O(log2 N)(已是最简:无常数项、只有一项,无需化简)。 

注意:这里的log2 N,是表示以2为底,N的对数。但是有时候会把这个简写成lg N ,我们也知道这个是以10为底,N的对数,这个也算是对的,注意一下就行了。

例7: 

    long factorial(int N) {
        return N < 2 ? N : factorial(N-1) * N;
    }

递归的时间复杂度 = 递归的次数 * 递归中的代码执行的次数。

因为递归其实也算是另一种意义上的迭代(循环),递归的次数就是外层循环的次数,每一次递归的之后执行的次数就是内层循环执行的次数。

递归执行了N-1次,根据规则化简:常数去掉。因此递归的时间复杂度是:O(N)

注意:这里可能有小伙伴会疑惑:递归的过程中递是1次,归也是一次,不应该递归的次数 X 2吗? 不不不,递归在递的时候,那并没有执行完1次,执行到一半的时候就递下去了,只有归的时候这1次才算执行完毕了。

例8:

    int fibonacci(int N) {
        return N < 2 ? N : fibonacci(N-1)+fibonacci(N-2);
    }

再经过规则化简(去掉常数),最终的时间复杂度就是O(2^N) 。

上面就是一系列有关时间复杂度的练习。下面我们再来学习空间复杂度。

空间复杂度

概念:

空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度 。空间复杂度不是程序占用了多少字节的空间,因为这个也没太大意义,所以空间复杂度算的是变量的个数。空间复杂度计算规则基本跟时间复杂度类似,也使用大O渐进表示法。

同样直接通过练习来感受一下吧。

相关练习:

例1:

    public void Swap(int[] array, int i , int j) {
        int tmp = array[i];
        array[i] = array[j];
        array[j] = tmp;
    }
    void bubbleSort(int[] array) {
        for (int end = array.length; end > 0; end--) {
            boolean sorted = true;
            for (int i = 1; i < end; i++) {
                if (array[i - 1] > array[i]) {
                    Swap(array, i - 1, i);
                    sorted = false;
                }
            }
            if (sorted) {
                break;
            }
        }
    }

计算空间复杂度就算看这个代码为了做这件事,创建了多少个临时变量。

end、sorted、i、Swap中的i、j 、tmp这些都是的,总共是6个。因为采用的是大O渐进表示法,所以常数化为1,最终的空间复杂度就是O(1)。

注意:

1. 可能有小伙伴会说这个数组为什么不算呢?因为这个数组是存储在堆区的,而我们只是创建了一个形参引用(不过这个形参得考虑进去,但因为是粗略估计,所以可以不算)来指向这个数组而已。并没有真正地在堆区用一块空间来创建数组。

2. 这个end 和 i 并不是说创建了100次,就占用了100分临时空间来存储它,永远只有一份空间,只不过这个空间中的值会发生变化而已。

例2:

    long[] fibonacci(int N) {
        long[] fibArray = new long[N + 1];
        fibArray[0] = 0;
        fibArray[1] = 1;
        for (int i = 2; i <= N ; i++) {
            fibArray[i] = fibArray[i - 1] + fibArray [i - 2];
        }
        return fibArray;
    }

上述代码是为了计算第n个斐波那契数。但是却创建了一个数组。因此空间中的临时变量个数是N+1、i 。根据规则(去掉常数):O(N)。

例3: 

    long factorial(int N) {
        return N < 2 ? N : factorial(N-1)*N;
    }

上述代码是通过递归来计算第N个斐波那契数。每一次递归都会创建一份新的临时空间,递归N次,会创建N-1分临时空间,再加上原本的空间,就是N分空间,因此最终的结果就是O(N)。

上面就是时间复杂度和空间复杂度的全部内容啦!

好啦!本期 初始Java篇(JavaSE基础语法)(8)认识String类(下)的学习之旅就到此结束了! 我们下一期再一起学习吧!

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

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

相关文章

Redis篇 Redis的基本命令和Keys命令

get,set命令和Keys命令 一. get,set命令二. keys命令1.?2. *3. [^]4.[] 一. get,set命令 在博主给大家说明这两个最核心的命令之前,还需要大家知道, Redis的客户端和服务器交互,需要非常非常多的命令 但是大家需要全记住吗? 大家只需要记住基本命令.并且会查询其他命令即可. …

http项目改为/支持https的方案、无需修改后台代码

背景描述&#xff1a;原来的项目前后台都是http&#xff0c;现在某个服务要求前台必须使用https&#xff1b; 方案1&#xff1a;前台部署在https里&#xff0c;后面代码修改&#xff1b;但是微服务架构&#xff0c;后台工作量太大&#xff1b; 方案2&#xff1a;前台部署在ht…

PySODEvalToolkit 使用笔记

1. 克隆仓库 首先&#xff0c;克隆PySODEvalToolkit仓库到你的本地机器&#xff1a; git clone https://github.com/lartpang/PySODEvalToolkit.git2. 创建虚拟环境 cd PySODEvalToolkit conda create -n pysodeval python3.73. 安装依赖 pip install -r requirements.txt4…

文心一言 VS 讯飞星火 VS chatgpt (266)-- 算法导论20.2 1题

一、写出 PROTO-vEB-MAXIMUM 和 PROTO-vEB-PREDECESSOR 过程的伪代码。如果要写代码&#xff0c;请用go语言。 文心一言&#xff1a; 首先&#xff0c;让我们明确一下 PROTO-vEB-MAXIMUM 和 PROTO-vEB-PREDECESSOR 这两个过程是在介绍van Emde Boas树&#xff08;简称vEB树&a…

Vue - JavaScript基础学习

一、语言概述 JavaScript 中的类型应该包括这些&#xff1a; 1.数字&#xff08;默认双精度区别于Java&#xff09; console.log(3 / 2); // 1.5,not 1 console.log(Math.floor(3 / 2)); // 10.1 0.2 0.30000000000000004NaN&#xff08;Not a Number&#x…

深入用户内心:设计师如何通过可用性测试洞察用户需求

可用性测试是指让用户体验产品的原型或成品。设计师通过观察和分析用户的使用行为和感受&#xff0c;进一步合理地改进产品的设计方法。你可能会想知道我们可以用什么方法来测试可用性&#xff1f;随着互联网行业的快速迭代更新&#xff0c;可用性测试衍生出了许多类型和方法。…

Packet Tracer-HSRP+DHCPv4+VLAN间路由+以太通道综合实验

实验拓扑&#xff1a; 实验内容&#xff1a; VLAN及VLAN间路由的配置&#xff0c;以太通道的配置&#xff0c;STP的根调整&#xff0c;DHCPv4的配置&#xff0c;首跳冗余HSRP的配置。 实验最终结果&#xff1a; PC可以自动获取到DHCP-Server分配的IP地址&#xff0c;实现首跳…

【人工智能】模型性能评估

模型性能衡量介绍 混淆矩阵 混淆矩阵(Confusion Matrix&#xff09; TP(真阳性)&#xff1a;预测为阳性&#xff0c;且预测正确。 TN(真阴性)&#xff1a;预测为阴性&#xff0c;且预测正确。 FP(伪阳性)&#xff1a;预测为阳性&#xff0c;但预测错误&#xff0c;又称型一误…

widedeep模型简介

wide&deep模型 1.简介2.原理2.1 网络结构 3. 稀疏密集特征4.API和子类方式实现 1.简介 Wide and deep 模型是 TensorFlow 在 2016 年 6 月左右发布的一类用于分类和回归的模型&#xff0c;并应用到了 Google Play 的应用推荐中。wide and deep 模型的核心思想是结合线性模…

VBA_MF系列技术资料1-615

MF系列VBA技术资料1-615 为了让广大学员在VBA编程中有切实可行的思路及有效的提高自己的编程技巧&#xff0c;我参考大量的资料&#xff0c;并结合自己的经验总结了这份MF系列VBA技术综合资料&#xff0c;而且开放源码&#xff08;MF04除外&#xff09;&#xff0c;其中MF01-0…

Ubuntu切换内核版本

#安装内核安装工具 sudo apt-get install software-properties-common sudo add-apt-repository ppa:cappelikan/ppa sudo apt-get update sudo apt-get install mainline#安装指定内核版本(有些版本并不能安装成功) mainline install 5.14.10#更新GRUB配置 sudo update-grub#查…

go mod模式下,import gitlab中的项目

背景 为了go项目能够尽可能复用代码&#xff0c;把一些公用的工具类&#xff0c;公用的方法等放到共用包里统一管理。把共用包放到gitlab的私有仓库中。 遇到的问题 通过https方式&#xff0c;执行go get报了错误。 通过ssh方式&#xff0c;执行go get报了错误。 修改配置&am…

Joomla 3.7.0 (CVE-2017-8917) SQL注入漏洞环境

1 漏洞概述 Joomla是一个基于PHP的内容管理系统&#xff08;CMS&#xff09;&#xff0c;广泛应用于各类网站。2017年&#xff0c;Joomla 3.7.0版本被发现存在SQL注入漏洞&#xff08;CVE-2017-8917&#xff09;&#xff0c;攻击者可以利用该漏洞对数据库进行未授权查询或操作…

副业树洞聊天项目/树洞倾诉/陪陪系统源码/树洞源码下载搭建

随着社会的发展和人们生活水平的提高&#xff0c;越来越多的人在面临心理压力、情感困扰或生活困境时&#xff0c;需要一个可以宣泄、倾诉和寻求支持的平台。而传统的人际交往方式往往会遇到难以排解的问题&#xff0c;比如担心被他人知晓自己的隐私等&#xff0c;这就导致了人…

鸿蒙系统和安卓系统通过termux搭建Linux系统—Centos

目录 1. 前言 2. 效果图展示 3. 安装termux 4. 安装Centos系统 4.1 更换源 4.2 拉取镜像 4.3 启动centos 5.结尾 1. 前言 大家好&#xff0c;我是jiaoxingk 今天这篇文章让你能够在手机或者平板上使用Linux-Centos系统 让你随时随地都能操作命令行进行装13 2. 效果图展示…

【科普知识】伺服电机中的内置制动器

在工业自动化和机器人技术快速发展的今天&#xff0c;伺服电机作为核心驱动元件&#xff0c;其性能与功能直接影响整个系统的运行效率与稳定性。 近年来&#xff0c;一体化伺服电机技术不断融合创新&#xff0c;并逐步加入了许多新的硬件和软件的功能&#xff0c;为工业自动化领…

全域外卖是谁创办的公司?

全域外卖是谁创办的公司&#xff1f;这个问题是抽象的。正确的问法应该是全域外卖是谁研发的系统。 在了解全域外卖系统前&#xff0c;我们首先要了解什么是全域外卖&#xff0c;什么是全域团购。全域指的是多平台。当然这个平台是越多越好。实际上也可以理解为聚合外卖、聚合…

图数据库助力供应链柔性升级

导读 当今市场环境受短视频等流媒体影响&#xff0c;任何风险事件在社交网络中传播速度极其迅速&#xff0c;留给企业的反应时间按分秒计&#xff0c;传统供应链的年度计划面对剧烈变化的市场环境已失去意义。此外&#xff0c;受近年局势动荡的影响&#xff0c;市场需求和供应…

Unity 资源 之 限时免费的Lowpoly农场动物,等你来领!

Unity资源 之 Lowpoly farm animals 农村动物 前言资源包内容领取兑换码 前言 Unity 资源商店为大家带来了一份特别的惊喜——限时免费的农场动物资源&#xff01;这是一个充满趣味和实用性的资源包。 资源包内容 在这个资源包中&#xff0c;你可以找到丰富多样的低地养殖动物…

VScode代码片段自动转图标

注&#xff1a;在VScode编辑器中&#xff0c;编辑html、vue等文件时&#xff0c;特定代码片段&#xff08;token/xxx’等&#xff09;自动转图标显示&#xff0c;按住“ctrl鼠标左键”还可跳转“https://icones.js.org/collections”&#xff0c;个人感觉干扰代码编写&#xff…