热门标签 | HotTags
当前位置:  开发笔记 > 人工智能 > 正文

数据结构与算法(二)复杂度

吐槽国庆假期第三天,昨天出去胡吃海喝,和朋友出去逛逛寺庙,然后今天早上又来实验室,给猫猫铲臭臭真的臭死我了,一见我就往我身上冲emmmmmmmmmm,今天就把上周就该写的复杂度分析



吐槽

国庆假期第三天,昨天出去胡吃海喝,和朋友出去逛逛寺庙,然后今天早上又来实验室,给猫猫铲臭臭真的臭死我了,一见我就往我身上冲emmmmmmmmmm,今天就把上周就该写的复杂度分析这块写下,因为这块真的还蛮重要的

//我看的是极客时间上一个老师讲的,就是把他讲的梳理一下


本文思维导图

在这里插入图片描述

我觉得复杂度这块主要就是这些,而且分析最多的也是时间复杂度


为什么要有复杂度分析?

这个问题,我之前学的时候重来没想过,写项目的时间重来没想过,好像生活中自己处理事情的时候也没怎么去想自己处理事情的方法所耗费的时间和资源

但是我之前刷ACM题的时候,有几次明明自己写的算法是正确的,但是网站判定的结果就是错误的,要么是时间超了,要么是空间超了,同样的一道题,不同人用的方法不一样,就是结果虽然可能一样,但是别人的算法就是没有超时,或者比自己的所耗费的时间少emmmmmm,这里就是涉及到复杂度的问题了。

但是这块复杂度又如何去算啊?

好像有方法,就是把代码跑一遍,然后看下时间,看下消耗的空间,这样好像也可以唉,但是仔细想下,这个方法很矛盾。

因为我不可能每次写个算法就都重新跑一遍,然后看下时间和空间emmmm这样有点傻,但是意思可以的

这个方法有两个很大的局限性:



  1. 测试结果非常依赖测试环境

    这个很明显啊,我拿个高处理器的电脑去处理同一段代码,和拿个低处理器的处理,肯定高处理器的在其他条件相同的情况下的时候,它处理的时间快啊

  2. 测试结果受数据规模的影响很大

    就好比排序算法一样,数据规模大小很影响排序算法的性能

说了这么多,我们到底需要什么样的分析算法的方式



  • 简单,明了,能在代码运行之前看出来//肯定不是很精确的

  • 不和测试机器环境有关

  • 不受测试规模影响


大O复杂度表示法


大O表示法:称一个函数g(n)是O(f(n)),当且仅当存在常数c>0和n0>=1,对一切n>n0均有|g(n)|<=c|f(n)|成立,也称函数g(n)以f(n)为界或者称g(n)受限于f(n)。记作g(n)=O(f(n))。 定义:如果一个问题的规模是n,解这一问题的某一算法所需要的时间为T(n),它是n的某一函数。T(n)称为这一算法的“时间复杂度”。当输入量n逐渐加大时,时间复杂度的极限情形称为算法的“渐近时间复杂度”。


这个就是百度百科上面的定义,but看这个概念一般人还真的是搞不懂什么意思,反正我当时学的时候也不知道这个是什么鬼意思。但是其实这个概念理解起来也是蛮简单的

因为我们要看代码的执行效率,所以我们要看代码的执行时间和占的空间//占的空间这里简单的小规模算法先不考虑

所以,我们在乎的代码执行的时间,但是我们又如何估算呢?

先看下这个代码

int text(int n){
int sum = 0; //1
int i,n; //2
for (i = 0; i sum += i; //4
}
return sum;
}

我们看下上面这个代码块,一共就执行的就4个重要代码,每段代码块就类似操作,尽管不一样,但是按照现在cpu执行的那么高速的样子,其实每个代码都只执行一点点时间,所以我们认为他们运行的时间都是一样的,记成一Time

现在上面的代码,第1个标记,第2个标记,分别执行了一个Time,然后第3个标记和第4个标记的位置的,是分别执行一个n*Time的时间

所以,总的来说,这段代码运行时间就是(2n+2)*Time,随着n的增大而增大

我们设代码执行的总时间为T(n)

所以,T(n) = (2n+2)*Time 代码执行时间和n成正比

所以,代码执行时间和每行代码的执行的次数成正比

综上所述,大O表示法就出来了

T(n) = O(f(n))

上面公式的个个含义:



  • T(n)表示代码的总的总的执行时间

  • n表示数据规模的大小

  • f(n)表示每行代码的执行次数和

  • 公式中的O表示正比的意思

要注意的点:大O时间复杂度并不是代码的具体执行时间,只是一种趋势,一种变化的趋势

当n特别特别大的时候,可以忽略不记常数项对这个的影响,我们上面代码里面,T(n) = (2n+2)Time 展开时候发现T(n) = 2Time+2nTime,当n为无穷大的时候

其实最后的结果就是:T(n) = O(n)


分析时间复杂度的三个原则


关注法则:只关心执行次数最大的那段代码

因为我们上面也看到了,大O就是一种变化趋势,那些计算的常量什么的其实都不重要,因为当n无穷大的时候,其他的常量,低价,系数什么的其实都没什么作用,所以,我们分析一段代码的时候,只需要看他循环次数最多的地方

int text(int n){
int sum = 0; //1
int i,n; //2
for (i = 0; i sum += i; //4
}
return sum;
}

我们继续看这个例子,这段代码就是只是关心下,执行次数最多的那给循环,其他的地方都无关紧要


加法法则:总的复杂度等于量级最大的那段

先看下下面的代码

int text(int n){
int sum1 = 0;
int sum2 = 0;
int sum3 = 0;
int i,j;
//1
for (i = 0; i <100;i++){
sum1 += i;
}
for(i = 0;i sum2 += i;
}
//3
for (i = 0;i for (j = 0;j sum3 += i;
}
}
return sum1+sum2+sum3;
}

我们再进行分析

第一段代码,就是执行了100次,所以这个就是个常量时间,和n无关

//就是能明显知道执行次数是具体数字的时候,这个常量就不能省略

第二段代码,时间复杂度就是O(n)

第三段代码,时间复杂度就是O(n*n)

然后我们要求这段总的代码的时间复杂度的时候,还是比较下,选最大的那一段,因为还是那句话,把n当成无穷大时候,就可以比较了

所以,这段代码的时间复杂度是O(n*n)


乘法法则:嵌套的复杂度等于里外复杂度的乘积

这个也好理解啊,冒泡就是典型的例子,但是我们还是看下下面的例子

int f(int n){
int sum = 0;
int i;
for(i = 0;i sum += i;
}
return sum;
}
int ff(int n){
int s = 0;
int i;
for(i = 0;i s = s + f(i);
}
return s;
}

这个代码也是很简单,就是一个代码里面调用另一个的函数

所以总的复杂度就是

T(n) = T(n1) * T(n2) = O(n*n)


几种常见的复杂度分析



  • 常量阶

  • 对数阶

  • 线性阶

  • 线性对数阶

  • k方阶

  • 指数阶

  • 阶乘阶

下面举例子说明


O(1) 常数阶

O(1)不是只是执行了一行代码的意思,它只是表示时间复杂度

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

这个整体有3行代码,但是还是O(1)的时间复杂度

只要代码的执行时间不随着n的增大而增大,就是O(1),一般情况下,就是算法中没有循环,递归这些的


O(logn)、O(nlogn)

这块高中数学要学好哇哇哇,对数这块都忘完了

看下下面的代码

int i = 1;
while (i <= n){
i = i * 2;
}

我们要求这块的时间复杂度就发行很尴尬,不知道要求多少次

一步一步分析,当变量i从1开始取的时候,每次循环都是乘以2,当它大于n的时候,循环结束

然后我们分析下i的变量的取值 2 ,22 ,222 , 22…

也就是2的阶乘

当2的x次方大于n的时候,就循环结束

所以 x = log2 n

所以,这个代码的时间复杂度就是O(log2 n)

然后我们在记对数的时候,通常忽略系数

所以,复杂度统一为O(lngn)

再利用乘法法则,像什么快排的时间复杂度就是O(nlong n)


O(m+n),O(m*n)

这块就是代码里面有两个数据规模,不是一个n了,所以这块要看下


int text(int m ,int n){
int sum1 = 0;
int sum2 = 0;

int i = 1;
for(i = 0; i sum1 += i;
}

for (i = 0; i sum2 += i;
}
return sum1 + sum2;
}

之前我们说的加法法则是看最大的那块,但是我们现在不知道那块最大,m,n都可以无穷大判定下,所以没办法比较,因此,这块的时间复杂度就是O(m+n)

乘法法则类似的套路


最好、最坏,平均,均摊情况时间复杂度

我们看了时间复杂度这块的概念和大O的表示方法,为什么还有有这四种情况呢,因为传入的数据可能会影响到最后的结果

很明显的例子就是排序算法

先看下一个例子

就是一个简单的从数组中找个数,看它在不在

int find(int []array,int n,int x){
int i = 0;
int pos = -1;
for (i = 0; i if (array[i] == x){
pos = i;
break;
}
}
return pos;
}

这块的分析也就尴尬了,因为我们发现这块程序结束的条件就是找到这个数就结束,或者找不到返回-1,遍历的过程次数不确定

当如果我们要找的数在第一个的时候,找到了,直接break了,那么时间复杂度就是O(1)

如果都找完了还是没找到,这个时候复杂度就是O(n),所以,不同情况下,这段代码的时间复杂度也是不同的

所以,引进了最好、最坏,平均,均摊情况时间复杂度的概念


最好时间复杂度

在最最理想的情况下,执行这段代码的时间复杂度,这块很好理解,比如我算法就是要找元素,第一个就是,所以,执行一次就找到了


最坏时间复杂度

在最最最坏的情况下,执行这段代码的复杂度,还是上面的例子,很简单,就是我找数组还是没找到,这就是最坏的情况


平均时间复杂度

在代码中心所有情况的次数加权平均表示


均摊时间复杂度

在代码执行的所有复杂度情况中绝大部分是低级别的复杂度,个别情况是高级别复杂度且发生具有时序关系时,可以将个别高级别复杂度均摊到低级别复杂度上。基本上均摊结果就等于低级别复杂度。


总结

下次分析时间复杂度的时候,自己也要分析下各种情况



推荐阅读
  • 计算机网络复习:第五章 网络层控制平面
    本文探讨了网络层的控制平面,包括转发和路由选择的基本原理。转发在数据平面上实现,通过配置路由器中的转发表完成;而路由选择则在控制平面上进行,涉及路由器中路由表的配置与更新。此外,文章还介绍了ICMP协议、两种控制平面的实现方法、路由选择算法及其分类等内容。 ... [详细]
  • 本文将介绍如何使用 Go 语言编写和运行一个简单的“Hello, World!”程序。内容涵盖开发环境配置、代码结构解析及执行步骤。 ... [详细]
  • 题目描述:给定n个半开区间[a, b),要求使用两个互不重叠的记录器,求最多可以记录多少个区间。解决方案采用贪心算法,通过排序和遍历实现最优解。 ... [详细]
  • 深入理解C++中的KMP算法:高效字符串匹配的利器
    本文详细介绍C++中实现KMP算法的方法,探讨其在字符串匹配问题上的优势。通过对比暴力匹配(BF)算法,展示KMP算法如何利用前缀表优化匹配过程,显著提升效率。 ... [详细]
  • 探讨一个显示数字的故障计算器,它支持两种操作:将当前数字乘以2或减去1。本文将详细介绍如何用最少的操作次数将初始值X转换为目标值Y。 ... [详细]
  • 本文详细介绍了Java编程语言中的核心概念和常见面试问题,包括集合类、数据结构、线程处理、Java虚拟机(JVM)、HTTP协议以及Git操作等方面的内容。通过深入分析每个主题,帮助读者更好地理解Java的关键特性和最佳实践。 ... [详细]
  • 本文探讨如何设计一个安全的加密和验证算法,确保生成的密码具有高随机性和低重复率,并提供相应的验证机制。 ... [详细]
  • 深入解析:手把手教你构建决策树算法
    本文详细介绍了机器学习中广泛应用的决策树算法,通过天气数据集的实例演示了ID3和CART算法的手动推导过程。文章长度约2000字,建议阅读时间5分钟。 ... [详细]
  • 在金融和会计领域,准确无误地填写票据和结算凭证至关重要。这些文件不仅是支付结算和现金收付的重要依据,还直接关系到交易的安全性和准确性。本文介绍了一种使用C语言实现小写金额转换为大写金额的方法,确保数据的标准化和规范化。 ... [详细]
  • 在给定的数组中,除了一个数字外,其他所有数字都是相同的。任务是找到这个唯一的不同数字。例如,findUniq([1, 1, 1, 2, 1, 1]) 返回 2,findUniq([0, 0, 0.55, 0, 0]) 返回 0.55。 ... [详细]
  • 本文探讨了卷积神经网络(CNN)中感受野的概念及其与锚框(anchor box)的关系。感受野定义了特征图上每个像素点对应的输入图像区域大小,而锚框则是在每个像素中心生成的多个不同尺寸和宽高比的边界框。两者在目标检测任务中起到关键作用。 ... [详细]
  • 网络攻防实战:从HTTP到HTTPS的演变
    本文通过一系列日记记录了从发现漏洞到逐步加强安全措施的过程,探讨了如何应对网络攻击并最终实现全面的安全防护。 ... [详细]
  • 本文深入探讨了Linux系统中网卡绑定(bonding)的七种工作模式。网卡绑定技术通过将多个物理网卡组合成一个逻辑网卡,实现网络冗余、带宽聚合和负载均衡,在生产环境中广泛应用。文章详细介绍了每种模式的特点、适用场景及配置方法。 ... [详细]
  • 本文探讨了如何在给定整数N的情况下,找到两个不同的整数a和b,使得它们的和最大,并且满足特定的数学条件。 ... [详细]
  • 深度学习理论解析与理解
    梯度方向指示函数值增加的方向,由各轴方向的偏导数综合而成,其模长表示函数值变化的速率。本文详细探讨了导数、偏导数、梯度等概念,并结合Softmax函数、卷积神经网络(CNN)中的卷积计算、权值共享及池化操作进行了深入分析。 ... [详细]
author-avatar
快乐生活HAPPY-GO
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有