数据结构-第二章 算法分析

本文详细解析了算法分析的两个关键方面:时间复杂度和空间复杂度,分别讨论了计算方法、数据区划分(固定部分和变量部分)、实例演示,以及如何使用大O、Omega和Theta表示法来表达复杂度。涵盖了常见编程概念如指令空间、数据空间和环境栈空间,以及如何评估实际运行中的内存需求和操作计数。

Algorithm Analysis(算法分析)
分析一个算法的时间和空间效率

1. 两个方法来进行算法分析

1.1. performance analysis

1.2. performance measurement

2. 空间复杂度(Space complexity)

  1. the amount of memory a program needs to run to completion 运行完成程序所需要的内存空间

2.1. 组件(components)

  1. 一个程序在电脑上运行时存储所需要的内存占用。

2.1.1. instruction space(指令空间)

  1. 这个是指程序编译成的二进制码的空间大小。
  2. 或者称为代码区,代码空间。
  3. 代码空间在某些情况下是不计算的:
    • 为什么:这部分和你的算法内容是无关的,更多的是系统对于二进制码的执行。
    • 同学们的源代码编译而成二进制码在载入内存时越大越大。这个对于不同软件的差异会很大。
    • 在windows里面的动态链接库就是用来处理这个问题,没必要一次性导入全部的二进制码。

2.1.2. data space(数据空间)

  1. space needed for constants,simple variables,component variables 常量、基本变量、成员变量所需的内存空间
  2. 数据区,主要是包括相应的数据等。各种变量常量的空间。

2.1.3. environment stack space(环境栈空间)

  1. to save information needed to resume execution of partially completed functions 保存恢复执行部分完成的函数所需的信息
  2. 栈空间、堆空间等。
    • 恢复执行程序功能而是用的空间。
    • 是一个比较细枝末节的空间。

2.1.4. 不计算的空间

  1. 操作系统的额外保留空间是不算的。(在计算算法复杂度的时候不考虑这个问题)

2.2. 空间复杂度讨论的数据区部分

2.2.1. a fixed part 定长部分

  1. include space for instructions,simple variables,fixed-size component variables,constants 包含指令、简单变量、定长度的成员变量、常量的内存空间
  2. 这部分空间是一定会存在的。
  3. 常数0所占据的空间也会算入。(但是是一个常数级空间)两个0怎么样呢?
    • 影响不大,因为怎么算都是常量级别的

2.2.2. a variable part 变量部分

  1. include space for component variables,dynamical allocated space,recursion stack 包含成员变量、动态分配空间、递归栈空间的内存空间部分
  2. 我们最关心的是这部分,也就是随着数据量的增长而带来的问题。

2.3. 算法例子

2.3.1. 例一

  1. 在调用相应函数之前的空间是不进行计算的,所以数组并没有单独多余输入。
  2. a是一个指针。

结果

  1. 例子机器是16个字节
  2. S(n):S表示space
  3. S(n) = 0:的含义是指占用空间是常数级的。
不同机器位数每个int占据的大小S(n)
16位2 bytes12 bytes
32位4 bytes24 bytes
64位8 bytes64 bytes

2.3.2. 例二

结果

  1. 只要你写了局部变量,局部变量静态分布,在执行局部之前就已经分配好了而不是等到执行的时候才分配地址。
  2. 返回值地址需要被存储(不然无法进行返回)

2.4. 为什么不是用空间增长率来表示空间复杂度

注意:S(n)并不是空间增长的导数。

3. 时间复杂度(Time complexity)

  1. the amount of time a program needs to run to completion 程序运行完成所需要的时间
  2. 编译时间是不计算近时间复杂度,一般来说我们的编译次数是远远小于运行时间,一般只消耗一次。

3.1. 计算程序的时间复杂度

  1. 关键的是计数中间的关键的操作个数。
    • 格外重要的是在循环中的操作个数
  2. 第一个例子:

  1. 先假设所有的操作都进行计算
    • 1(pos)+1(i)+(n-1)(for): 包含赋值语句,还包括for循环中每次都必须要做的语句。
    • 在循环中:首先进行条件判断,然后有一个可能做也可能不做的操作,然后是做i++,所以一个循环中最少是3倍,最多是4倍操作。
    • 所以操作数在(1+1+3(n-1)+1)-(1+1+4(n-1)+1)之间。
    • 不要太纠结这个非这个层次的操作个数
  2. 为什么计算时间复杂度比较模糊、很难精准表示?
    • 因为很多操作可能不止一个操作。
    • CPU执行不同指令的速度是不同的。

3.1.1. 补充:CPU和GPU的区别

  1. 在GPU中计算条件指令的速度很慢,但计算图形界面指令很快。而CPU则相反。
  2. GPU擅长负责图形(三维)、矩阵运算。
  3. CPU擅长运算数理逻辑。

3.1.2. 问题:我们需要计算到多么精细是合适的?

  1. 我们只需要做到时间复杂度的数量级即可。

3.2. 不同情况下的时间复杂度的情况

3.2.1. 最好情况

3.2.2. 平均情况

  1. 这个计算起来时很复杂的。
    • 其前提是所有情况的概率是一致的。
  2. 使用算术平均值来代替平均情况是一个简化的运算,在考试中是可以这么进行计算,但是并不能完全代表平均情况。

3.2.3. 最差情况

3.2.4. 例子

  1. 最好情况下:一次性结束,第一次找到
  2. 最坏情况下:n次结束,最后一次找到
  3. 平均情况下:(n+1)/2,使用算术平均值

  1. 不等概率下如何进行计算?
    • 将对应的位置上的概率加权进行计算。

4. 如何统计大规模的算法步数

  1. 使用整形变量count,并且加入到相应的必要的地方进行计数。
  2. 为程序加入计数器
  3. 第四个count是表示跳出的条件判断

5. 渐进性时间分析方法

  1. 我们更加关心的是文件规模比较大的情况下的算法时间复杂度问题。

5.1. 大O表示法

  1. provide a upper bound for the function f.表示函数 f 的上界
  2. 定义:f(n)=O(g(n)) iff positive constant c and n0 exist such that f(n) <=c * g(n) for all n, n>=n0
  3. 例子:

  1. 复杂度排序:从小到大
    1. O(1)
    2. O(log n)
    3. O(n)
    4. O(nlogn)
    5. O(n2)
    6. O(n3)
    7. O(2n)
    8. O(3n)
    9. O(n!)
    10. O(nn)
    11. O(nn...n)

5.1.1. 最大子序列的和

  1. MAXIMUM SUBSEQUNCE SUM PROBLEM 给定整数a1,a2,…,an (可能有负数),求∑ak的最大值。
    • 求最大子序列和
  2. 算法:
public static int maxSubSum1( int [] a ) {   
    int maxSum = 0; 
    for ( int i = 0; i < a.length; i++ )
        //双指针i和j卡两边,全遍历
        for ( int j = i; j < a.length; j++ ) {
            int thisSum = 0;
            for ( int k = i; k <= j; k++ ) 
                thisSum += a[k];
            if ( thisSum > maxSum )
                maxSum = thisSum;
        }
    return maxSum;
}
  1. 时间复杂度:O(n3)

算法优化

  1. 例子:通过前一个子序列加上之后的一个元素的和即为结果。
public static int maxSubSum2( int [] a ) {   
    int maxSum = 0; 
    for( int i = 0; i < a.length; i++ ) {   
        int thisSum = 0; 
        for ( int j = i; j < a.length; j++ ) {
            //不从头加,进行累加
            thisSum += a[j]; 
            if ( thisSum > maxSum ) 
            maxSum = thisSum; 
        } 
    } 
    return maxSum; 
}

进一步的优化

  1. 我们使用分治法:将一个问题分为两个部分来解决。
  2. 分阶段:把问题分成两个大致相等的子问题,然后递归地对它们求解。
  3. 治阶段:将两个子问题的解合并到一起,可能在做些少量的附加工作,最后得到整个问题的解
  4. key:什么时候是两边都不是含有最大子序列的情况。
    • 如果是这样子的情况,那么左边的最右边和右边的最左边必定在这个序列中,通过生成法生成一个最大子序列。
    • 如果含有那么完成了优化。
  5. 优化代码
private static int maxSumRec( int [] a, int left, int right ) {   
    if ( left == right ) 
        if ( a[left] > 0 ) 
            return a[left];
        else  return 0;
    int center = ( left + right ) / 2;
    int maxLeftSum = maxSumRec( a, left, center );
    int maxRightSum = maxSumRec( a, center + 1, right );
    //向左生成局部最大子序列
    int maxLeftBorderSum = 0, leftBorderSum = 0;
    for ( int i = center; i >= left; i-- ) {
        leftBorderSum += a[i];
        if ( leftBordersum > maxLeftBorderSum )
            maxLeftBorderSum = leftBorderSum;
    }
    // 向右生成局部最大子序列
    int maxRightBorderSum = 0,rightBorderSum = 0; 
    for ( int i = center + 1; i <= right; i++ ) {
        rightBorderSum += a[i] ;
        if ( rightBorderSum > maxRightBorderSum )
            maxRightBorderSum =  rightBorderSum;
    }
    return max3( maxLeftSum, maxRightSun, maxLeftBorderSum + maxRightBorderSum );
    // 返回三者的最大值。
}
  1. 算法复杂度:O(nlog2n)
    • 先跳过去递归部分
    • for循环:两个for循环相当于遍历数组整整一遍。

算法复杂度 = 左递归 + 右递归 + O(n)
= 左左递归 + 左右递归 + 右左递归 + 右右递归 + 2 * O(n)
= …一共切(log2n)次
= O(log2n)*O(n)
= O(nlog2n)

5.1.2. 求最大公因数(GCD)

  1. 辗转相除法,用来求最大公因数。
public static long gcd( long m, long n ) {   
    while( n != 0 ) {    
        long rem = m % n; 
        m = n; 
        n = rem; 
    }
    return m;
}
  1. 分析最大公因数的算法复杂度
    1. 从逆向进推断:m1、m2、…、mn
    2. 前一个值等于后面一个值乘以商加上余数。也就是mi = p * mi+1 + mi+2
    3. 最坏情况下:p=1,p不可能为0。
      • 除了在一开始的情况下,p可能为0。
      • 最坏情况下是斐波那契数列。
      • 比如第一个数是一个亿,那么在最坏情况下我们就可以认为是斐波那契数列的第几项是一个亿。
    4. 复杂度是O(log N),在1884年有一个精确的证明(By Lame),也就是这个问题复杂度不会比斐波那契数列更差,转言之,斐波那契数列的算法复杂度是O(log N)

5.2. Omega Ω表示法

  1. 表示的是一个算法的复杂度在数量级上的下界。
  2. 定义:is the lower bound analog of the big Oh notation,permits us to bound the value f from below.

  1. 表示的是下限。

5.3. Theta θ表示法

  1. 不能向大了写,也不能向小了写。
  2. 考试中我们使用的表示法。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值