算法录 之 复杂度分析。

  一个算法的复杂度可以说也就是一个算法的效率,一般来说分为时间复杂度和空间复杂度。。。

  注意接下来说的均是比较YY的,适用与ACM等不需严格分析只需要大致范围的地方,至于严格的算法复杂度分析的那些数学证明,主定理什么的在《算法导论》这本书上有十分详细的讲解,网上应该也会有人写过,这里就不多说了(其实,是我不会而已o(╯□╰)o。。。)。

  — 到底啥是复杂度呢?先来个栗子。



   小明有10个苹果,有一天他饿了,然后准备吃掉一个苹果,但是小明有中二病,他要吃里面重量最大的那个,于是。。。他需要一个找到那个最大的,可是这应该怎么找呢?

   小明要先量一下第一个苹果多重,然后第二个多重,然后量第三个。。。一直到第十个,量的时候记录当前最重的那个,然后当找完了这十个就好了。。。

   下面来看看这个神奇的找苹果算法的复杂度,有10个苹果,所以需要测量10次才能找到,如果有100个苹果,显然需要测量100次,1000个呢,1000次,n个也就需要n次。

   下面设n为问题的规模,f(n)为运算次数,那么对于小明这个问题来说 f(n)=kn,k是一个常数,这个例子 k=1。(看到如此眼熟的高中数学气息有没有啥感觉。。。)



  — 不用多说就知道如果需要的运算次数多的话,需要花费的时间也就多,所以这就是时间复杂度了,对于上面那个问题的时间复杂度就是 f(n) 了。

  — 但是。。。对于一个问题的常数 k 是比较难搞定的,可能称一下苹果的重量需要一步操作,也可能两步,也可能好几步。所以对于时间复杂度的分析一般是去找渐进复杂度。简单说就是函数 f(n) 省略了常数和低次项,只留下最高次项。比如函数 6n^3+3n^2+2n+10 变成了 n^3 ,因为当n很大很大的时候,变化趋势就是 n^3 型的,再比如 2^n+n^5 就变成了 2^n,因为指数爆炸嘛。。。

  — 这里还要说说符号 O,o 和 Θ,这三个应该高数课会讲。。。O( f(n) ) 表示函数的上界,也就是一个一直比 f(n) 大的函数,然后 o 就是下界,至于  Θ 的话,叫做中界(我YY的一个名字)?也就是和 f(n) 的增长速度一样。。。不过一般来说之后的分析都是用 O 的,因为这个字母好写。。。饿,其实可以理解为因为O是上界,所以算法遇到再坏的情况也不会超过这个函数。

  — 对于一个算法来说一般常用的渐进复杂度函数有 O( n )  O( n^2 )  O( n^3 )  O(1)  O( 2^n )  O( n!)  O( log n)  O( n*logn )  O( n*2^n ) 差不多这些,注意 log 指以2为底的对数。。。函数图就像下面这样:

  — 然后分别比较看看哪种复杂度更好,显然 O(1)是最好的,因为不管问题的规模有多大,都能一下子得到答案。。。然后看看 O(n),小明的问题就是这个复杂度的,如果问题规模是n,需要运行n次,显然已经不错了,挺快的了。。。但是还有一个更快的,O(log n)的,如果n=1000000 那么才只需要运行 20次不到就能得到答案,但是 O(n)的却需要运行1000000次,你说哪个快。



举个栗子:

  要求输入一个n,然后算 1^2+2^2+3^2+4^2+...+n^2的值。

  那么先说一种做法,直接for循环,代码如下:

#include <iostream>

using namespace std;

int main()
{
    int n;
    int sum=0;
    cin>>n;

    for(int i=1;i<=n;++i)
        sum+=i*i;

    cout<<sum;

    return 0;
}

  显然这种算法的复杂度是 O (n) 的,因为运行了 n 次嘛。(注意 n 比较大的时候结果会超过 int 的表示范围,具体看 ACM录 常识与错误那篇文章有说。)

  然后看看第二种做法:推出公式来。1^2+2^2+。。。+n^2 = n*(n+1)*(2n+1)/6

  所以第二种做法就是

#include <iostream>

using namespace std;

int main()
{
    int n;

    cin>>n;
    cout<<n*(n+1)*(2*n+1)/6<<endl;

    return 0;
}

  这就是 O ( 1 ) 的复杂度了,那么哪个快就不说了。。。



  — 至于后面的 O( n^2 )  O( n^3 )  O( 2^n ) 啊啥的,也是这样算,上面那些里面复杂度最高,效率最差的应该是 O(n!),如果 n 是10,就需要运行 3628800 次,这效率,不多说了。。。

  — 上面说的是运行的次数,然后说说时间,对于现在的个人计算机来说,速度大约是一秒能运行10000000(7个0)次多,这个可以自己写个程序感受一下,for循环10000000次或者更多看看。。。一般来说如果只有加减法 100000000(8个0)次也很快,但是如果有了除法或者其他东西会慢一点。。。

#include <iostream>

using namespace std;

int main()
{
    int x;

    for(int i=0;i<1000000000;++i)
        x=123*321;    // 对比 x=123/321;

    return 0;
}

  — 然后对于一个题目来说一般限制了1秒啊2秒啊啥的,那么如果题目的数据规模是100000,那么如果采用的算法是 O(n^2)的,那么就需要运行 10000000000次,也就需要大约1000秒,显然超时了。。。如果算法是 O(n log n)的,那么大约是 1700000 次还是可以接受的。。。至于 O(n) O(1) O(log n)啥的就更不用说了。。。所以说解一个题目的话要注意数据范围和时间限制。。。

  — 这里顺便说下常数吧,之前都是把常数忽略了然后看的是一个大致的增长函数。但是如果常数很大,比如算法只有一个 for 循环,但是里面有1000次运算,那么虽然他的复杂度是 O(n)的,但是对于100000的规模需要运行100000000次,也就很可能会超时了。。。所以当常数比较大的时候要注意看看。。。

  — 至于空间复杂度的话,也就是用的空间的多少和数据规模n的函数,但是因为这个不是很常用而且和时间复杂度几乎差不多,就不多说了。。。

///////////////////////////////////////////////////

  — 那么怎么算复杂度呢,在一些算法和数学的书上有十分严格的数学方法。。。然而,平时不需要的。。。

  — 其实靠肉眼看看就差不多知道了,看看有几个循环,大致估算一下运行多少次,然后就知道了。。。这些当写代码前想算法的时候其实就已经大致了解了。。。

  — 当然这里对于存在递归的算法,就比较麻烦了,这里建议大家学了一些基本的算法之后再来看,因为这里实在是找不到简单的例子。这里有一个主定理(名字就叫主定理),用来计算递归的函数的复杂度计算。比如说一个算法是把问题分成两个小问题,然后在花费 g(n) 的复杂度来合并两个小问题得到大问题的解。那么函数差不多是 f(n)=2*f(n/2)+g(n) 至于 f(n) 怎么推,就可以用上面的那个主定理(这里可以去网上找找这个定理学习一下),其实。。。也可以先猜一下,然后带入那个等式看看行不行。。。

  — 经典的递推比如 f(n)=2f(n/2)+n 的复杂度就是 O(n log n)。。。所以如果 g(n) 如果比 O(n) 要好的话,显然最后的复杂度会比 O(n log n)更好。。。

  — 另外还有一些复杂度分析比如均摊分析就更丧心病狂了,这种分析是找平均值,期望值啥的,通过概率或者是其他什么东西证明运行比如100次的平均复杂度一定不会高于一个数,所以平均就是多少多少的,这个的话就不详细多说了,之后学习比较高级的算法的时候才会接触到。。。(其实是我不会。。。)

////////////////////////////////////////////////////

  复杂度分析感觉就这些东西,其实大部分算法的复杂度是一眼就能看出来,当然也有一些需要仔细分析这个算法的各种情况才行。。。

时间: 2024-11-03 21:52:13

算法录 之 复杂度分析。的相关文章

常用排序算法 - 稳定性和复杂度分析

一.前言 上一篇,只是简单的记录了常用算法的主要思想以及代码实现( 常用算法记录 ); 这次简单的记录一下算法的稳定性以及复杂度 二.稳定性 1. 稳定性的定义 如果两个相等的数据的先后位置,排序前后保持不变的话,那就是稳定的,反之,就是不稳定: 例如:A[i] == A[j] , A[i]的位置在A[j]之前,排序后,A[i]的位置依然在A[j]之前: 2. 稳定性的好处 (1)如果排序算法是稳定的,那么从一个键上排序,然后再从另一个键上排序,第一个键排序的结果可以为第二个键排序所利用. 基数

算法录 目录索引。

(注:本文持续更新中.) 忙完了九月的一系列比赛之后,十月初收拾了一下也该准备写点入门的东西了,顺便给许久不更新(太懒了)的博客拉拉访问量.不过限于个人水平的原因写的不好请别打我... 至于算法是啥这些思想性的东西就不多说了,几乎每本入门书都会提及到.而且这类哲学问题还是要大家自己去想去理解... 这一篇文章主要是目录索引,接下来的话算法录这个分类里面应该会慢慢更新三种类型的文章,分别是 算法录,数据结构录,ACM录,这三种也算是相互联系的... 算法录: 内容:主要说的是对一些算法的学习和讲解

Python 文本相似度分析

环境 Anaconda3 Python 3.6, Window 64bit 目的 利用 jieba 进行分词,关键词提取 利用gensim下面的corpora,models,similarities 进行语料库建立,模型tfidf算法,稀疏矩阵相似度分析 代码 # -*- coding: utf-8 -*- import jieba from gensim import corpora, models, similarities from collections import defaultdi

算法9-4:最大流算法复杂度分析

前面一节介绍了Ford-Fulkerson算法.那么这个算法是否一定能够在有限步骤内结束?要多少步骤呢? 这个问题的答案是,该算法确实能够在有限步骤之内结束,但是至于需要多少步骤,就要仔细分析. 为了分析问题,需要假定图中所有边的容量都是整数.但是有个严重的问题,比如下图中,如果使用Ford-Fulkerson算法,需要迭代200次才能结束. 首先将所有边的容量都初始化为0. 第一次迭代和第二次迭代之后,两条边各增加了1. 到最后200次迭代之后整个算法才结束. 这还不算最坏的情况.因为整数最多

比较排序算法及复杂度分析

比较排序算法分类 比较排序(Comparison Sort)通过对数组中的元素进行比较来实现排序. 比较排序算法(Comparison Sorts) Category Name Best Average Worst Memory Stability  插入排序  (Insertion Sorts) 插入排序 (Insertion Sort) n n2 n2 1 Stable 希尔排序 (Shell Sort) n n log2 n n log2 n 1 Not Stable  交换排序 (Exc

排序算法的时空复杂度、稳定性分析

1.基本概念 2.时空复杂度 3.稳定性 4.使用情况分析 排序算法总结(C语言版)已介绍排序算法的基本思想和C语言实现,本文只介绍时空复杂度和稳定性. 1.基本概念 时间复杂度: 一个算法花费的时间与算法中语句的执行次数成正比例,哪个算法中语句执行次数多,它花费时间就多.一个算法的语句执行次数称为语句频度或时间频度.记为T(n).n称为问题的规模,当n不断变化时,时间频度T(n)也会不断变化.但有时我们想知道它变化时呈现什么规律,为此,引入时间复杂度概念.若有某个辅助函数f(n),使得当n趋近

转 算法复杂度分析

转自 http://www.cnblogs.com/gaochundong/p/complexity_of_algorithms.html 为什么要进行算法分析? 预测算法所需的资源 计算时间(CPU 消耗) 内存空间(RAM 消耗) 通信时间(带宽消耗) 预测算法的运行时间 在给定输入规模时,所执行的基本操作数量. 或者称为算法复杂度(Algorithm Complexity) 如何衡量算法复杂度? 内存(Memory) 时间(Time) 指令的数量(Number of Steps) 特定操作

几种简单的求素数算法的复杂度分析

素数的算法有很多种,现在主要讲两种算法及其改进版本的复杂度分析,解释性能提升的幅度.现以求100000内素数为例,两种算法分别是: 1.基础思路是去掉偶数,包括取模的范围,代码如下: print(2) for i in range(3,100000,2): for a in range(3,int(i*0.5)+1,2): if i%a == 0: break else: print(i,end = ' ')此两层循环的算法的复杂度为0.5n((n**0.5+1)/2) 2.应用一个素数定理:大

01 | 复杂度分析(上):如何分析、统计算法的执行效率和资源消耗?

我们都知道,数据结构和算法本身解决的是“快”和“省”的问题,即如何让代码运行得更快,如何让代码更省存储空间.所以,执行效率是算法一个非常重要的考量指标.那如何来衡量你编写的算法代码的执行效率呢?这里就要用到我们今天要讲的内容:时间.空间复杂度分析.其实,只要讲到数据结构与算法,就一定离不开时间.空间复杂度分析. 而且,我个人认为,复杂度分析是整个算法学习的精髓,只要掌握了它,数据结构和算法的内容基本上就掌握了一半.其实,只要讲到数据结构与算法,就一定离不开时间.空间复杂度分析. 复杂度分析实在太