算法之时间复杂度---数据结构

目录

前言:

1.时间复杂度

1.1时间复杂度的理解

1.2规模与基本操作执行次数

1.3大O渐进表示法

1.4计算基本操作的次数

2.常见的时间复杂度及其优劣比较


❤博主CSDN:啊苏要学习

    ▶专栏分类:数据结构◀

  学习数据结构是一件有趣的事情,希望读者能在我的博文切实感受到,数据之间存在的关系,在对数据元素进行操作的时候,能心中有数,脑中有画! 


前言:

  在计算机中,如何判断一个算法的好与坏,我们使用时间复杂度来评判。时间复杂度不是一个精确的值,而是大致描绘算法的时间效率。大家目前写的代码对于计算机来说都是可以很快就跑完的,因为都是比较简单的常数阶O(1)或线性阶O(n),但如果写了一个很差劲的算法,加上规模很大(数据多),计算机看了都要落泪。

1.时间复杂度

1.1时间复杂度的理解

  时间复杂度的概念,为什么需要有时间复杂度的概念?因为我们人在算术的时候,是需要耗费时间的;计算机在执行我们预设的指令也需要耗费时间,耗费时间长短就显的比较重要,在保证不错的前提下,我们期望计算机能很快跑出结果,而不用等上一段时间

  了解完时间复杂度的必要性,那我们如何算一个算法的时间复杂度呢?把一个程序放在计算机上跑起来,计算其用的时间?这个方法显然是不可行的,相信读者也想到了不同计算机,硬件的性能和软件的加持是不一样的;即使是同一台计算机,你也不能完全保证在执行过程中,每一次计算机的状态和前一次是完全一样的;

1.2规模与基本操作执行次数

   假设计算机在处理每一条语句的时间是一个计算机单位时间,我们通过计算算法里基本操作的执行次数,使用大O(有点像极限)表示法来表示

void Func(int N)
{
    int count = 0;
    for(int i = 0; i < N; i++)//执行n次
    {
        for(int j = 0; j < N; j++)
        {
            count++;//对于外层的每一次,里层都要执行n次
            //n*n
        }
    }
    for(int k = 0; k < 5N; k++)
    {
        count++;//5n
    }
    int a = 10;
    while(a--)
    {
        count++;//10
    } 
    printf("%d", count);
}

  这里面count这个基本操作被执行了几次呢?

  1. 第一部分是两层循环:执行了n^2次;
  2. 第二部分是一层for循环:执行了5n次
  3. 第三部分是while循环:执行了10次

  总共执行了N = n^2 + 5n + 10次,随着N(问题规模)的增大,执行的次数也会随之增大。我们来比较一下下面所列出的问题规模对应的需执行基本操作的次数:

  • N = 10 、n = 160
  • N = 100、n = 10510
  • N = 1000、n = 1005010

  随着问题规模的增大,n也在变大。

我们计算N = n^2的执行次数

N = 10、n = 100  

N = 100、n = 10000

N = 1000、n = 1000000

对比5n+10这部分,n^2对执行次数的贡献是最大的,我们只考虑n^2,时间复杂度就是O(N^2),这就是大O渐进表示法。随着规模的增大,对算法时间效率高低影响最大的那一部分,就是该算法的时间复杂度。     

 大O渐近表示法:我们只关注执行次数表达式中对结果影响最大的那一项,也就是最高阶。

1.3大O渐进表示法

  我们再通过分析代码来理解大O法表示时间复杂度以及总结大O表示的方法

void Fun(int N)
{
    int count = 0;
    for(int k = 0; k < 2N; k++)
    {
        count++;//2n次
    }
    int n = 10;
    while(n--)
    {
        count++;//10次
    }
}

  这段代码的执行次数是2n+10,时间复杂度是多少呢?大家可能说是O(2N),但真的是这样吗?其实应该是O(N)不妨这样想,对于次数起最大影响的是由n引起的,如果n为0,2n就为0,就只有固定的10次。而n从0-10000,次数也就越来越多的,前面的2改变次数的多和少还是取决于n的大小

  大O渐近表示法:对于最高次数的项,与之相乘或相除的常数都是可以省略的,省略后就是大O表示法

void Fun(int N, int M)
{
    int count = 0;
    for(i = 0; i < N; i++)
    {
        count++//n次
    }
    for(j = 0; j < M; j++)
    {
        count++;//m次
    }
}

  这里的时间复杂度是O(N+M),想表达的点是,我们习惯用N来表示执行的次数,不要因为习惯而阻碍了我们做出正确的判断这里N和M都是未知数,对基本操作的次数都有影响,所以自然就都得写在大O里面。大O表示法可以有两个未知数,甚至多个,视情况而定

  如果条件里有说N远大于M,时间复杂度就变成了O(N);如果说N和M差不读,时间复杂度为O(N)或O(M),因为N+M相当于2N或2M,最高项前面的常数影响不大

void Fun(int N)
{
    int count = 0;
    for(int i = 0; i < 100; i++)
    {
        count++;//100次
    }
}

  这里执行了100次,时间复杂度不是O(N),因为N我们没用到。那是不是O(100)呢?也不是,我们用1来表示大O表示法中的所有加法常数所以这段代码的时间复杂度是O(1),表示执行常数次,O(1)不是说算法执行1次的意思,就好像执行100次写成O(1),不是说只执行一次,这里强调的是,不管规模怎么变,变的多大,这个算法执行完常数次就结束,是最优的算法

大O渐近表示法:用常数1代替所有加法常数

大O表示法
最高阶对结果影响最大,关注最高阶。
最高阶的常数省略掉
所有加法常数都用数字1替代

  例子:N = 2n^2 + n + 10。直接看出来时间复杂度是N^2,我想读者们应该都get到了。给出我们基本操作的次数,我们就可以很轻松的用大O表示法表示出时间复杂度,这个并不难,重要的一点是算出或者看出算法的执行次数

1.4计算基本操作的次数

三种情况:

char* serch_ch(const char* str, char ch)
{
    //从字符数组的头开始往后找
    //在一个字符数组里查找一个想要查找的字符
    while(*str != '\0')
    {
        if(*str == ch)
            return str;
        str++;
    }
    return NULL;
}

  一个字符数组,长度为N,在字符数组里查找一个字符ch,到底需要查找多少次(基本执行次数)?

  • 最好的情况:第一个字符就是我们要查找的,只进行一次,时间复杂度为O(1)
  • 最差的情况:遍历完整个数组,可能在最后有这个字符,也可能没有,总之对长度为n的数组都找了一遍(基本操作次数:查找了n次),时间复杂度为O(N)。
  • 平均的情况:要查找的数是随机的,数组里放的字符也是随机的,从概论上来讲,我们会在接近中间的位置找到,执行n/2次,时间复杂度还是O(N)。

  当一个算法存在这三种情况的时候,我们看最坏的情况,做最坏的打算,为我们打底。如果读者看过修仙类的小说的话,我们一般会看到男主很厉害但又想着可能出现的最坏的情况,换句话说就是想的周全。

冒泡排序的基本操作次数:

  冒泡排序就是将一组数字给排成升序。说到冒泡,大家有没有想到生活中煮水器在煮水的时候,那个水泡从底部升起来,从小泡变成大泡。抽象理解小泡是小的数字,升起来的大泡就是大的数字。因为是计算机的知识,我们引进来让大家提个兴趣,不是来讲物理滴(doge)。

  一组数字 8 7 6 5 4 3 2 1。我们想排成1 2 3 4 5 6 7 8的升序序列。我们有思路、分析分析思路就可以了,可以不用敲代码就可以算出这个思路的时间复杂度

  因为这是一个长度为N的数字序列,将第一个数冒泡到最后,需要冒泡N次,这是第一趟冒泡第一趟冒泡的结果就是,把一个数字放到了它该放在的位置上,接下里就只有N-1个数字需要排序了了第二趟冒泡N-1次、第三趟冒泡N-2次、.......、第N趟冒泡1次总共冒泡的次数N + (N - 1) + (N - 2) +.......+ 2 + 1是一个等差数列求和,首项加尾项乘项数除2--- (N+1)*N/2就是冒泡排序的基本操作次数。 时间复杂度O(N^2)。 

二分查找的基本操作次数:

  二分查找是在一个有序的数组里查找想要的数字。现在有一个数组的元素排列是 1 2 3 4 5 6 7 8 9。

  如果不是,假设我们找的是9这个数字,这时候,因为这个数组是有序的,我们中间元素比要查找的小,中间下标左边的就不用再找了,left左下标变成指向mid+1的元素(就相当于少了一半的数据),再和right重新生成中间下标

  当left<=right的时候,就有元素可以找,当left大于了right,就找不到了,最后一次查找是在left = right的时候,此时无论找不找的到、都相当于把长度为N的有序数组给查遍了查了几次呢?由于我们每次查,数组的元素少一半,对于数组长度N,我们得出2的x次方等于N。这里可以这样想,假设这个数组有8个元素,我用二分查找,每次查找都会去掉一半的数据,请问查几次可以查完,2的3次方等于8,我们知道查3次就够了

  所以X是基本操作此时 X = log以2为底N的对数。时间复杂度是O(logN)。

  logN的意思是以2为底,N的对数,由于底数电脑上不太支持写出来,所以我们约定这样写。当然有些数还这样写lgN,实际上这个不合理的程度要大一点,我们尽量写成logN。

练习:递归求阶乘的时间复杂度:

long long Fac(size_t N)
{
    return N < 2 ? N : Fac(N-1) * N;
}

  这里使用递归实现求N的阶乘,这里的时间复杂度是多少呢?请看下面的画图:

   N为几,Fac()函数就被调用几次,每一次函数都进行三次基本操作(判断、得到值、返回)也就是说,Fac函数里的算术复杂度是O(1),而调用这个函数的时间复杂度是O(N)也就是执行了3N次基本操作,所以时间复杂度是O(N)

用for循环来理解也很容易,读者可以类比一下:

int main()
{
    int count = 0;
    int N = 0;
    scanf("%d", &N);
    for(i = 0; i < N; i++)//循环N次
    {
        for(j = 0; j < 3; j++)//i的每一个值都进行三次
        {
            count++;//3N次
        }
    }
    return 0;
}

  这里的循环N次就相当于递归调用是用了N次,每次里面都有三次基本操作,就是3N

  扩展话题:斐波那契数列的时间复杂度的计算

2.常见的时间复杂度及其优劣比较

  常见的时间复杂度有:O(N^2)、O(N)、O(logN)、O(1)这几种

  O(1)是最牛的时间复杂度,没有再厉害的了。O(logN)也是一个非常好的时间复杂度,它和O(N)是有个量级在的,我们来计算一下:

  比如遍历数组查找一个元素,恰巧要找的数组元素在末尾,当N = 1000的时候,O(N)的算法需要执行1000次基本操作、O(logN)的算法只需要10次就够了,因为2的10次方是1024

  当N = 1000000(百万),O(N)需要百万次,O(logN)只需要20次,因为1024*1024我们简单看成1000*1000就够了

  当N = 1000000000(十亿),O(logN)仅需要30次就足亦!

  讲到这里,我们就把时间复杂度的知识内容讲完啦。空间复杂度和算法的一些特性博主可能会另外出一篇,内容不多,而且空间复杂度和时间复杂度一样用大O表示法表示,也一样是估算,空间复杂度算的是程序额外占用空间的数量,比如创建一个int a;变量,占的数量就是1。


结语:希望读者读完能有所收获!对数据结构有进一步的认识!✔

  读者对本文不理解的地方,或是发现文章内容上有误等,请在下方评论留言告诉博主哟~,也可以对博主提出一些文章改进的建议,感激不尽!最后的最后!

  ❤求点赞,求关注,你的点赞是我更新的动力,一起进步吧。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

什么是Web1.0时代、Web2.0时代、Web3.0时代?

什么是Web1.0时代、Web2.0时代、Web3.0时代&#xff1f; 互联网的起源。1969年美国的阿帕网的出现标志着互联网的诞生&#xff0c;而1973年第一台个人电脑The Xerox Alto的出现就预示了互联网将蓬勃生长&#xff0c;随之而来的就是我们迈入了信息时代。短短几十年的发展&#x…

浏览器缓存策略:强缓存和协商缓存

浏览器缓存&#xff1a;其实就是在本地使用的计算机中开辟一个内存区&#xff0c;同时也开辟一个硬盘区&#xff0c;作为数据传输的缓冲区&#xff0c;然后利用这个缓冲区来暂时保护用户以前访问的信息通常浏览器的缓存策略分为两种&#xff1a;强缓存和协商缓存&#xff0c;强…

零基础学java——【基础语法】基本输入、输出语句,变量,运算符

目录 变量 数据类型 基本数据类型一览表 声明和初始化 基本的输出、输出语句 输出语句 补充“”的使用 输入语句Scanner 使用步骤 代码演示 运算符 有些内容可能会与c语言作比较 内容借鉴了韩顺平老师的java课堂笔记&#xff08;b站课&#xff09; 变量 数据类型 基本…

CKA证书题库-总结

CKA真题&#xff08;考题总结&#xff09; 文章目录 CKA真题&#xff08;考题总结&#xff09;证书个人考试总结申诉结果 CKA题目参考博主重点介绍 CKA模拟题库 注意事项考试概要考试注意事项&#xff1a; CKA题目答案设置自动补全方法一方法二 第⼀题&#xff1a;权限控制RBAC…

【4. ROS的主要通讯方式:Topic话题与Message消息】

【4. ROS的主要通讯方式&#xff1a;Topic话题与Message消息】 1. 前言1.1 王者解释结点通讯&#xff1a;1.2 通讯小结 2. 灵活的Topic话题图解2.1 话题注意细节2.2 外延补充 3. Message消息图解3.1 消息类型3.2 查看标准消息类型std_msgs 4. 使用C实现Publisher发布者4.1 发布…

Sametime 12.0.1 FP1发布以及Notes中的SwiftFile使用

大家好&#xff0c;才是真的好。 上周&#xff0c;HCL推出了Sametime 12.0.1FP1FP1更新包程序&#xff0c;包含不少新功能以及很多修复程序。虽然Sametime组件现在不需要运行在Domino服务器上&#xff0c;但毕竟Sametime通常会使用Domino目录或Domino中的LDAP目录服务&#xf…

Maven 如何下载依赖包的源码包

使用Maven下载依赖包的时候&#xff0c; 默认是不会下载源码包的&#xff0c;但是有时候&#xff0c; 需要Debug代码&#xff0c;或是看看依赖项的源码的写法&#xff0c; 就需要下载源码包了。 这里以 Apache 的 commons-text 为例&#xff0c; 在Maven中添加如下依赖配置&am…

HCIA-RS实验-ENSP设备的基础配置

本文主要简单地介绍ENSP设备的基础配置&#xff0c;帮助读者快速上手使用ENSP。可以掌握一些基础的配置方案&#xff0c;更改名称&#xff0c;系统时间&#xff0c;系统地区、密码登录等信息 以下是该文章的拓扑图&#xff1b;现将这2台设备启动&#xff1b;后续双击即可进入命…

Openswan安装和简单配置

Openswan安装和简单配置 安装环境&#xff1a; 操作系统&#xff1a;Ubuntu20.0.4TLS 用户权限&#xff1a;root下载Openswan: wget https://github.com/xelerance/Openswan/archive/refs/tags/v3.0.0.zip安装Openswan: 解压Openswan&#xff1a;&#xff08;PS&#xff1a…

Arduino学习笔记4

一.声控灯实验 1.源代码 int led2;//定义板子上数字2口控制小灯 int flag0;//定义一个变量记录小灯是亮起还是熄灭 int shengyin3;//定义声音传感器的控制口void setup() {pinMode(led,OUTPUT);//定义小灯为输出模式pinMode(shengyin,INPUT);//定义声音控制口为输入模式 } vo…

基于天牛须(BAS)与NSGA-Ⅱ混合算法的交直流混合微电网多场景多目标优化调度(Matlab代码实现)

&#x1f4a5; &#x1f4a5; &#x1f49e; &#x1f49e; 欢迎来到本博客 ❤️ ❤️ &#x1f4a5; &#x1f4a5; &#x1f3c6; 博主优势&#xff1a; &#x1f31e; &#x1f31e; &#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 …

SentiBank Dector上手指南

​ 官网链接&#xff1a;https://www.ee.columbia.edu/ln/dvmm/vso/download/sentibank.html SentiBank Detector可以抽取图片中的形容词-名词对&#xff0c;之前一直看到&#xff0c;这次复现模型才第一次用到&#xff0c;上手的时候有点手足无措&#xff0c;因为官网在如何使…

Python入门教程+项目实战-11.4节: 元组与列表的区别

目录 11.4.1 元组与列表的区别 11.4.2 可变数据类型 11.4.3 元组与列表的区别 11.4.4 知识要点 11.4.5 系统学习python 11.4.1 不可变数据类型 不可变数据类型是指不可以对该数据类型进行修改&#xff0c;即只读的数据类型。迄今为止学过的不可变数据类型有字符串&#x…

我做了个GPT3键盘,用了两个月发现它有点傻

自 ChatGPT 出世&#xff0c;各类文本类AI产品层出不穷。甚至接连几日&#xff0c;Producthunt 上新品过半都是AI相关。 这其中部分原因是 OpenAI 公司开放的 GPT3 1API 接口十分易用。只要一个简单的文本请求&#xff0c;就能将现有产品加入AI功能。例如&#xff0c;Notion、…

提取文本的摘要snownlp模块

【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 提取文本的摘要 snownlp模块 [太阳]选择题 关于以下python代码说法错误的一项是&#xff1f; from snownlp import SnowNLP myText """ChatGPT的出现标志着人类科技发…

【flask】三种路由和各自的比较配置文件所有的字母必须大写if __name__的作用核心对象循环引用的几种解决方式--难Flask的经典错误上下文管理器

三种路由 方法1&#xff1a;装饰器 python C#, java 都可以用这种方式 from flask import Flask app Flask(__name__)app.route(/hello) def hello():return Hello world!app.run(debugTrue)方法2: 注册路由 php python from flask import Flask app Flask(__name__)//app…

【JavaWeb】jQuery(上)

本章内容 1.jQuery Hello world 2.jQuery 选择器 3.jQuery 过滤器 4.jQuery 元素筛选 1、jQuery 介绍 什么是 jQuery ? jQuery&#xff0c;顾名思义&#xff0c;也就是 JavaScript 和查询&#xff08;Query&#xff09;&#xff0c;它就是辅助 JavaScript 开发的 js 类…

Linux 文件内容相关命令使用汇总

Linux操作系统有很多强大的文件内容相关命令&#xff0c;这些命令可以让您查看、分析和编辑文件。其中&#xff0c;最基本和常用的命令包括cat、more、less和head/tail等。除了这些基本命令之外&#xff0c;grep和find命令也是文件搜索和过滤方面的有力工具。 前言 我们这篇主…

根据 vue-grid-layout 动态设置Echarts尺寸大小

文章目录 前言一、vue-grid-layout 是什么&#xff1f;二、正文1.引入vue-grid-layout2.myEcharts组件3. Utils中的debounce防抖函数 总结 前言 此文背景是根据 vue-grid-layout 动态拖拽组件大小里面包含 Echarts 组件情景&#xff0c;也可以单独把监听动态设置Echarts 尺寸抽…

【神经网络】tensorflow实验7--回归问题

1. 实验目的 ①掌握一元线性回归模型的实现方法 ②掌握多元线性回归模型的实现方法 ③掌握三维数据可视化方法 2. 实验内容 ①使用TensorFlow建立一元线性回归模型&#xff0c;使用商品房销售数据训练模型&#xff0c;并使用训练好的模型预测房价 ②使用TensorFlow建立多元线…