Java算法之时间复杂度和空间复杂度的概念和计算

1. 算法效率

如何去衡量一个算法的好坏?

通常我们从时间效率和空间效率两个方面去分析算法的好坏。时间效率即时间复杂度,空间效率被称为空间复杂度。时间复杂度主要是衡量一个算法的运行速度,而空间复杂度主要衡量一个算法所需要的额外空间。

常见的复杂度大小比较:O(2^N) > O(N^2) > O(N*logN) > O(N) > O(logN) > O(1)

在计算机发展的早期,计算机的存储容量很小。所以对空间复杂度很是在乎。但是经过计算机行业的迅速发展,计算机的存储容量已经达到了很高的程度。所以我们如今已经不需要再特别关注一个算法的空间复杂度。因为现在的内存不像以前那么贵,所以经常听到过牺牲空间来换取时间的说法

事后统计法

**这种方法可行,但不是一个好的方法。**该方法有两个缺陷:一是要想对设计的算法的运行性能进行评测,必须先依据算法编制相应的程序并实际运行;二是所得时间的统计量依赖于计算机的硬件、软件等环境因素,有时容易掩盖算法本身的优势。

事前分析估算的方法

因事后统计方法更多的依赖于计算机的硬件、软件等环境因素,有时容易掩盖算法本身的优劣。因此人们常常采用事前分析估算的方法
在编写程序前,依据统计方法对算法进行估算。一个程序在计算机上运行时所消耗的时间取决于下列因素:
(1) 算法采用的策略、方法;(2). 编译产生的代码质量;(3) 问题的输入规模;(4) 机器执行指令的速度。

2. 时间复杂度

定义:在计算机科学中,算法的时间复杂度是一个数学函数,它定量描述了该算法的运行时间。一个算法执行所耗费的时间与其中语句的执行次数成正比例,算法中基本的执行次数,即算法的时间复杂度。

通常在计算算法的时间复杂度时,并不一定要计算精确的执行次数,而只需要计算大概执行次数,我们通常使用大 O 渐进法来表示。

1. 用常数 1 来取代运行时间中所有的加法常数;

2. 只保留最高的阶项;

3. 如果最高项存在且不是 1,则去除与这个项目相乘的常数,得到的结果就是大 O 阶.

代码演示如下:

//请计算一下func1基本操作运行了多少次
void func1(int N){int count = 0;for (int i = 0; i < N ; i++) {for (int j = 0; j < N ; j++) {count++;}}//这个for循环运行 N * N 次for (int k = 0; k < 2 * N ; k++) {count++;}//这个for循环运行 N 次int M = 10;while ((M--) > 0) {count++;}//这个操作运行 10 次System.out.println(count);
}

对于上述代码进行时间复杂度分析:

第一个嵌套 for 循环的执行次数为 N^2,第二个 for 循环的执行次数为 2N,第三个 while 循环的执行次数是 10, 则 F(N)=F(N^2) + F(2N)+10,根据大 O 渐进表示法,该算法的时间复杂度为 O(N^2).

算法的时间复杂度存在最好、平均、最坏情况:

最好情况:任意输入规模的最大运行次数(上界)

平均情况:任意输入规模的期望运行次数

最坏情况:任意输入规模的最小运行次数(下界)

注意: O(1) 表示基本语句的执行次数是一个常数,一般来说,只要算法中不存在循环语句,时间复杂度就为 O(1)。

下面选取一些常见的进行讲解

常数阶 O(1)

无论代码执行了多少行,只要是没有循环等复杂结构,那这个代码的时间复杂度就都是 O(1),如:

int i = 1;
int j = 2;
++i;
j++;
int m = i + j;

上述代码在执行的时候,它消耗的时候并不随着某个变量的增长而增长,那么无论这类代码有多长,即使有几万几十万行,都可以用 O(1) 来表示它的时间复杂度。

线性阶 O(n)

这个在最开始的代码示例中就讲解过了,如:

for(i=1; i<=n; ++i)
{j = i;j++;
}

这段代码,for 循环里面的代码会执行 n 遍,因此它消耗的时间是随着 n 的变化而变化的,因此这类代码都可以用 O(n) 来表示它的时间复杂度。

对数阶 O(logN)
int i = 1;
while(i<n)
{i = i * 2;
}

从上面代码可以看到,在 while 循环里面,每次都将 i 乘以 2,乘完之后,i 距离 n 就越来越近了。我们试着求解一下,假设循环 x 次之后,i 就大于 2 了,此时这个循环就退出了,也就是说 2 的 x 次方等于 n,那么 x = log2^n
也就是说当循环 log2^n 次以后,这个代码就结束了。因此这个代码的时间复杂度为:O(logn)

线性对数阶 O(nlogN)

线性对数阶 O(nlogN) 其实非常容易理解,将时间复杂度为 O(logn) 的代码循环 N 遍的话,那么它的时间复杂度就是 n * O(logN),也就是了 O(nlogN)。

就拿上面的代码加一点修改来举例:

for(m=1; m<n; m++)
{i = 1;while(i<n){i = i * 2;}
}
平方阶 O(n2)

平方阶 O(n²) 就更容易理解了,如果把 O(n) 的代码再嵌套循环一遍,它的时间复杂度就是 O(n²) 了。
举例:

for(x=1; i<=n; x++)
{for(i=1; i<=n; i++){j = i;j++;}
}

这段代码其实就是嵌套了 2 层 n 循环,它的时间复杂度就是 O(n*n),即 O(n²)
如果将其中一层循环的 n 改成 m,即:

for(x=1; i<=m; x++)
{for(i=1; i<=n; i++){j = i;j++;}
}

那它的时间复杂度就变成了 O(m*n)

立方阶 O(n³)、K 次方阶 O(n^k)

参考上面的 O(n²) 去理解就好了,O(n³) 相当于三层 n 循环,其它的类似。

指数阶 O(2^N)

递归的时间复杂度计算: 递归的次数 * 每次递归后代码的执行次数 (也是用大 O 渐进法表示)

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

上面的代码是一个计算斐波那契数列的方法,使用的是递归的方法,我们知道递归的方法来计算斐波那契数列是非常低效的,最好还是使用循环的方法,但是递归的时间复杂度是多少呢?
我们知道每一次调用这个方法时,我们的时间复杂度是一个常数,那么这个递归的时间复杂度就是我们一共调用了多少次方法,现在我们来分析一下我们到底调用了多少次这个方法。
其调用结构如图所示

在这里插入图片描述

当我们输入 N 时,方法会进行两调用,然后不断地调用,其调用的结果如图所示,在右下角是有一个空缺区域的,但是我们可以把这一块空缺的区域看作一个常数,假设它是满的,那么我们执行调用的总次数为 F(N)=2⁰+2¹+2²+…+2N-1=2N-1(使用等比数列得出结果),所以该算法的时间按复杂度为 O(2^N)。
由以上的计算我们就可以发现用递归来算斐波那契数的算法的时间复杂度太高了,也就说明了这个算法的低效。

除此之外,其实还有 平均时间复杂度、均摊时间复杂度、最坏时间复杂度、最好时间复杂度 的分析方法,有点复杂,这里就不展开了。

3. 空间复杂度

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

注意:函数运行时所需要的栈空间 (存储参数、局部变量、一些寄存器信息等) 在编译期间已经确定好了,因此空间复杂度主要通过函数在运行时候显式申请的额外空间来确定。

1. 一个算法在计算机上占用的内存包括:程序代码所占用的空间,输入输出数据所占用的空间,辅助变量所占用的空间这三个方面,程序代码所占用的空间取决于算法本身的长短,输入输出数据所占用的空间取决于要解决的问题,是通过参数表调用函数传递而来,只有辅助变量是算法运行过程中临时占用的存储空间,与空间复杂度相关;

2. 通常来说,只要算法不涉及到动态分配的空间,以及递归、栈所需的空间,空间复杂度通常为 O(1);

代码演示如下:

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 == true) {
break;
}

空间复杂度:O(1)

这里需要理解:算法在运行过程中临时占用存储空间(额外)大小的量度,这里这开辟 end,sorted ,i 三个零时变量,因为大 O 渐进表示法,3 为常数所以为 1 。

空间复杂度 O(n)

int[] m = new int[n]
for(i=1; i<=n; ++i)
{j = i;j++;
}

这段代码中,第一行 new 了一个数组出来,这个数据占用的大小为 n,这段代码的 2-6 行,虽然有循环,但没有再分配新的空间,因此,这段代码的空间复杂度主要看第一行即可,即 O(n)

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

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

相关文章

有道词典网页版接口分析与爬虫研究

说明&#xff1a;仅供学习使用&#xff0c;请勿用于非法用途&#xff0c;若有侵权&#xff0c;请联系博主删除 作者&#xff1a;zhu6201976 一、目标站点 有道词典网页版&#xff1a;网易有道 二、目标接口 url&#xff1a;https://dict.youdao.com/jsonapi_s?doctypejson&…

Linux系统搭建FastDFS文件服务结合内网穿透实现公网访问本地文件

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

C++-基础

C语言介绍 C 是一种通用编程语言&#xff0c;具有高性能、灵活性和广泛的应用领域。它是在 1979 年由 Bjarne Stroustrup 开发的&#xff0c;最初被称为“C with Classes”&#xff0c;随后在 1983 年正式更名为 C。C 基于 C 语言&#xff0c;同时引入了面向对象编程&#xff0…

使用冒泡排序模拟实现qsort函数

目录 冒泡排序qsort函数的使用1.使用qsort函数排序整型数据2.使用qsort函数排序结构数据 冒泡排序模拟实现qsort函数今日题目1. 字符串旋转结果2.杨氏矩阵3.猜凶手4.杨辉三角 总结 冒泡排序 冒泡排序的核心思想是:两两相邻的元素进行比较 代码如下: //⽅法1 void bubble_so…

【Linux】线程的控制

目录 POSIX线程库 常用的POSIX线程库接口声明&#xff1a; 注意事项 创建一个进程 pthread_create函数 参数 返回值 使用示例 线程ID和进程地址空间布局 线程ID 进程地址空间布局 示例图 获取一个进程的线程ID 函数原型 返回值 使用示例 注意事项 线程终止 p…

设计模式系列:责任链模式

简介 责任链模式是一种行为型设计模式&#xff0c;它允许你将请求沿着处理者链进行发送。每个处理者都可以对请求进行处理&#xff0c;或者将其传递给链上的下一个处理者。责任链模式主要应用于面向对象编程中&#xff0c;特别是当系统中的对象需要根据其属性来决定如何处理请…

嘴尚绝美味健康:探索美食背后的健康密码

在快节奏的现代生活中&#xff0c;人们对美食的追求从未停止。然而&#xff0c;随着健康意识的提升&#xff0c;越来越多的人开始关注美食与健康的平衡。今天&#xff0c;我们就来一起探讨“嘴尚绝美味健康”这一话题&#xff0c;看看如何在享受美食的同时&#xff0c;保持身体…

JMeter入门教程 —— 事务!

简介&#xff1a; JMeter中事务的基本介绍 1.任务背景 JMeter中的事务是通过事务控制器实现的。&#xff0c;为了衡量服务器对某一个或一系列操作处理的响应时间&#xff0c;需要定义事务。下面我们详细介绍在JMeter中如何使用事务 2.任务目标 掌握基于JMeter性能测试脚本开…

speccpu2017安装与使用

国产化桌面下Speccpu2017安装与使用 1、 安装依赖库 安装speccpu2017前需要安装依赖包&#xff0c;通过终端命令对依赖包进行安装 sudo apt-get install gcc g gfortran &#xff08;以上是已经安装好的&#xff09; 注&#xff1a;若安装不上&#xff0c;需替换/etc/apt下的s…

Docker部署SpringBoot服务(Jar包映射部署)

介绍 项目在docker部署运行以后&#xff0c;每次需更新jar包时&#xff0c;都得重新制作镜像&#xff0c;再重新制作容器。流程及其繁琐&#xff0c;效率极低。 以下步骤是在不更新镜像和容器的前提下&#xff0c;直接更新jar完成项目更新的操作。 不更新镜像 1. 创建你存放…

几款高效在线文档编辑器推荐,编辑文档更轻松

在数字化时代&#xff0c;文档编辑工作变得越来越重要。无论是工作报告、学习笔记还是创意文稿&#xff0c;一个优秀的在线文档编辑器都能让你的工作事半功倍。现在市面上也有很多优秀的在线文档编辑器&#xff0c;比如WPS Office、腾讯文档、 Microsoft Word Online。今天&…

openGauss_5.0.1 企业版安装及问题记录(CentOS系统):主备模式服务器安装

目录 &#x1f4da;第一章 官方地址&#x1f4d7;安装包下载地址&#x1f4d7;文档指南 &#x1f4da;第二章 安装&#x1f4d7;准备工作&#x1f4d7;开始安装&#x1f4d5;创建XML配置文件&#x1f4d5;初始化安装环境&#x1f4d5;执行安装&#x1f4d5;验证 &#x1f4da;第…

【数据结构】第三节:单链表

前言 本篇要求掌握的C语言基础知识&#xff1a;指针、结构体 目录 前言 单链表 概念 对比链表和顺序表 创建链表 实现单链表 准备工作 打印链表 创建节点并初始化 尾插 二级指针的调用 尾插代码 头插 尾删 头删 查找&#xff08;返回节点&#xff09; 在指定位…

C#硬件接口开发------一文了解WMI

&#x1f388;个人主页&#xff1a;靓仔很忙i &#x1f4bb;B 站主页&#xff1a;&#x1f449;B站&#x1f448; &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f917;收录专栏&#xff1a;C# 硬件接口开发 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足…

优优嗨聚集团:如何优雅地解决个人债务问题,一步步走向财务自由

在快节奏的现代生活中&#xff0c;个人债务问题似乎已成为许多人不得不面对的挑战。正确处理个人债务&#xff0c;不仅关系到个人信用和财务状况&#xff0c;更是实现财务自由的重要一步。本文将为您提供一些实用的建议&#xff0c;帮助您优雅地解决个人债务问题&#xff0c;走…

设计模式之备忘录模式(下)

3&#xff09;实现多次撤销 1.结构图 对负责人类MementoCaretaker进行了修改&#xff0c;在其中定义了一个ArrayList类型的集合对象来存储多个备忘录。 2.代码实现 import java.util.*;public class MementoCaretaker {//定义一个集合来存储多个备忘录private ArrayList mem…

学员分享丨十年架构师感悟:敢于“提出问题”

最近呢小誉收到了一位工作十年的学员投稿&#xff0c;这位学员是2011年从誉天学习HCIE课程并顺利拿证&#xff0c;先后在华为等大厂工作。他想把他这十年的工作经验分享给各位学弟学妹们。 这些经验并非来自于具体的技术实现&#xff0c;而是在架构设计和实施过程中所体会到的一…

Github 2024-04-09 Python开源项目日报 Top10

根据Github Trendings的统计,今日(2024-04-09统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Python项目10Vue项目1JavaScript项目1系统设计指南 创建周期:2507 天开发语言:Python协议类型:OtherStar数量:241693 个Fork数量:42010 次…

C++生成动态库,C++和C#以及Java在windows和linux调用

Windows生成dllC库 1、创建动态链接库项目 源文件编写函数 // dllmain.cpp : 定义 DLL 应用程序的入口点。 #include "pch.h"int sum(int a, int b) {return a b; }BOOL APIENTRY DllMain( HMODULE hModule,DWORD ul_reason_for_call,LPVOID lpReserved) {switch…

【LAMMPS学习】八、基础知识(1.8)键的断裂

8. 基础知识 此部分描述了如何使用 LAMMPS 为用户和开发人员执行各种任务。术语表页面还列出了 MD 术语&#xff0c;以及相应 LAMMPS 手册页的链接。 LAMMPS 源代码分发的 examples 目录中包含的示例输入脚本以及示例脚本页面上突出显示的示例输入脚本还展示了如何设置和运行各…