复杂度(上卷)

news2024/11/16 19:02:29

前言

在正式进入今天的主题之前,我们不妨先来回顾一下初步学习数据结构后必须知道的概念。🎶

数据结构

数据结构是计算机存储、组织数据的方式,指相互间存在一种或多种特定关系的数据元素的集合

(没有一种单一的数据结构能够满足所有用途,因此我们需要学习各种不同的数据结构。如:线性表、树、图、哈希等。)

算法

算法(Algorithm)指的是定义好的计算过程,它取一个或一组的值为输入,并产生出一个或一组值作为输出

简单来说算法就是一系列的计算步骤,用于将输入数据转化成输出结果。


正文

我们知道算法有好坏之分,但是要怎么去衡量一个算法的好坏呢?

比如我们在一些刷题平台做算法题时会遇到“超出时间限制”的“滑铁卢”,所以我们可以知道,时间就是其中一个标准。除此之外还有空间。用最少的时间办最大的事是我们现实中办事能力好坏的标准,对于算法来说也是类似的:用最少的时间、最少的空间,解决问题,这就是好的算法。

其实我们有一个专门衡量算法好坏的概念:复杂度。复杂度分为时间复杂度和空间复杂度,刚好从两个维度去衡量一个算法。

说得更精确一些:时间复杂度主要衡量一个算法的运行快慢,而空间复杂度主要衡量一个算法运行所需要的额外空间

其实在计算机发展的早期,计算机的存储容量很小所以对空间复杂度很是在乎。而如今计算机的存储容量不可同日而语,所以我们已不再特别关注空间复杂度而是以时间复杂度为主

小小补充:摩尔定律

摩尔定律最初是由英特尔公司的创始人之一戈登·摩尔在1965年提出的观察结果和预测。他发现集成电路上可容纳的晶体管数量每隔一段时间就会翻倍,同时价格也会减半。这一发现揭示了信息技术进步的速度,并成为了计算机行业的基础法则之一。

摩尔定律的实际效果是,计算机芯片的性能每隔一段时间就会显著提高,而成本也会随之下降。这一定律对整个信息技术产业的发展产生了深远的影响,推动了半导体芯片的集成化趋势,进而给人们的生活带来了巨大的变化。然而,随着晶体管电路逐渐接近性能极限,摩尔定律的适用性也面临挑战。

归纳起来,“摩尔定律”主要有以下3种“版本”:

  1. 集成电路芯片上所集成的电路的数目,每隔18个月就翻一番;
  2. 微处理器的性能每隔18个月提高一倍,而价格下降一半;
  3. 用一美元所能买到的计算机性能,每隔18个月翻两番。

这里不再多阐述,回到复杂度。

时间复杂度

在计算机科学中,算法的时间复杂度是一个函数式T(N),它定量描述了该算法的运行时间。

这里的函数不是我们C语言中指的函数,而是数学概念。

先来说说,时间复杂度是衡量程序的时间效率,那么为什么不去计算程序的运行时间呢?

我们是可以计算程序的运行时间的。我们可以分别计算程序开始和结束的时间进行相减得到运行时间。

如:

#include<stdio.h>
#include<time.h>

int main()
{
	//计算程序运行时间
	int begin = clock();//用来保存程序的运行时间
	int count = 0;
	for (int i = 0; i < 1000000000; i++)
	{
		count++;
	}
	int end = clock();
	printf("time:%d\n", end - begin);
	return 0;
}

执行后我的电脑上VS(Debug模式)得到的结果是time:507,Release模式则是0。单位是毫秒。

而且多次运行得出来的结果还不一定相同,无法给出精确的运行时间。

  1. 程序运行时间和编译环境以及运行机器的配置都有关系,同一个算法程序,用一个老编译器和新编译器进行编译,在同样机器下运行时间不同。

  2. 同一个算法程序,用一个老低配置机器和新高配置机器,运行时间也不同。

  3. 并且运行的时间只能程序写好后测试,不能在编写程序之前通过理论思想计算评估。(而我们希望算法的复杂度是我们在编写程序之前就能知道的。)

这几点解释了为什么通过计算程序的运行时间来评估算法的时间效率不好。

时间复杂度和空间复杂度是我们在提出一个算法,编写程序之前就能知道的,是一个粗估而非精确的结果。

时间复杂度该怎么计算呢?

程序的时间效率由两个维度决定:每条语句运行的时间和运行的次数。

程序时间效率:每条语句运行时间×运行次数

每条语句运行的时间也如前面所说,和编译环境、运行机器的配置等有关,不好确定。但是运行的次数我们是可以知道的。比如上面的int count = 0;这句代码的运行次数为1,而count++;的运行次数为1000000000。

我们可以将每条语句运行时间去掉,只看运行次数。

为什么可以这样呢?

在这里插入图片描述

因为程序的运行时间和运行次数是正相关的,所以我们可以不看每条语句运行时间,通过运行次数来看时间复杂度。

计算时间复杂度的案例

我们现在有一道算法题,来看看怎么计算它的时间复杂度。

// 请计算⼀下Func1中++count语句总共执⾏了多少次? 
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--) 
 	{ 
 		++count; 
 	} 
}

我们看的是运行次数。在第一个嵌套循环中:外循环每执行1次,内循环要执行N次,而外循环执行了N次。所以整个部分执行了N×N次;在下一个循环中执行了2×N次;最后一个循环执行了10次。所以该算法的时间复杂度函数式为T(N)=N²+2N+10。

定义变量这样的语句相较于其他的循环语句运行次数太小,故忽略不计。

我们可以看出N的大小决定了T(N)的大小:

NT(N)
10130100
1001021010000
100010020101000000

可以看出对T(N)影响大的是N²,而2N+10对结果的影响小,可以忽略不计,只看影响最大的项。T(N)=N²,是一个粗估。

大O的渐进表示法

上面我们讲了T(N),但其实复杂度的表示我们使用的是大O的渐进表示法。

比如上面的那个算法的时间复杂度就表示为O(N²)。

大O既可以表示时间复杂度,也可以表示空间复杂度。

接下来我们来看本文中最重要的内容:

推导大O阶规则

  1. 时间复杂度函数式T(N)中,只保留最⾼阶项,去掉那些低阶项,因为当N不断变⼤时, 低阶项对结果影响越来越⼩,当N⽆穷⼤时,就可以忽略不计了。
  2. 如果最⾼阶项存在且不是1,则去除这个项⽬的常数系数,因为当N不断变⼤,这个系数 对结果影响越来越⼩,当N⽆穷⼤时,就可以忽略不计了。
  3. T(N)中如果没有N相关的项⽬,只有常数项,⽤常数1取代所有加法常数。

可以看到,刚才那个算法的例子就是符合以上规则的一个例子。

我们现在再来看几个例子。

示例1
// 计算Func2的时间复杂度? 
void Func2(int N) 
{ 
 int count = 0; 
 for (int k = 0; k < 2 * N ; ++ k) 
 { 
 	++count; 
 } 
 int M = 10; 
 while (M--) 
 { 
 	++count; 
 } 
 printf("%d\n", count); 
}

第一个循环执行次数为2N,第二个循环次数为10。所以T(N)=2N+10

根据规则1,10作为低阶项,被去掉;

根据规则2,最⾼阶项存在且不是1,去除这个项⽬的系数2。

所以最后我们得到的时间复杂度为O(N)。

对于计算机来说,1万和2万,1亿和2亿的区别并不大,所以可以去除系数。2倍的无穷大与无穷大结果一样。

示例2
// 计算Func3的时间复杂度? 
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; 
    } 
 	printf("%d\n", count); 
}

第一个循环的执行次数为M,第二个循环执行次数为N,所以T(N)=M+N,M和N都是变量,都会影响T(N)的大小。那么时间复杂度是多少呢?答案是O(M+N)。

如果T(N)=2M+N,也是O(M+N),没有区别。因为M和N取极限时,2倍的极限和极限没有区别。

大小关系复杂度
M>>NO(M)
M<<NO(N)
M==NO(M+N)

注意:

  • 这里的高阶项与低阶项如何区别?高和低是相对而言的,看谁对结果影响最大则为高阶。

  • 一亿这样的数对于我们来说虽然感觉很大,对于高速处理数据的计算机来说却不过是一秒不到的事,所以我们说数字大与小是要对于计算机来说而不是我们的主观感受。当我们说“非常大”,指的是无限。

本文到此结束,但对于复杂度的探讨并没有结束,敬请期待下卷。( ̄︶ ̄)↗

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

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

相关文章

在centos7中安装MySQL5.7,是否必须卸载centos7自带的mariadb?

在CentOS 7 中安装 MySQL 5.7 时&#xff0c;不一定必须卸载系统自带的 MariaDB&#xff0c;但为了避免冲突和确保 MySQL 的正常运行&#xff0c;通常建议先卸载 MariaDB。以下是具体的步骤&#xff1a; 卸载 MariaDB&#xff08;如果已经安装&#xff09;&#xff1a; sudo sy…

强化学习驱动的狼人游戏语言智能体战略玩法

Language Agents with Reinforcement Learning for Strategic Play in the Werewolf Game 论文地址: https://arxiv.org/abs/2310.18940https://arxiv.org/abs/2310.18940 1.概述 在AI领域,构建具备逻辑推理、战略决策以及人类沟通能力的智能体一直被视为长远追求。大规模语…

小阿轩yx-NoSQL 之 Redis 配置与优化

小阿轩yx-NoSQL 之 Redis 配置与优化 Redis 数据库介绍 是一个非关系型数据库 关系数据库与非关系型数据库 按照数据库结构划分的 关系型数据库 是一个结构化的数据库&#xff0c;创建在关系模型基础上&#xff0c;一般面向于记录借助集合代数等数学概念和方法处理数据库…

设计模式探索:责任链模式

1. 什么是责任链模式 责任链模式 (Chain of Responsibility Pattern) 是一种行为型设计模式。定义如下&#xff1a; 避免将一个请求的发送者与接收者耦合在一起&#xff0c;让多个对象都有机会处理请求。将接收请求的对象连接成一条链&#xff0c;并且沿着这条链传递请求&…

数列分块<2>

本期是数列分块入门<2>。该系列的所有题目来自hzwer在LOJ上提供的数列分块入门系列。 Blog:http://hzwer.com/8053.html sto hzwer orz %%% [转载] 好像上面的链接↑打不开&#xff0c;放一个转载:https://www.cnblogs.…

CUDA原子操作

代码 #include <cuda_runtime.h> #include <stdio.h>__global__ void atomicAddAndGet(int *result, int *valueToAdd) {// 原子加法int addedValue atomicAdd(result, *valueToAdd);// 通过原子操作后读取值&#xff0c;确保是加法后的值addedValue *valueToAd…

LabVIEW开发CAN总线多传感器液位检测系统

设计并实现了一个基于CAN总线和LabVIEW的多传感器液位检测系统。该系统利用STM32F107单片机进行模拟信号与数字信号的转换&#xff0c;通过TJA1050实现CAN总线通信&#xff0c;并使用USB-CAN分析仪连接PC。LabVIEW用于数据采集、人机交互界面的设计、数据分析和仪器标定。系统能…

前端必修技能:高手进阶核心知识分享 - 三万字帮你搞定CSS动画(形变动画、过渡动画、关键帧动画)

在CSS的世界里,存在着多种能体现动画效果的属性:CSS transform、CSS Transition 和 CSS Animation。让开始接触CSS的同学感到困惑。要搞清楚CSS的动画,我们就必须先把这几种属性做一下区别。 CSS transform 属性、CSS Transition 属性、 CSS Animation 属性的区别 CSS tra…

FL Studio21.5.3.21中文版破解安装包!音乐制作新神器,让创意无限飞扬!

&#x1f3b6; 音乐制作&#xff0c;轻松入门&#xff01;FL Studio21中文版本体验分享 嘿&#xff01;各位音乐小能手和创作小白们&#xff0c;今天我要给大家安利一个超酷炫的音乐制作软件——FL Studio21中文版&#xff01;&#x1f389; FL Studio21汉化版下载网盘链接: …

Python函数 之 模块和包---练习

题目 1 1.定义一个模块 toolls.py , 定义函数实现对两个数据进行加法操作的函数 add_2_num &#xff0c;并返回相加之和的结 果&#xff1b; 再定义一个实现对三个数据进行加法操作的函数 add_3_num &#xff0c;并返回相加之和的结果&#xff1b; 2.最后新定义一个代码文件 …

AutoMQ vs Kafka: 来自小红书的独立深度评测与对比

测试背景 当前小红书消息引擎团队与 AutoMQ 团队正在深度合作&#xff0c;共同推动社区建设&#xff0c;探索云原生消息引擎的前沿技术。本文基于 OpenMessaging 框架&#xff0c;对 AutoMQ 进行了全面测评。欢迎大家参与社区并分享测评体验。 01 测试结论 本文主要测评云…

JavaDS —— 单链表 与 LinkedList

顺序表和链表区别 ArrayList &#xff1a; 底层使用连续的空间&#xff0c;可以随机访问某下标的元素&#xff0c;时间复杂度为O&#xff08;1&#xff09; 但是在插入和删除操作的时候&#xff0c;需要将该位置的后序元素整体往前或者向后移动&#xff0c;时间复杂度为O&…

二分查找算法——部分OJ题详解

目录 关于二分查找算法 部分OJ题详解 704.二分查找 一&#xff0c;分析题目 二&#xff0c;细节处理 三&#xff0c;题目代码 四&#xff0c;*总结朴素模板 *34.在排序数组中查找元素的第一个和最后一个位置 一&#xff0c;查找左端点 二&#xff0c;处理左端点细…

ts实现将相同类型的数据通过排序放在一起

看下效果&#xff0c;可以将相同表名称的字段放在一起 排序适用于中英文、数字 // 排序 function sortByType(items: any) {// 先按照类型进行排序items.sort((a: any, b: any) > {if (a.label < b.label) return -1;if (a.label > b.label) return 1;return 0;});r…

【记录】LaTex|LaTex调整算法、公式、表格内的字体大小(10种内置字号)

文章目录 【记录】LaTex&#xff5c;LaTex调整算法、公式、表格内的字体大小&#xff08;10种内置字号&#xff09;省流版1 字体大小2 测试代码 详细版1 \tiny2 \scriptsize3 \footnotesize4 \small5 \normalsize6 \large7 \Large8 \LARGE9 \huge10 \Huge 【记录】LaTex&#x…

实验02 黑盒测试(组合测试、场景法)

1. 组合测试用例设计技术 指出等价类划分法和边界值分析法通常假设输入变量相互独立&#xff0c;但实际情况中变量间可能存在关联。全面测试&#xff1a;覆盖所有输入变量的所有可能组合&#xff0c;测试用例数量随输入变量的增加而指数增长。 全面测试需要对所有输入的各个取…

Geoserver源码解读六 插件

系列文章目录 Geoserver源码解读一 环境搭建 Geoserver源码解读二 主入口 Geoserver源码解读三 GeoServerBasePage Geoserver源码解读四 REST服务 Geoserver源码解读五 Catalog Geoserver源码解读六 插件&#xff08;怎么在开发模式下使用&#xff09; 目录 系列文章目…

ubuntu计划任务反弹

目录 实验环境 实验步骤 目标主机构造任务计划 构造语句 语句解释 kali开启监听 监听成功 问题 原因 实验环境 攻击者 操作系统&#xff1a;kali IP&#xff1a;192.168.244.141 目标主机 操作系统&#xff1a;ubuntu IP&#xff1a;192.168.244.151 实验步骤 目…

CSS 中的 ::before 和 ::after 伪元素

目录 一、CSS 伪元素 二、::before ::after 介绍 1、::before 2、::after 3、content 常用属性值 三、::before ::after 应用场景 1、设置统一字符 2、通过背景添加图片 3、添加装饰线 4、右侧展开箭头 5、对话框小三角 6、插入icon图标 一、CSS 伪元素 CSS伪元…

数据库使用SSL加密连接

简介 数据库开通SSL加密连接是确保数据传输过程中安全性的关键措施&#xff0c;它通过加密数据、验证服务器身份、保护敏感信息、维护数据完整性和可靠性&#xff0c;同时满足行业标准和法规要求&#xff0c;进而提升用户体验和信任度&#xff0c;为企业的数据安全和业务连续性…