草庐IT

复杂度分析

CJuncheng 2023-03-28 原文

复杂度

复杂度分析是数据结构与算法的核心精髓,指在不依赖硬件、宿主环境、数据集的情况下,粗略推导,考究出算法的效率和资源消耗情况, 包括时间复杂度和空间复杂度

时间复杂度

首先从CPU的角度看待程序,每行代码执行的操作都包括:读程序写程序运算。这里粗略估计,忽略每行代码读程序和写程序的时间
假设每行代码执行(运算)的时间都是一样的,为单位时间(即假设程序执行一次均消耗单位时间)

\(O\)记号

\[T(n)=O(f(n)) \]

其中:

  • \(T(n)\): 程序执行总时间
  • \(n\): 数据规模大小
  • \(f(n)\): 程序执行的总次数
  • \(O\):程序执行总时间\(T(n)\)\(f(n)\)成正比

\(O\)记号按最坏的情况来估计程序执行时间;大\(\Omega\)记号按最好情况估计程序执行时间;大\(\Theta\)记号按平均情况来估计程序执行时间。后文只分析大\(O\)记号。

\(O\)记号的性质:

  • 对任意常数\(c>0\), \(O(f(n))=O(cf(n))\)
  • 对任意常数\(a>b>0\), \(O(n^a+n^b)=O(n^a)\)

常见时间复杂度

  1. 常数阶\(O(1)\)
public void sum(int n) {
    int sum = 0; // 执行一次
    sum = n*2; // 执行一次
    System.out.println(sum); // 执行一次
}

程序执行常数次,与问题规模\(n\)无关,复杂度记为\(O(1)\)

  1. 对数阶\(O(logn)\)
public void logarithm(int n) {
    int count = 1; // 执行一次
    while (count <= n) { // 执行logn次
        count = count*2; // 执行logn次
    }
}

该段代码什么时候会停止执行呢?是当count大于n时。也就是说多少个2相乘后其结果值会大于\(n\),即\(2^x=n\)。由\(2^x=n\)可以得到\(x=logn\),所以这段代码时间复杂度是\(O(logn)\)

  1. 线性阶\(O(n)\)
public void circle(int n) {
    for(int i = 0; i < n; i++) { // 执行n次
        System.out.println(i); // 执行n次
    }
}
  1. 线性对数阶 \(O(nlogn)\)
    线性对数阶\(O(nlogn)\)就是将一段时间复杂度为\(O(logn)\)的代码执行\(n\)
public void logarithm(int n) {
    int count = 1;
    for(int i = 0; i < n; i++) { // 执行n次
        while (count <= n) { // 执行logn次
            count = count*2; // 执行nlogn次
        }
    }
}
  1. 平方阶 \(O(n^2)\)
    双重for循环
public void square(int n) {
    for(int i = 0; i < n; i++){ // 执行n次
        for(int j = 0; j <n; j++) { // 执行n次
            System.out.println(i+j); // 执行n方次
        }
    }
}

复杂度分析

示例如下(限定条件: \(0<n\)\(0<x\)\(n\)\(x\)为整数):

 public int Function(int n, int x)
 {
    int sum = 0;
    for (int i = 1; i <= n; ++i)
    {
        if (i == x)
            break;
            sum += i;
    }
    return sum;
  }
  • \(x>n\)时,此代码的时间复杂度是\(O(n)\)
  • \(1<=x<=n\)时,时间复杂度是一个我们不确定的值,取决于x的值
  • \(x=1\)时,时间复杂度是\(O(1)\)

这段代码在不同情况下,其时间复杂度是不一样的。所以为了描述代码在不同情况下的不同时间复杂度,我们引入了最好、最坏、平均时间复杂度。

  1. 最好情况时间复杂度(best case)

最好情况时间复杂度,表示在最理想的情况下,执行这段代码的时间复杂度。
上述示例就是当x=1的时候,循环的第一个判断就跳出,这个时候对应的时间复杂度就是最好情况时间复杂度。

  1. 最坏情况时间复杂度(worst case)

最坏情况时间复杂度,表示在最糟糕的情况下,执行这段代码的时间复杂度。
上述示例就是\(n<x\)的时候,我们要把整个循环执行一遍,这个时候对应的时间复杂度就是最坏情况时间复杂度。

  1. 平均情况时间复杂度(average case)

好和最好情况是极端情况,发生的概率并不大。为了更有效的表示平均情况下的时间复杂度,引入另一个概念:平均情况时间复杂度
分析上面的示例代码,判断x在循环中出现的位置,有\(n+1\)种情况:\(1<=x<=n\)\(n<x\)。将所有情况下代码执行的次数累加起来,然后再除以所有情况数量,就可以得到平均情况时间复杂度,即

\[\frac{((1+2+3+\cdots+n)+n)}{(n+1)}=\frac{n(n+3)}{2(n+1)} \]

\(O\)表示法会省略系数、低阶、常量,所以平均情况时间复杂度是\(O(n)\)

考虑概率的平均情况复杂度:

\(x\)要么在\(1\sim n\)中,要么不在\(1\sim n\)中,所以它们的概率都是\(1/2\)。同时数据在\(1\sim n\)中各个位置的概率都是一样的为1/n。根据概率乘法法则,\(x\)\(1\sim n\)中任意位置的概率是\(1/(2n)\)。因此在前面推导过程的基础上,我们把每种情况发生的概率考虑进去,得到考虑概率的平均情况复杂度

\[1\dfrac{1}{2n}+2\dfrac{1}{2n}+3\dfrac{1}{2n}+\cdots+n\dfrac{1}{2n})+n\dfrac{1}{n}=\frac{3n+1}{4} \]

引入概率之后,平均复杂度变为\(O(\frac{3n+1}{4})\),忽略系数及常量后,最终得到加权平均时间复杂度\(O(n)\)

多数情况下,我们不需要区分最好、最坏、平均情况时间复杂度。只有同一块代码在不同情况下时间复杂度有量级差距,我们才会区分3种情况,为的是更有效的描述代码的时间复杂度。

  1. 均摊情况时间复杂度

均摊复杂度是一个更加高级的概念,它是一种特殊的情况,应用的场景也更加特殊和有限。对应的分析方式称为:摊还分析平摊分析
示例如下(限定条件:\(0\leq x\leq n\)\(0\leq n\)\(n\), \(x\)为整数):

int n;
public int Function2(int x)
{
    int count = 0;
    if (n == x)
    {
        for (int i = 0; i < n; i++)
            count += i;
    }
    else
        count = x;
    return count;
 }

共有\(n+1\)种情况,每种情况的概率均为\(\dfrac{1}{n+1}\)\(0\leq x<n\)时,时间复杂度为\(O(1)\); 当\(x=n\)时,时间复杂度为\(O(n)\)
平均时间复杂度为:

\[(1+1+\cdots+1)\dfrac{1}{n+1}+n\dfrac{1}{n+1}=\dfrac{2n}{n+1} \]

当省略系数及常量后,平均时间复杂度为\(O(1)\)
通过分析可以看出,上述示例代码复杂度遵循一定的规律,对应1个\(O(n)\),和n个\(O(1)\)。针对这样一种特殊场景使用更简单的分析方法:摊还分析法, 即把耗时多的复杂度均摊到耗时低的复杂度,得到对应的均摊时间复杂度为O(1)。

  • 均摊时间复杂度是将高高复杂度均摊到其余低复杂度,故一般均摊时间复杂度就等于最好情况时间复杂度。
  • 均摊时间复杂度是一种特殊的平均复杂度(特殊应用场景下使用)

空间复杂度

空间复杂度定性地描述该算法或程序运行所需要的存储空间大小,算法空间复杂度的计算公式记作:\(S(n)=O(f(n))\),其中\(n\)为问题的规模,\(f(n)\)为语句关于\(n\)所占存储空间的函数。

参考:

有关复杂度分析的更多相关文章

  1. ruby - 使用 AES 的 Rails 加密,过于复杂 - 2

    我在加密来self正在使用的第三方供应商的值时遇到问题。他们的指令如下:1)Converttheencryptionpasswordtoabytearray.2)Convertthevaluetobeencryptedtoabytearray.3)Theentirelengthofthearrayisinsertedasthefirstfourbytesontothefrontofthefirstblockoftheresultantbytearraybeforeencryption.4)EncryptthevalueusingAESwith:1.256-bitkeysize,2.25

  2. ruby - 测试一个复杂的方法 - 2

    我正在开发西洋跳棋实现,其中有许多易于测试的方法,但我不确定如何测试我的主要#play_game方法。我的大多数方法都可以很容易地确定输入和输出,因此也很容易测试,但这种方法是多方面的,实际上并没有容易辨别的输出。这是代码:defplay_gameputs@gui.introwhile(game_over?==false)message=nil@gui.render_board(@board)@gui.move_requestplayer_input=getscoordinates=UserInput.translate_move_request_to_coordinates(play

  3. ruby - 是否有用于复杂比较的漂亮语法? - 2

    方法应返回-1,0或1分别表示“小于”、“等于”和“大于”。对于某些类型的可排序对象,通常将排序顺序基于多个属性。以下是可行的,但我认为它看起来很笨拙:classLeagueStatsattr_accessor:points,:goal_diffdefinitializepts,gd@points=pts@goal_diff=gdenddefothercompare_pts=pointsother.pointsreturncompare_ptsunlesscompare_pts==0goal_diffother.goal_diffendend尝试一下:[LeagueStats.new(

  4. ruby-on-rails - 如何构建复杂的 Rails 系统 - 2

    关闭。这个问题需要更多focused.它目前不接受答案。想改进这个问题吗?更新问题,使其只关注一个问题editingthispost.关闭8年前。Improvethisquestion我们有以下(以及更多)系统,我们将数据从一个应用推送/拉取到另一个:托管CRM(InsideSales.com)Asterisk电话系统(内部)横幅广告系统(openx,我们托管)潜在客户生成系统(自行开发)电子商务商店(spree,我们托管)工作板(本土)一些工作网站抓取+入站工作提要电子邮件传送系统(如Mailchimp,自主开发)事件管理系统(如eventbrite,自主开发)仪表板系统(大量图表和

  5. 建模分析 | 平面2R机器人(二连杆)运动学与动力学建模(附Matlab仿真) - 2

    目录0专栏介绍1平面2R机器人概述2运动学建模2.1正运动学模型2.2逆运动学模型2.3机器人运动学仿真3动力学建模3.1计算动能3.2势能计算与动力学方程3.3动力学仿真0专栏介绍?附C++/Python/Matlab全套代码?课程设计、毕业设计、创新竞赛必备!详细介绍全局规划(图搜索、采样法、智能算法等);局部规划(DWA、APF等);曲线优化(贝塞尔曲线、B样条曲线等)。?详情:图解自动驾驶中的运动规划(MotionPlanning),附几十种规划算法1平面2R机器人概述如图1所示为本文的研究本体——平面2R机器人。对参数进行如下定义:机器人广义坐标

  6. 网站日志分析软件--让网站日志分析工作变得更简单 - 2

    网站的日志分析,是seo优化不可忽视的一门功课,但网站越大,每天产生的日志就越大,大站一天都可以产生几个G的网站日志,如果光靠肉眼去分析,那可能看到猴年马月都看不完,因此借助网站日志分析工具去分析网站日志,那将会使网站日志分析工作变得更简单。下面推荐两款网站日志分析软件。第一款:逆火网站日志分析器逆火网站日志分析器是一款功能全面的网站服务器日志分析软件。通过分析网站的日志文件,不仅能够精准的知道网站的访问量、网站的访问来源,网站的广告点击,访客的地区统计,搜索引擎关键字查询等,还能够一次性分析多个网站的日志文件,让你轻松管理网站。逆火网站日志分析器下载地址:https://pan.baidu.

  7. ABB-IRB-1200运动学分析MATLAB RVC工具分析+Simulink-Adams联合仿真 - 2

    一、机器人介绍        此处是基于MATLABRVC工具箱,对ABB-IRB-1200型号的微型机械臂进行正逆向运动学分析,并利Simulink工具实现对机械臂进行具有动力学参数的末端轨迹规划仿真,最后根据机械模型设计Simulink-Adams联合仿真。 图1.ABBIRB 1200尺寸参数示意图ABBIRB 1200提供的两种型号广泛适用于各作业,且两者间零部件通用,两种型号的工作范围分别为700 mm 和 900 mm,大有效负载分别为 7 kg 和5 kg。 IRB 1200 能够在狭小空间内能发挥其工作范围与性能优势,具有全新的设计、小型化的体积、高效的性能、易于集成、便捷的接

  8. 关于Qt程序打包后运行库依赖的常见问题分析及解决方法 - 2

    目录一.大致如下常见问题:(1)找不到程序所依赖的Qt库version`Qt_5'notfound(requiredby(2)CouldnotLoadtheQtplatformplugin"xcb"in""eventhoughitwasfound(3)打包到在不同的linux系统下,或者打包到高版本的相同系统下,运行程序时,直接提示段错误即segmentationfault,或者Illegalinstruction(coredumped)非法指令(4)ldd应用程序或者库,查看运行所依赖的库时,直接报段错误二.问题逐个分析,得出解决方法:(1)找不到程序所依赖的Qt库version`Qt_5'

  9. ruby - 了解 Ruby Enumerable#map(具有更复杂的 block ) - 2

    假设我有一个函数defodd_or_evennifn%2==0return:evenelsereturn:oddendend我有一个简单的可枚举数组simple=[1,2,3,4,5]然后我用我的函数在map中运行它,使用一个do-endblock:simple.mapdo|n|odd_or_even(n)end#=>[:odd,:even,:odd,:even,:odd]如果不首先定义函数,我怎么能做到这一点?例如,#doesnotworksimple.mapdo|n|ifn%2==0return:evenelsereturn:oddendend#Desiredresult:#=>[

  10. ruby-on-rails - 如何使用 ruby​​-prof 和 JMeter 分析 Rails - 2

    我想使用ruby​​-prof和JMeter分析Rails应用程序。我对分析特定Controller/操作/或模型方法的建议方法不感兴趣,我想分析完整堆栈,从上到下。所以我运行这样的东西:RAILS_ENV=productionruby-prof-fprof.outscript/server>/dev/null然后我在上面运行我的JMeter测试计划。然而,问题是使用CTRL+C或SIGKILL中断它也会在ruby​​-prof可以写入任何输出之前杀死它。如何在不中断ruby​​-prof的情况下停止mongrel服务器? 最佳答案

随机推荐