数据结构与算法-复杂度分析

时间、空间复杂度:衡量算法执行小路的指标,数据结构与算法离不开时间、空间复杂度分析,复杂度分析是算法的精髓。

为什么需要分析复杂度

为什么不直接去运行代码然后测试运行速度?
1、环境影响大
不同的硬件条件下处理器的处理速度不一样导致的性能差
2、数据集对结果的影响
如果数据规模较小,测试结果就无法真实反映算法效率
复杂度分析是一个不需要具体测试数据来测试,粗略计算出执行效率的一种方法。

大O复杂度表示法

算法复杂度其实就是算法代码执行的时间。
如何不运行代码粗略估算时间:

int sum(int n){
    int i=0;
    int sum=0;
    for(;i<n;i++){
        sum+=i;
    }
    return sum;
}

用上面这个累加算法为例子说明,因为是粗略计算,现在假定CPU执行一行代码的时间为t,然后整个sum方法的执行时间为:1+1+n+n+1=(2n+3)*t;可以看出代码总的执行时间和n成正比,总结出大O表示公式为:

其中,T(n)表示代码执行的时间,n为数据集规模,f(n)是一个公式,以上述例子说明就是2n+3,所以T(n)=O(2n+3),
大O表示法只是代表代码执行的时间的变化趋势,并不能得出真正执行时间,所以也叫渐进时间复杂度,简称时间复杂度。当n趋向于很大时,常数项的影响微乎其微,所以我们常常将常公式中公式中的低阶、常量、系数三部分去掉。所以,上述例子就可以表示为:

常用时间复杂度分析方法:
1、只关注循环最多的一段代码
由于大O复杂度表示方法只是表示一种变化趋势,常量、低阶和系数这些并不会对变化趋势造成很大影响,所以我们一般选择忽略。
2、加法法则
如下代码:

int cal(int n) {
   int sum_1 = 0;
   int p = 1;
   //T1
   for (; p < 100; ++p) {
     sum_1 = sum_1 + p;
   }

   int sum_2 = 0;
   int q = 1;
   //T2
   for (; q < n; ++q) {
     sum_2 = sum_2 + q;
   }
 
   int sum_3 = 0;
   int i = 1;
   int j = 1;
   //T3
   for (; i <= n; ++i) {
     j = 1; 
     for (; j <= n; ++j) {
       sum_3 = sum_3 +  i * j;
     }
   }
 
   return sum_1 + sum_2 + sum_3;
 }

我们只关注循环部分,T1、T2、T3,由于T1只循环100此,跟n无关,可以忽略,T2=O(n),T3=O(n^2),有这么一个结论:代码总的时间复杂度等于量级最大的那段代码的时间复杂度:

所以上面这个例子的时间复杂度为O(n^2)
3、乘法法则
例子:

//Tn
int fun1(int n) {
   int sum1 = 0; 
   int i = 1;
   //T1
   for (; i < n; ++i) {
     sum = ret + f(i);
   } 
 } 
 
 int fun2(int n) {
  int sum2 = 0;
  int i = 1;
  //T2
  for (; i < n; ++i) {
    sum2 = sum2 + i;
  } 
  return sum2;
 }

由于T1里循环执行了fun2方法,fun2里T2=O(n),T1也是O(n),所以总的时间复杂度就是:


4.jpg

总结乘法公式:
若:T1(n)=O(f(n)),T2(n)=O(g(n)),则Tn=T1(n)T2(n)=O(f(n))O(g(n))=O(f(n)*g(n))

常见复杂度量级
多项式量级 非多项式量级
常量阶O(1) 指数阶O(2^n)
对数阶O(logn) 阶乘阶O(n!)
线性阶O(n)
线性对数阶O(nlogn)
N次方台阶O(n^N)

差别:非多项式阶在数据随着n增大,时间会爆炸式增加,这类是非常低效的算法,不推荐使用。
1、O(1)
常量级复杂度,比如代码只有5行10行或者1000行这些,只要代码的执行时间不随n增大而增大,有限数量级的都归为O(1)复杂度;我们平时在分析时,只要代码不存在循环、递归语句,代码再多,也可以算是O(1)复杂度。
2、O(logn)、O(nlogn)
对数阶复杂度,比如下面这样的代码:

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

循环终结条件是i<=n,
n=2^x,求出x就能得出代码,其中


当然上面是用的2为例子说明,至于O(logn)是怎么来的呢?其实是根据对数的换底公式推导得出的:

举个栗子:我们上面的2换成3,会有复杂度为:

上面说过分析复杂度时常数可以去掉不算,推导下来还是会算回以2为底时一样的复杂度,因此,我们可以将对数的底忽略掉,统一用O(logn)表示。

3、O(m+n)
m、n为两个不同数量级的数据,且无法确定谁大谁小,也就不能根据前面的加法法则来说胜率掉某一个,这个时候算法复杂度就得改为:
T(n)=T1(m)+T2(n)=O(f(m))+O(f(n))=O(m+n)

空间复杂度

算法复杂度分时间复杂度和空间复杂度,上面分析了时间复杂度,空间复杂度分析原理是一样的,时间复杂度算的是代码运行的时间,而空间复杂度则算代码所占用的内存大小,举个栗子:

void fun(int n){
    int i=1;
    int[] arr=new int[n];
    for(;i<n;i++){
        arr[i]=i;
    }
}

跟时间复杂度一样,i常量声明所占用的空间为1,可以忽略,我们着重看循环部分,没循环一次就要申请一次内存,所以整个代码的空间复杂度为O(n)。

最好、最坏时间复杂度

例子:

int findItem(int[] array,int x){
    int n=array.lenth;
    int item=-1;
    for(int i=0;i<n;i++){
        if(array[i]==x){
            return i;
        }
    }
    return item;
    
}

上面这段代码是在一个数组中找到某个值的下标,这段代码的复杂度如果用上面介绍的几种方法是没办法准确得出复杂度的,分析它需要用到新的复杂度概念:最好、最坏时间复杂度;加入我们要找的元素在第一个位置,那么只要运行一次的循环就可以结束代码,那么复杂度就是O(1),如果很不幸,我们要找的元素根本不在数组中,那么就会出现需要循环n次才能结束代码,那么此时的复杂度就变为O(n)了,这两个就是分别对应最好、最坏时间复杂度。

平均时间复杂度

前面的最好最坏时间复杂度对应的都是极端情况下的复杂度,没办法作为衡量算法好坏的标准,所以就引入了平均时间复杂度。我们继续分析上面的代码例子,首先列出所有的可能,一共会有n+1中情况,当我们要找的元素在数组n到n-1中某一个时有n中可能,此时需要遍历的次数分别为1到n次,再加上一种就是元素不在数组中,此时也需要遍历n次,所以一共需要遍历的次数加起来就是1+2+3+...+n+n,然后算一下每一项的概率,由于存在在或者不在数组的情况,假定概率都为1/2,然后在的情况下每项的概率又为1/n,所以组合起来就是1/2n,然后开始求和,公式为:

N=1*1/2n+2*1/2n+3*1/2n+...+n*1/2n+n*1/2 

最后这个n为要找的元素不在数组里,所以乘上的概率是1/2.

所以平均时间复杂度O(n)为:


均摊时间复杂度

均摊时间复杂度分析的情况是,比如一个算法,在大多数情况下复杂度都为O(1),但当符合某个条件时会变为O(n),然后这个时候均摊时间复杂度就能有所用处了。比如java中ArrayList的自动扩容:

add()方法的源码如下:
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);
        elementData[size++] = e;//添加对象时,自增size
        return true;
    }
 
    private void ensureCapacityInternal(int minCapacity) {
        modCount++;//定义于ArrayList的父类AbstractList,用于存储结构修改次数
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }  
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);//新容量扩大到原容量的1.5倍,右移一位相关于原数值除以2。
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;//MAX_ARRAY_SIZE和Integer.MAX_VALUE为常量,详细请参阅下面的注解
    }

在容量充足的情况下,只需要执行插入操作,此时复杂度为O(1),但是当容量满了的时候,就需要扩容,此时的复杂度变为O(n),然后算均摊时间复杂度,假设在到达刚好扩容的数据一共有n个,那么前n种复杂度都为O(1),第n此为O(n),每种情况的概率都为1/n+1,最后:


总结

基本复杂度分析到这就学完了,这些方法都会贯穿整个算法学习的全部,所以要牢固掌握。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容